はじめに
Rui UeyamaさんのCコンパイラ作成集中講座 (2020) 第14回を聞いて知ったのですが、GCCは「同じマクロ」が定義された時に、マクロの再定義警告をしないんですね。例えばこんなコードです。
#include <cstdio>
#define A 1
#define B 1
#define A 1 // No warning
#define B 2 // test.cpp:7:0: warning: "B" redefined
int main() {
printf("%d %d\n", A, B);
}
AとBをそれぞれ再定義していますが、二回目の定義でAは同じなのにBは違う定義になっています。この時、GCCはBの再定義にだけ文句を言って、Aの再定義はスルーします。
$ g++ test.cpp
test.cpp:7:0: warning: "B" redefined
#define B 2 // test.cpp:7:0: warning: "B" redefined
test.cpp:4:0: note: this is the location of the previous definition
#define B 1
これ、マクロの等価判定は、単純に文字列の比較だけでやっており、既に定義済みのマクロを展開して比較してくれたりはしないようです。
#include <cstdio>
#define A 1
#define B 1
#define A B // test.cpp:5:0: warning: "A" redefined
int main() {
printf("%d\n", A);
}
これは、事実上はAは同じものに再定義されていますが、GCCは再定義とみなし、警告してきます。
で、ふと思ったんですよ。Cのマクロって単なる文字列展開で、その展開は多段にできますよね。これって、何段まで展開してくれるんでしょう?これってトリビアになりませんか?
このトリビアの種、つまりこういうことになります。
「コンパイラに多段のマクロ展開を食わせた時、xx段で力尽きる」
実際に調べてみた。
マクロの多段展開
とりあえずマクロの多段展開をするC++コードを吐くRubyスクリプトでも書いてみましょうかね。
puts "#include<cstdio>"
N = 10
N.times do |i|
puts "#define A#{i} A#{i + 1}"
end
puts <<EOS
#define A#{N} 1
int main(){
printf("%d\\n",A0);
}
EOS
実行はこんな感じ。
ruby test.rb > test.cpp
出てくるソースはこんな感じになります。
#include<cstdio>
#define A0 A1
#define A1 A2
#define A2 A3
#define A3 A4
#define A4 A5
#define A5 A6
#define A6 A7
#define A7 A8
#define A8 A9
#define A9 A10
#define A10 1
int main(){
printf("%d\n",A0);
}
プリプロセッサが最終的にA0
を1
に展開するには、マクロを10段展開してやる必要があります。
とりあえずN=10000
にしてみましょうか。
$ ruby test.rb > test.cpp && time g++ test.cpp && ./a.out
g++ test.cpp 0.28s user 0.34s system 92% cpu 0.672 total
楽勝ですね。ではN=100000
では?
$ ruby test.rb > test.cpp && time g++ test.cpp && ./a.out
g++ test.cpp 0.20s user 0.67s system 85% cpu 1.021 total
1
まだ余裕っぽいです。ではさらに10倍では?
$ ruby test.rb > test.cpp && time g++ test.cpp && ./a.out [~]
g++: internal compiler error: Segmentation fault (program cc1plus)
Please submit a full bug report,
with preprocessed source if appropriate.
See <file:///usr/share/doc/gcc-7/README.Bugs> for instructions.
g++ test.cpp 5.64s user 30.31s system 76% cpu 46.807 total
GCCがICEで死にましたね。
二分探索
N=10000
では死なず、N=100000
では死にました。どこかに「死ぬギリギリ」のマクロ展開数があるはずですね。手抜き二分探索で調べてみましょう。
# frozen_string_literal: true
def check(n)
open("test.cpp", "w") do |f|
f.puts "#include<cstdio>"
n.times do |i|
f.puts "#define A#{i} A#{i + 1}"
end
f.puts <<EOS
#define A#{n} 1
int main() {
printf("%d\\n", A0);
}
EOS
end
system("g++ test.cpp 2> /dev/null")
end
def binary_search
s = 100000
e = 1000000
while s != e && s + 1 != e
m = (s + e) / 2
if check(m)
puts "#{m} OK"
s = m
else
puts "#{m} NG"
e = m
end
end
end
binary_search
実行してみましょう.
$ ruby search.rb
550000 OK
775000 OK
887500 NG
831250 NG
803125 OK
817187 OK
824218 NG
820702 OK
822460 OK
823339 NG
822899 NG
822679 OK
822789 OK
822844 NG
822816 OK
822830 NG
822823 NG
822819 OK
822821 OK
822822 OK
822822段はOKで、822823段で死にました。
まとめ
こうしてこの世界にまた一つ
新たなトリビアが生まれた。
「822823回マクロを展開するとGCCが死ぬ」
というわけで、皆さんマクロの多段展開をしたくなっても、80万回くらいまでにしておくのが良いと思います。
2020年8月1日追記
コメ欄で「一段足りなくないですか?」と聞かれて、見直してみたら、最後の
#define A822823 1
をマクロ展開としてカウントし忘れていました。なので、「822824回マクロを展開するとGCCが死ぬ」と表題を訂正させていただきます。