2014年08月21日

C/C++プリプロセッサの挙動の違い(defined編)

前回の記事でビルドした Facebook 社の Warp の挙動を調べていた所、#if や #elif 指令中でのみ使用可能な defined 単項演算子の挙動が GCC のプリプロセッサと異なり、GCC 用のヘッダを前処理できない事例を発見したので、その時のメモです。



以下のような、現象が再現する最小コード片を用意しました。
$ cat defined_test.c
#define FOO defined (BAR)
#define BAR

#if FOO
#endif
これは warp.exe ではエラーになります。FOO の最初のマクロ展開の時に defined 演算子の引数の BAR も一緒に #if defined() のように展開(削除)され、エラーになっているように思われます。
$ ./warp.exe --stdout defined_test.c
# 1 "defined_test.c"
# 1 "c:\work\warp//"
# 1 "<command-line>"
# 1 "defined_test.c"

defined_test.c(4) : identifier expected after 'defined'
一方、GCC 4.9.0 では、-Wall や -pedantic を付けても無警告で問題無く通ります。実装までは調べてませんが、おそらく defined 演算子の引数を特別扱いしているのではないかと思われます。
$ gcc -v
...
gcc version 4.9.0 (crosstool-NG hg+unknown-20140610.075849 - 20140615-2.065-a8ad6a6678)
$ gcc -E -Wall -std=c99 -pedantic defined_test.c
# 1 "defined_test.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "defined_test.c"
VC++ でも通るようです。
>cl /E defined_test.c
Microsoft(R) 32-bit C/C++ Optimizing Compiler Version 15.00.30729.01 for 80x86
Copyright (C) Microsoft Corporation.  All rights reserved.

defined_test.c
#line 1 "defined_test.c"




#line 6 "defined_test.c"
これは Warp のバグなのでしょうか?
結論から言うと、これは未定義なので、どのプリプロセッサの挙動も正しいようです。
GCC の The C Preprocessor のマニュアル(C/C++/Objective-C 共通)には、以下のように記述されていました。(-pedantic オプションで警告が出なかった点は気になりますが…。)
https://gcc.gnu.org/onlinedocs/cpp/Defined.html
If the defined operator appears as a result of a macro expansion, the C standard says the behavior is undefined. GNU cpp treats it as a genuine defined operator and evaluates it normally. It will warn wherever your code uses this feature if you use the command-line option -pedantic, since other compilers may handle it differently.
(意訳:defined 演算子がマクロ展開の結果に出現した場合、規格 C はその挙動を未定義としている。GNU cpp はこれを本物の defined 演算子として扱い、通常通り評価する。他のコンパイラは異なる取扱いをするかもしれないので、(GNU cpp は)-pedantic コマンドラインオプションを使用した場合、このような使い方をしているソースコードの当該箇所を警告する。)
C99 の final draft の 6.10.1 (p.160) にも、これを裏付ける記述がありました。
http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf
If the token defined is generated as a result of this replacement process or use of the defined unary operator does not match one of the two specified forms prior to macro replacement, the behavior is undefined.
(意訳:defined トークンが置換処理の結果として生成された場合、あるいはマクロ置換の前の defined identifier か defined ( identifier ) のどちらにもマッチしない defined 単項演算子の使用は、その挙動は未定義となる。)
Warp は Facebook 社で実運用されているようですが、ここらへんの差異は問題にならないのでしょうか。気になります。
ちなみに、私が簡単に試した範囲では、MinGW GCC の x86_64-gdcproject-mingw32/x86_64-gdcproject-mingw32/sysroot/mingw/include/psdk_inc/intrin-impl.h の、以下のようなマクロなどが、windows.h を前処理してみようと実験している時に問題となりました。
/* The logic for this macro is:
   if the function is not yet defined AND
   (
       (if we are not just defining special OR 
           (we are defining special AND this is one of the ones we are defining)
       )
   )
*/
#define __INTRINSIC_PROLOG(name) (!defined(__INTRINSIC_DEFINED_ ## name)) && ((!defined (__INTRINSIC_ONLYSPECIAL)) || (defined (__INTRINSIC_ONLYSPECIAL) && defined(__INTRINSIC_SPECIAL_ ## name)))


トラックバックURL

コメントする

名前
URL
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード