ARCHIVE  ENTRY  COMMENT  TRACKBACK  CATEGORY  RECOMMEND  LINK  PROFILE  OTHERS
<< October 2017 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 >>
2016.02.25 Thursday

スポンサーサイト

一定期間更新がないため広告を表示しています

2012.07.28 Saturday

セグメンテーションフォルトを出す最小コード。

セグメンテーションフォルトを出す最小コードで友達と盛り上がったのでメモ。
試験前は妙な話題が盛り上がる。

最初に考えたコード。ヌルをint*にキャストして参照する。

  1. main(){*(int*)0=1;}

コンパイル
>gcc seg.c -Wall
seg.c:1:1: warning: return type defaults to 'int'
seg.c: In function 'main':
seg.c:1:1: warning: control reaches end of non-void function
意外にワーニング少ない。

バイナリを関数ポインタにキャストすると呼び出せる、というのを思い出して、こんなのを書いてみた。
  1. char main[]="a";
コンパイル。
>gcc seg.c -Wall
seg.c:1:6: warning: 'main' is usually a function
ワーニング減った。何で(笑)
こんなワーニングあるのね。…ってかそもそも通るのか。

どうせセグフォル出すだけならメモリを確保する必要すらない、ってことで1バイト短縮。
  1. char *main="a";
コンパイル。

>gcc seg.c -Wall

seg.c:1:6: warning: 'main' is usually a function
通る。まあ、ね。

ポインタなら何でもいいやってことで型名を省略。
  1. *main="a";
コンパイル。
>gcc seg.c -Wall
seg.c:1:1: warning: data definition has no type or storage class
seg.c:1:2: warning: type defaults to 'int' in declaration of 'main'
seg.c:1:2: warning: 'main' is usually a function
seg.c:1:7: warning: initialization from incompatible pointer type
gccでは型名を省略するとintになるので、mainはいまint*ですね。
そこに文字列リテラル"a"のポインタが代入されることになります。

文字列リテラルの内容は適当"a"と決めたわけですが、内容は何でもいい。
ということで、試しにヌルを参照してみる。
  1. *main=0;
コンパイル。
gcc seg.c -Wall
seg.c:1:1: warning: data definition has no type or storage class
seg.c:1:2: warning: type defaults to 'int' in declaration of 'main'
seg.c:1:2: warning: 'main' is usually a function
通った(笑)

ここまでくるともうヌルである必要もないので、初期化しなくてもコンパイル通るのでは。
ということで
  1. *main;
コンパイル。
gcc seg.c -Wall
seg.c:1:1: warning: data definition has no type or storage class
seg.c:1:2: warning: type defaults to 'int' in declaration of 'main'
seg.c:1:2: warning: 'main' is usually a function
通る。結果はもちろんSegmentation Fault。

試験前に何やってるんだろう…w

【追記】

mainの前の「*」もいらないんじゃないの、と@toku51nさんにご指摘いただきました。
確かに、型がなんでもいいならもはやポインタ型である必要もないですね…w
結局、mainっていう名前さえ宣言されてればあとは何でもいいんですね。ちょっと納得。
2012.07.24 Tuesday

「邪悪なCコード」を読む。続き。

 まず、前回の補足。

いきなり
1[argv]

argv[1]
に特に断りなく書き換えてましたが、これは
1[argv] = *(1+argv) = *(argv+1) = argv[1]
ということですね。

あと、2番目のif文の条件で、3段目の出力ついてコメントをしていませんでした。
3段目ではi>=41になります。
i==41ではi/21=1ですが、i==42ではi/21=2になるので
偶偶奇偶奇偶
となってちゃんとスペースが2つ出力されます。お見事。

…さて。残る問題はこいつでした。
i^"pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2]
この文が評価されるときのiの値を確認すると
1, 3, 5, …, 19,
22, 24, 26, …, 38
43, 45, 47, …, 55
なので、i/2は
0, 1, 2, …, 9,
11, 12, 13, …, 19
21, 22, 23, …, 27
となって、10と20がスキップされるんですね。気づかなかった。

ということで
"qwertyuiop_asdfghjkl_zxcvbnm"[i/2]
に書き換えてみると…
>gcc konno.c
>./a.out n
. . . . . . . . . .
 . . . . . . . . .
  . . . . . o .

無事動きました。

…なんていうか、ゴルファー怖。これに尽きます。
2012.07.24 Tuesday

「邪悪なCコード」を読む。

 初投稿からこんなネタ。しかも未完成。

