C プリプロセッサを理解しようとする
-
17-09-2020 - |
質問
これらのコード ブロックが異なる結果をもたらすのはなぜですか?
いくつかの一般的なコード:
#define PART1PART2 works
#define STRINGAFY0(s) #s
#define STRINGAFY1(s) STRINGAFY0(s)
ケース1:
#define GLUE(a,b,c) a##b##c
STRINGAFY1(GLUE(PART1,PART2,*))
//yields
"PART1PART2*"
ケース 2:
#define GLUE(a,b) a##b##*
STRINGAFY1(GLUE(PART1,PART2))
//yields
"works*"
ケース 3:
#define GLUE(a,b) a##b
STRINGAFY1(GLUE(PART1,PART2*))
//yields
"PART1PART2*"
VS.net 2005 sp1 の MSVC++ を使用しています
編集:現在のところ、マクロを展開するときにプリプロセッサは次のように動作すると考えています。ステップ1:- ボディを取る - ##オペレーターの周りの空白を削除 - パラメーターの名前と一致する識別子が見つかった場合、文字列を解析します。-## 演算子の隣にある場合は、識別子をパラメータのリテラル値に置き換えます (つまり、渡された文字列) - ##オペレーターの隣にない場合、最初にパラメーターの値でこの説明プロセス全体を実行してから、識別子をその結果に置き換えます。(Stringafy Single '#' Case atmを無視) - すべての##オペレーターをremove
ステップ2:- 結果の文字列を取得し、マクロがないか解析します。
これから、3 つのケースすべてでまったく同じ結果の文字列が生成されるはずだと思います。
パート1パート2*
したがって、ステップ 2 の後の結果は次のようになります。
作品*
しかし、少なくとも同じ結果になるはずです。
解決
ケース 1 と 2 には、定義された動作がありません。 *
1 つのプリプロセッサ トークンにまとめられます。プリプロセッサの関連付けルールに従って、これはトークンを接着しようとします。 PART1PART2
(あるいは単に PART2
) そして *
. 。あなたの場合、これはおそらくサイレントに失敗します。これは、物事が未定義である場合に考えられる結果の 1 つです。トークン PART1PART2
に続く *
その後、マクロ展開の対象として再度考慮されなくなります。文字列化により、ご覧のような結果が得られます。
私の gcc はあなたの例では異なる動作をします:
/usr/bin/gcc -O0 -g -std=c89 -pedantic -E test-prepro.c
test-prepro.c:16:1: error: pasting "PART1PART2" and "*" does not give a valid preprocessing token
"works*"
つまり、ケース 1 には 2 つの問題があると要約できます。
- 有効なプリプロセッサトークンにならない2つのトークンを貼り付けます。
- の評価順序
##
オペレーター
ケース 3 では、コンパイラが間違った結果を返します。それはすべきです
- 引数を評価して、
STRINGAFY1
- そのためには拡張する必要があります
GLUE
GLUE
結果としてPART1PART2*
- 再度拡張する必要があります
- 結果は
works*
- それは次に渡されます
STRINGAFY1
他のヒント
あなたがそれをやることを正確にやっていることです。1番目と2番目は、シンボル名を渡して新しいシンボルにまとめて貼り付けます。3番目は2つのシンボルを受け取り、それらを貼り付け、あなたは自分で*を自分で*に置くことです(これは最終的に何か他のものに評価されます。)
結果のある質問は何ですか?あなたは何を得ることを期待しましたか?それはそれを期待するように働いているようです。
それでは、なぜあなたはとにかくこのようなシンボルの暗証の暗い芸術と遊んでいるのですか?:)