GCCとClangの差を追い込む
前回 https://zenn.dev/okuoku/scraps/d9c5ad4ddcc894 はコンパイル 後 のバイナリに含まれるシンボルの差に注目していたが、そもそも、セマンティクス的な差が無いかを追い込んでやる必要がありそう。
Clangはgccのdrop-in-replacementである事を割と目標にしていて、例えば __gcc__
等が事前定義に含まれる。(もっともClangはgccの特殊な機能(ローカル関数定義とか)には対応していないという差はある。まぁ"そんなもん使うな"で良いだろう。)
ちょっと困る可能性があるのは、 コンパイル自体は通るがコードの解釈は異なる というケースで、実際に動かしてみてハマった場合解決できる自信がない。
predefine
C/C++では特に何も指定していなくても暗黙にdefineされるマクロが存在する。例えば、 __clang__
はClangであれば 1
にpredefineされるので、ソースコード上でclangにしか存在しない機能を使いたい場合に #ifdef
できる。
Predefineはダンプする方法が存在するため、ダンプしたうえで比較できる。ダンプするには -dM
オプション を付与して空のソースをプリプロセスする。
$ touch empty.cpp
g++ -dM -E empty.cpp | sort > gcc.i.cpp
Cygwin付属のgcc、clang、野良ビルドしたclangの3つで出力させてみた 。
- gcc
#define __cplusplus 201703L
#define __PIC__ 1
#define __pic__ 1
#define __GXX_ABI_VERSION 1016
#define __INT_FAST16_TYPE__ long int
#define __INT_FAST32_TYPE__ long int
#define __WINT_TYPE__ unsigned int
- clang
#define __cplusplus 201402L
#define __PIC__ 2
#define __pic__ 2
#define __GXX_ABI_VERSION 1002
#define _GNU_SOURCE 1
#define __INT_FAST16_TYPE__ short
#define __INT_FAST32_TYPE__ int
#define __WINT_TYPE__ int
比較してみると、gccでは __cplusplus
がC++17になっているのに対し、clangはC++14になっている。つまり、何もオプションを指定しなかったときのデフォルトが異なっている。
_GNU_SOURCE
はclang側にしかない。つまりclangではデフォルト( -std=gnu++14
)になっているが、gccでは -std=c++17
になっているということだろう。
PICとか型のサイズやsignednessが合ってないのはちょっと移植が不完全だな。。
とりあえずC++17に統一すると割と揃うようなので、これを基準にする。
Pragmaの差
次に、
#include <iostream>
#include <future>
#ifdef __clang__
#pragma GCC warning "I'm clang"
#else
#pragma GCC warning "I'm gcc (maybe)"
#endif
int main()
{
std::future<int> f1 = std::async([](){ return 42; });
f1.wait();
std::cout << "Magic number is: " << f1.get() << std::endl;
}
のプリプロセス結果の差分を確認してみた(赤がgcc、緑がclang)。
大体は構文解釈やホワイトスペースの差だが、int128のようなコンパイラ機能に起因する差もある。
... ちょっと気になるのは、 #pragma GCC visibility push(default)
はプリプロセス結果に両方で残り (白色)、 #pragma GCC warning
はclangにしかない (緑色) 点。そもそも #pragma
はプリプロセサ指令なので後段に残す必要性って無いと思うんだけど。。
もちろん、通常のシチュエーションでは g++ -E
したソースを g++
に掛けたら一括でコンパイルしたものと同じ挙動を示すべきなのでこの仕様自体は理解できるが、ではgccは何故 #pragma GCC warning
を落としてしまうんだろうか。。?
gccは一応 cpp
を別の実行ファイルとして提供しているので、 #pragma GCC warning
を cpp
の出力に含めると cpp
と cc1
の両方でwarningを出してしまうという点を心配しているんではないかという気はする。
$ cat warn.c
#pragma GCC warning "Oh"
$ cpp warn.c
# 0 "warn.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "warn.c"
warn.c:1:21: warning: Oh
1 | #pragma GCC warning "Oh"
| ^~~~
$ /usr/lib/gcc/x86_64-pc-cygwin/11/cc1 -o dummy.o warn.c
warn.c:1:21: warning: Oh
1 | #pragma GCC warning "Oh"
| ^~~~
Analyzing compilation unit
Performing interprocedural optimizations
<*free_lang_data> {heap 1280k} <visibility> {heap 1280k} <build_ssa_passes> {heap 1280k} <opt_local_passes> {heap 1280k} <remove_symbols> {heap 1280k} <targetclone> {heap 1280k} <free-fnsummary> {heap 1280k} <emutls> {heap 1280k}Streaming LTO
<whole-program> {heap 1280k} <fnsummary> {heap 1280k} <inline> {heap 1280k} <modref> {heap 1280k} <free-fnsummary> {heap 1280k} <single-use> {heap 1280k}Assembling functions:
<simdclone> {heap 1280k}
Time variable usr sys wall GGC
phase setup : 0.01 (100%) 0.00 ( 0%) 0.02 ( 59%) 1302k ( 90%)
phase opt and generate : 0.00 ( 0%) 0.02 (100%) 0.01 ( 28%) 2664 ( 0%)
callgraph ipa passes : 0.00 ( 0%) 0.02 (100%) 0.01 ( 24%) 2512 ( 0%)
ipa inlining heuristics : 0.00 ( 0%) 0.02 (100%) 0.00 ( 3%) 0 ( 0%)
TOTAL : 0.01 0.02 0.03 1441k
ただ全ての #pragma
がこの挙動に従っているのかは何とも言えない。