ふと思い立って、IOCCC(邪悪なCコードコンテスト)のコードを読んでみました。
頭の体操、もしくは試験勉強からの逃避。

お題は2011年のBest one liner賞
  1. main(_,l)char**l;{6*putchar(--_%20?_+_/21&56>_?strchr(1[l],_^"pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[_/2])?111:46:32:10)^_&&main(2+_,l);}
…なにがなんだかですね。

とりあえず実行してみましょう。
>gcc konno.c
>./a.out n
. . . . . . . . . .
 . . . . . . . . .
  . . . . . o .
…?
>./a.out unko
. . . . . . o . o .
 . . . . . . . o .
  . . . . . o .
…??
>./a.out qwerty
o o o o o o . . . .
 . . . . . . . . .
  . . . . . . .
!!含まれる文字のキーの位置が表示されるわけですね!なるほど。

…コレを解読していきます。

とりあえず改行とインデント。ついでに変数名を変えておきます。
  1. main(int i, char* argv[]){
  2.      6*putchar(
  3.           --i%20?
  4.                i+i/21&56>i?
  5.                     strchr(1[argv],i^
  6.                     "pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2])?
  7.                     111 : 46
  8.                :32
  9.           :10
  10.      )^i
  11.      &&
  12.      main(2+i,argv);
  13. }
…まだなにがなにやらですね。

main関数の第一引数はコマンドライン引数の数になりますから、2ですね。
そこで
  1. int i=2;
  2. main(int argc, char* argv[]){
  3.      6*putchar(
  4.           --i%20?
  5.                i+i/21&56>i?
  6.                     strchr(1[argv],i^
  7.                     "pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2])?
  8.                     111 : 46
  9.                :32
  10.           :10
  11.      )^i

  12.      &&

  13.      (i+=2, main(0,argv));
  14. }
こんな書き換えをします。main再帰の引数で渡していたiをグローバルにしました。

プログラムを眺めてみると、iがしょっぱなでデクリメントされてるのが気になります。
初めから減らしておきましょう。
  1. int i=1;
  2. main(int argc, char* argv[]){
  3.      6*putchar(
  4.           i%20?
  5.                i+i/21&56>i?
  6.                     strchr(1[argv],i^
  7.                     "pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2])?
  8.                     111 : 46
  9.                :32
  10.           :10
  11.      )^i

  12.      &&

  13.      (i++, main(0,argv));
  14. }
これに伴って、iの増分が1になることに要注意です。
もとのプログラムでは「1戻って2進む」だったのが、「戻らず1進む」に変わります。

putcharに渡している定数も気持ち悪いので消しておきましょう。
明らかにASCIIコードなので、ASCIIコード表をググれば
111, 46, 32, 10

'o', '.', ' ', '¥n'
とわかります。
  1. int i=1;
  2. main(int argc, char* argv[]){
  3.      6*putchar(
  4.           i%20?
  5.                i+i/21&56>i?
  6.                     strchr(1[argv],i^
  7.                     "pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2])?
  8.                     'o' : '.'
  9.                :' '
  10.           :'¥n'
  11.      )^i

  12.      &&

  13.      (i++, main(0,argv));
  14. }
(…これ読めるのかな…)

そろそろ頭を使って読んでいきます。

まずいきなり再帰の条件がヤバいですね。
6*putchar(ごにょごにょ)^i && (i++, main(0,argv));
^(XOR)を見ると緊張します。

&&演算子の左辺が偽のとき右辺は評価されないので、結局
6*putchar(ごにょごにょ)^i == 0
すなわち
6*putchar(ごにょごにょ) == i
のとき、再びmainが呼ばれます。よくわからない人はXORの真理値表をググる。

putcharの戻り値は出力した文字のASCIIらしいので、6*putchar(ごにょごにょ)は
6*'o', 6*'.', 6*' ', 6*'¥n'
すなわち
666, 276, 192, 60
のどれかです。

mainが192回以上も回るとは思えないので、怪しいのは60ですね。
アルファベットが26文字なので、間に空白類を挟むと60くらいにはなりそうです。

試しに
  1. int i=1;
  2. main(int argc, char* argv[]){
  3.      putchar(
  4.           i%20?
  5.                i+i/21&56>i?
  6.                     strchr(1[argv],i^
  7.                     "pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2])?
  8.                     'o' : '.'
  9.                :' '
  10.           :'¥n'
  11.      );

  12.      i<60 && (i++, main(0,argv));
  13. }
としてみると、確かに動きました。

ループの終了条件がわかったので、いよいよmain再帰を消せます。
このとき、条件に等号が付くことに注意します。
  1. main(int argc, char* argv[]){
  2.      int i;

  3.      for(i=0; i<=60; i++)
  4.           putchar(
  5.                i%20?
  6.                     i+i/21&56>i?
  7.                          strchr(1[argv],i^
  8.                          "pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2])?
  9.                          'o' : '.'
  10.                     :' '
  11.                :'¥n'
  12.           );
  13. }
だいぶスッキリしてきました。

2つ目の三項演算子の条件もかなりヤバいです。
i+i/21&56>i
C言語の文法に従って優先順位を整理すると
(i+i/21)&(56>i)
となります。中央の演算子が&&でなく&なのが危険な香りです。

とりあえず、
56>i
は戻り値が1または0です。

0の場合、&を取っても0なのでただちに式全体が0です。

1の場合、&の左辺の1の位が1かどうかが重要になってきます。
…あ、二進数で表した場合の1の位ですよ?

1の位は奇数なら1、偶数なら0ですから、結局、
((i+i/21)%2==1) && (56>i)
という判定をしていることになります。
  1. int i=1;
  2. main(int argc, char* argv[]){
  3.      for(i=0; i<=60; i++)
  4.           putchar(
  5.                i%20?
  6.                     ((i+i/21)%2==1) && (56>i)?
  7.                          strchr(1[argv],i^
  8.                          "pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2])?
  9.                          'o' : '.'
  10.                     :' '
  11.                :'¥n'
  12.           );
  13. }
そろそろ三項演算子も見づらいので消してしまいましょう。
ついでに、先程の条件を否定しておきます。みんな大好きド・モルガン。
  1. main(int argc, char* argv[]){
  2.      int i;

  3.      for(i=1; i<=60; i++){

  4.           if(i%20 == 0){ printf("¥n"); continue; }

  5.           if(i >= 56 || (i+i/21)%2 == 0){ printf(" "); continue; }

  6.           if(strchr(argv[1],i^
  7.           "pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2]))
  8.                 printf("o");
  9.            else
  10.                 printf(".");
  11.      }
  12. }
ここで少し意味を読んでみます。

1つ目のifについて、
if(i%20 == 0){ printf("¥n"); continue; }
とあるので、20文字ごとに改行が来ることがわかります。

2つ目のifについて、
if(i >= 56 || (i+i/21)%2 == 0){ printf(" "); continue; }
は、まず、iが56以上ならば空白を出力します。
具体的には、手元のキーボードを見てもらうとよいのですが、
mの右の,をスキップするということに相当します。

56未満ならば、
i+i/21
の偶奇を確認します。とりあえず1段目を考えてみると、改行が20文字ごとだったので
必ずi<=20です。したがってi/21=0となるので(iも21もintですね)、
奇偶奇偶…
の並びになります。一方、2段目ではi/21=1になるので、
偶奇偶奇偶…
これで先頭にスペースが挟まってキーボードみたいな形になると。…見事です。

3つ目のifです。
if(strchr(argv[1],i^
"pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2]))
     printf("o");
else
     printf(".");
strchrは第一引数の文字列から第二引数の文字を探す関数なので、
argv[1]
から
i^"pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2]
を探します。見つかれば'o'を、見つからなければ'.'を出力します。

fmfm…って、検索する文字がなかなかヤバいですね。
最後の壁って感じです。

検索される文字列を調べるためにこんなプログラムを書きました。
main(i){
     --i%20?(i+i/21)&(56>i)?
          printf("%c",i^"pt`u}rxf~c{wk~zyHHOJ]QULGQ[Z"[i/2]):
     0:0;

     i<60 && main(2+i);
}
せっせと実行。
>gcc test.c
>./a.out
qwertyuiopasdfghjklzxcvbnm
…なるほど!

と思って
  1. main(int argc, char* argv[]){
  2.      int i;

  3.      for(i=1; i<=60; i++){

  4.           if(i%20 == 0){ printf("¥n"); continue; }

  5.           if(i >= 56 || (i+i/21)%2 == 0){ printf(" "); continue; }

  6.           if(strchr(argv[1],
  7.           "qwertyuiopasdfghjklzxcvbnm"[i/2]))
  8.                 printf("o");
  9.            else
  10.                 printf(".");
  11.      }
  12. }
と書き換えてみたら…
>gcc konno.c
>./a.out n
. . . . . . . . . .
 . . . . . . . . .
  . . . o . o o
…まだなんかあるの(´;ω;`)

今日はここまで。
Powered by
30days Album
PR