コンパイラは数を当てるマジックを解けるか
「任意の数を思い浮かべてもらい、特定の計算をしてもらい、その結果を当てる」という子供向けのマジックがあります。これを C 言語のコンパイラで解いてみましょう。
動作確認環境
- Windows 10 Home 21H1, 64bit
- Visual Studio Community 2019
マジックの内容
このマジックは無限のバリエーションが作れますが、今回は次の計算をすることにします。
- 好きな数を思い浮かべてください。
- その数に 5 を足してください。
- 2 倍してください。
- 4 を引いてください。
- 半分にしてください。
- 最初に思い浮かべた数を覚えていますか。その数を引いてください。
結果は、最初に思い浮かべた数が何であっても、「3」になります。
理由は次の通りです。
((n + 5) × 2 - 4) / 2 - n
= (2n + 10 - 4) / 2 - n
= (2n + 6) / 2 - n
= n + 3 - n
= 3
「2」を思い浮かべた場合
最初に思い浮かべた数を「2」として、このマジックを C 言語で書き下します。
// MyMagic.c
#include <stdio.h>
int main()
{
int any;
int answer;
any = 2; // 1. 好きな数を思い浮かべてください
answer = any + 5; // 2. その数に 5 を足してください
answer *= 2; // 3. 2 倍してください
answer -= 4; // 4. 4 を引いてください
answer /= 2; // 5. 半分にしてください
answer -= any; // 6. 最初に思い浮かべた数を引いてください
printf("%d", answer);
return 0;
}
「cl /O2 /Fa MyMagic.c」でコンパイルします。「/O2」は速度優先で最適化、「/Fa」はアセンブリファイルを出力、の意味です。
どのようにコンパイルされたか、アセンブリファイルを見てみます(関係する箇所のみ抜粋・編集)。
_main PROC
push 3
push <"%d" へのポインタ>
call _printf
何の計算もしない、単なる「3」 を表示するプログラムに最適化されました。
任意の数を受け取る場合
思い浮かべた数を実行時に入力できるようにしてみましょう。scanf 関数を使います。
// MyMagic.c
#include <stdio.h>
int main()
{
int any;
int answer;
scanf("%d", &any); // 1. 好きな数を思い浮かべてください
answer = any + 5; // 2. その数に 5 を足してください
answer *= 2; // 3. 2 倍してください
answer -= 4; // 4. 4 を引いてください
answer /= 2; // 5. 半分にしてください
answer -= any; // 6. 最初に思い浮かべた数を引いてください
printf("%d", answer);
return 0;
}
「cl /O2 /Fa MyMagic.c」でコンパイルし、実行し、思い浮かべた数として「12345」を入力します。
C:\tmp>MyMagic.exe
12345
3
予想通り、「3」と表示されました。
どのようにコンパイルされたのでしょうか。アセンブリファイルを見てみます (関係する箇所のみ抜粋・編集) 。
_main PROC
push <変数 any のアドレス>
push <"%d" へのポインタ>
call _scanf
push 3
push <"%d" へのポインタ>
call _printf
ユーザーからの入力は受け付けるものの、その値は無視し、固定値「3」を表示するプログラムにコンパイルされました。
以上、数を当てるマジックをコンパイラの最適化で解く話でした。