2016年9月13日
C90, C99, C11, C++98, C++11で異なる動作をするコード
本記事は、原著者の許諾のもとに翻訳・掲載しております。
(訳注:2016/9/28、頂きましたフィードバックを元に記事を修正いたしました。)
C言語の規格のリビジョン間には微妙な違いがありますが、このことを利用して「C90、C99、C11のどれとしてコンパイルされたかどうかにより、違う挙動をする」というプログラムを作ることが可能です。同様に、C++はほぼC言語の上位互換ですが、C言語とC++で違った結果を生み出すプログラムも存在します。
これは2015年の International Obfuscated C Code Contest (難読Cコード・国際コンテスト)への Don Yangの投稿 において、
C89、C99、C11、C++98、C11のどれとしてコンパイルされるかによって異なる出力を生成するプログラムを作成するのに使われています。C90の場合は、以下のような星形を出力します。
********************************************* *** **
*********************** ***************** ****** **
********************* **************** ********** **
******************** **************** ************** **
****** ****** ***************** ****************************
****** ** **************************************************
****** *************************(O************************
******* * *********************************************
********* ****************************************
*********** ***************************************
************ ***************************************
********* *****************************************
******* ***************************************************
****** ****************)d));o((d=************************
****** ** **************************************************
****** ***** ********************* ***********************
********************( O****************** **********************
********************** ******************* *********************
************************ ******************* **************)p)-p);
o(d-(p=***************************************** *********************
C99ではこの星に目がついたものが出力され、C++11では丸が出力される、といったものになっています。(仕掛けはさらにあります。このプログラムは標準入力のテキストを読み込み、難読化されたC90のソースコードを生成するのですが、その生成されたソースは生成時に入力された文字列を出力するのです―コード内の *
は全てポインタのデリファレンスなのです!)
このプログラムのソースコードは、読むのが少し難しいものになっています。
#define r(R) R"()"
/*[*/#include /**/<stdio.h>
#include<math.h>/*!![crc=0f527cd2]*/
float I,bu,k,i,F,u,U,K,O;char o[5200];int
#define R(U) (sizeof('U')==1||sizeof(U"1"[0])==1)
h=0,t=-1,m=80,n=26,d,g,p=0,q=0,v=0,y=112,x=40; float
N(float/*x*/_){g=1<<30;d=-~d*1103515245&--g;return d*_
/g;}void/**/w(int/**/_){if(t<0){for(g=0;g<5200;o[g++ ]=
0);for(;g;o[g+79]=10)g-=80;for(t=37;g<62;o[80+g++]=32) ;
}if(m&&o[h*80+m-1]==10){for(g=0;g<79;o[t*80+g++]=0){}o[t
++*80+g]=10;t%=64;n+=2;I=N(70)+5;if(n>30&&(I-x)*(I-x)+n*
n>1600&&R()){O=0;F=(x=0x1!=sizeof(' '))?k=1+N(2),i=12-k+N(
8),N(4):(k=17+N(5),i=0,r()[0]?O=.1: 0);for(u=U=-.05;u<32;
U=k+i+i*.5*sin((u+=.05)+F))for( K=0 ;K< U;K+=.1)if((bu=K*
sin(u/5),g=I+cos( u/5) *K)>=0&&g < 79 )o[g+(int)(t+44+
bu*(.5-(bu>0?3*O: O) ) )%64* 80 ] =32;x*=02//* */2
-1;n=O+x?n=I+(x?0 :N (k)- k /2),g=(t+42 )%
64,m=-~g%64,x?g=m =-~ m%64:0 ,n>5?o[g*80 +
n-3]=o[m*80+n-3]= 0: 0 ,n <75?o[g*80+n
+2]=o[m*80+n+2]=0 :0:0; x=I;}h=-~h%64
;m=0;}putchar((g=o [h* 80+m++])?g:_);
if(g){w(_);}}void W (const char*_
){for(;*_;w(*_++));} int main(int a
,char**_){while(a--)d +=_[a ]-(char*)0;W( \
"#include<stdio.h>typed" "e" "f\40int\40O;v"
"oid o(O _){putchar(_);}O" "\40main(){O" ""
"*_[512],**p=_,**d,b,q;for(b=0;b" "++<512;p=_+q)_[q" \
"=(p-_+1)*9%512]=(O*)p;") ; for(;(g= getchar())-EOF;p=
q){q=p;for(v=512;p-q-g&&q-p- g; v--)q=-~q*9%512
;W("o(");if(p>q)w(y),w(45);w( 40);w(y^=20
);w(075);for(a=0;a<v;a++)w(42); for(W("(O**"
);a--;w(42)){}w(41);w(y^024);w( 41);if(p<=q)w(
45),w(y^20);W(");");}for(a=7;a-6 ;W(a<6?"{;}":""
))for(a =0;a <6 && !o[h*80+m +a];a++){}W("r"
"etu" /*J */ "rn+0;}\n" );return
/* "#*/0 ;}
しかし、私が見てわかる限り、どのC/C++の方言が使われているかを検知するために3つのトリックを使っているようです。
-
//
コメント
C90には//
コメントがありません。そのため、int i = 2 //**/2 ;
という文は、C90と他のC/C++のリビジョンを区別するのに使われています。C90ではこれを以下のようにコンパイルします。
int i = 2 //**/2 ;
一方、C++やC言語のより新しいリビジョンでは、以下のようにコンパイルされます。
int i = 2 //**/2 ;
-
文字定数の型
'a'
などの文字定数は、C言語ではint
型になりますが、C++ではchar
型になります。つまり、sizeof('a')
はC言語とC++で違う値として評価されます。 -
ワイド文字リテラル
C11とC++11にはワイド文字リテラルがあり、例えばU"hello!"
はchar32_t
型の文字からなる文字列です。これを以下のマクロと組み合わせます。#define R(U) sizeof(U"a"[0])
このマクロはプログラム中で
R("")
という形で用いられています。C11とC++では、これは以下のように展開されます。sizeof(U"a"[0])
これは4として評価されます。一方、古いリビジョンの規格では、
U
と"a"
は2つのトークンとして扱われるので、このマクロは以下のように展開されます。sizeof("""a"[0])
これは1として評価されます。
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa