Slide 1

Slide 1 text

1 19 未定義動作でFizzBuzz 2023年12月6日 C++ MIX #8 kaityo256

Slide 2

Slide 2 text

2 19 自己紹介 ロボ太/kaityo256 ※画像はイメージです コンパイラいじめ芸人

Slide 3

Slide 3 text

3 19 コンパイラいじめってなに? 括弧で34087重に囲んだ関数を食わせる とg++が死ぬ ※画像はイメージです

Slide 4

Slide 4 text

4 19 コンパイラいじめってなに? printfに4285個アスタリスクをつける とclang++が死ぬ ※画像はイメージです

Slide 5

Slide 5 text

5 19 コンパイラいじめってなに? GCCに27958段ネストした関数を 食わせると死ぬ ※画像はイメージです

Slide 6

Slide 6 text

6 19 コンパイラをいじめてるとどうなるの? A. 翻訳AIもいじめはじめる ※画像は本物です

Slide 7

Slide 7 text

7 19 Fizz Buzz Fizz Buzzが組める能力、 実運用で必要か? Fizz Buzzも組めない プログラマがいる! 定期的に話題になる 話題になる度に変態超絶解法も話題に

Slide 8

Slide 8 text

8 19 Fizz Buzz 僕もなんか変態超絶解法やってみたい!

Slide 9

Slide 9 text

9 19 Fizz Buzz C/C++の未定義動作を使って Fizz Buzzっぽいことをやってみよう ※画像はイメージです

Slide 10

Slide 10 text

10 19 未定義動作でFizz Buzz #include int main(){ int a = 0, b = 0; a = --a + ++a + ++a; b = ++b + ++b + a; for (int i=1;i<16;i++){ if (i%b==0){ printf("%s¥n",a?"buzz":"fizz"); }else{ printf("%d¥n",i); } } } こんなコードを書いてみた

Slide 11

Slide 11 text

11 19 未定義動作でFizz Buzz $ clang++ fizz.cpp; ./a.out 1 2 fizz 4 5 fizz 7 8 fizz 10 11 fizz 13 14 fizz $ g++ fizz.cpp; ./a.out 1 2 3 4 buzz 6 7 8 9 buzz 11 12 13 14 buzz clang++でコンパイルすると 3の倍数の時だけfizzがでる g++でコンパイルすると 5の倍数の時だけbuzzがでる ※ Fizz Buzzの処理?シェルでなんとかすればいいんじゃん?

Slide 12

Slide 12 text

12 19 なにが起きたか? int b = 0; b = ++b + ++b + a; int a = 0; a = --a + ++a + ++a; clang++ではa=0に、g++ではa=1になる clang++ではb=3に、g++ではb=5になる GCCかどうかのフラグとして使える 整数剰余に使える

Slide 13

Slide 13 text

13 19 なにが起きたか? int a = 1; int b = ++a + ++a; 複数の前置インクリメントを式に入れたら動作は未定義 int a = 1; int tmp1 = a + 1; a = tmp1; int tmp2 = a + 1; a = tmp2; int b = tmp1 + tmp2; int a = 1; a = a + 1; a = a + 1; int tmp = a + a int b = tmp; clang++の解釈 g++の解釈

Slide 14

Slide 14 text

14 19 clang++の気持ち int func(int a){ return ++a; } $ clang -emit-llvm -S test.c ; Function Attrs: noinline nounwind optnone uwtable define dso_local i32 @func(i32 %0) #0 { %2 = alloca i32, align 4 store i32 %0, i32* %2, align 4 %3 = load i32, i32* %2, align 4 %4 = add nsw i32 %3, 1 store i32 %4, i32* %2, align 4 ret i32 %4 } clangの気持ちの調べ方 LLVM中間コードを吐く 中間コードを 読み解く

Slide 15

Slide 15 text

15 19 clang++の気持ち int a = 1; int b = ++a + ++a; int a = 1; int tmp1 = a + 1; a = tmp1; int b = tmp1 + ++a; int a = 1; int tmp1 = a + 1; a = tmp1; int tmp2 = a + 1; a = tmp2; int b = tmp1 + tmp2; ++aを再帰的に tmp = a + 1; に展開

Slide 16

Slide 16 text

16 19 g++の気持ち int func(int a){ return ++a + ++a; } GCCの気持ちの調べ方 $ gcc -c -fdump-tree-all test.c int func (int a) { int D.2719; a = a + 1; a = a + 1; D.2719 = a * 2; return D.2719; } GIMPLE中間表現を吐く 中間表現を 読み解く

Slide 17

Slide 17 text

17 19 g++の気持ち int a = 1; int b = ++a + ++a; int a = 1; a = a + 1; a = a + 1; tmp = a + a int b = tmp; 二項演算子 (++a + ++a) ごとに再帰的に展開 ++a + ++a + ++a (++a + ++a) + ++a tmp = (++a + ++a) tmp + ++a 複数あったら 二個ずつ処理

Slide 18

Slide 18 text

18 19 まとめ • 同じソースでclang++とg++で異なる動作をする コードを書き、Fizz Buzzもどきをやってみた • 他の言語処理系でも起きる • clang派(PHP, D, JavaScript) • GCC派(Perl) • 多くの後発言語はC/C++を反面教師として設計さ れている • インクリメント演算子廃止(Ruby, Python) • 後置のみにして文に(Go) • 未定義動作について学ぶと、言語処理系について の理解が進む

Slide 19

Slide 19 text

19 19 オ レ は よ う や く の ぼ り は じ め た ば か り だ か ら な こ の は て し な く 遠 い コ ン パ イ ラ い じ め 道 を よ …