web-dev-qa-db-ja.com

Cプリプロセッサで2回連結し、「arg ## _ ## MACRO」のようにマクロを展開する方法は?

いくつかの関数の名前が特定のマクロ変数の値に依存するプログラムを、次のようなマクロで作成しようとしています。

_#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);
_

残念ながら、マクロNAME()はそれを

_int some_function_VARIABLE(int a);
_

のではなく

_int some_function_3(int a);
_

したがって、これは明らかに間違った方法です。幸いなことに、VARIABLEの異なる値の数は少ないので、単純に_#if VARIABLE == n_を実行し、すべてのケースを個別にリストできますが、それを行う巧妙な方法があるかどうか疑問に思いました。

137
JJ.

標準Cプリプロセッサ

_$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$
_

2レベルの間接参照

別の答えへのコメントで、 Cade Rouxasked これには2レベルの間接参照が必要な理由。軽率な答えは、それが規格が機能するためにそれを必要とする方法だからです。文字列化演算子を使用した同等のトリックも必要になる場合があります。

C99標準のセクション6.10.3は「マクロ置換」を、6.10.3.1は「引数置換」をカバーしています。

関数のようなマクロの呼び出しの引数が識別された後、引数の置換が行われます。置換リスト内のパラメーターは、_#_または_##_前処理トークンが先行するか、_##_前処理トークン(以下を参照)が先行しない限り、すべてのマクロが含まれた後、対応する引数に置き換えられます。その中で拡張されています。置換される前に、各引数の前処理トークンは、前処理ファイルの残りを形成するかのように完全にマクロ置換されます。他の前処理トークンは使用できません。

呼び出しNAME(mine)では、引数は 'mine'です。それは完全に「私のもの」に拡張されています。次に、置換文字列に置換されます。

_EVALUATOR(mine, VARIABLE)
_

これでマクロEVALUATORが発見され、引数は「mine」と「Variable」として分離されました。後者は完全に「3」に展開され、置換文字列に置換されます。

_PASTER(mine, 3)
_

この操作は、他のルール(6.10.3.3「##演算子」)でカバーされています:

関数のようなマクロの置換リストで、パラメーターの直前または直後に_##_前処理トークンが続く場合、パラメーターは対応する引数の前処理トークンシーケンスに置き換えられます。 [...]

オブジェクトのようなマクロ呼び出しと関数のようなマクロ呼び出しの両方で、置換リストを再検査してさらに置換するマクロ名を探す前に、(引数からではなく)置換リストの_##_前処理トークンの各インスタンスが削除され、前の前処理トークンは、次の前処理トークンと連結されます。

したがって、置換リストには、xの後に_##_が続き、_##_の後にyが続きます。だから私たちが持っている:

_mine ## _ ## 3
_

_##_トークンを削除し、両側のトークンを連結すると、「mine」と「_」および「3」が結合され、次の結果が得られます。

_mine_3
_

これは望ましい結果です。


元の質問を見ると、コードは(「some_function」の代わりに「mine」を使用するように適合されていました):

_#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)
_

NAMEへの議論は明らかに「私のもの」であり、完全に拡張されています。
6.10.3.3のルールに従うと、次のことがわかります。

_mine ## _ ## VARIABLE
_

_##_演算子が削除されると、次のようにマッピングされます:

_mine_VARIABLE
_

質問で報告されたとおり。


従来のCプリプロセッサ

ロバートリューガー質問

トークン貼り付け演算子_##_を持たない従来のCプリプロセッサでこれを行う方法はありますか?

たぶん、そうでないかもしれません-それはプリプロセッサに依存します。標準プリプロセッサの利点の1つは、この機能が確実に機能することです。一方、標準プリプロセッサにはさまざまな実装がありました。 1つの要件は、プリプロセッサがコメントを置換するときに、ANSIプリプロセッサが行う必要があるため、スペースを生成しないことです。 GCC(6.3.0)Cプリプロセッサはこの要件を満たしています。 XCode 8.2.1のClangプリプロセッサーはサポートしていません。

それが機能するとき、これは仕事をします(_x-paste.c_):

_#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);
_

_fun,_とVARIABLEの間にはスペースがないことに注意してください。これは、存在する場合は出力にコピーされ、名前として_mine_ 3_になります。もちろん、構文的には有効ではありません。 (今、髪を取り戻せますか?)

GCC 6.3.0(_cpp -traditional x-paste.c_を実行)では、次のようになります:

_# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);
_

XCode 8.2.1のClangを使用すると、次の結果が得られます。

_# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);
_

これらのスペースはすべてを台無しにします。両方のプリプロセッサが正しいことに注意してください。さまざまな先行標準プリプロセッサが両方の動作を示したため、コードを移植しようとすると、トークンの貼り付けが非常に面倒で信頼性の低いプロセスになりました。 _##_表記の標準は、それを根本的に簡素化します。

これを行う他の方法があるかもしれません。ただし、これは機能しません。

_#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);
_

GCCは以下を生成します。

_# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);
_

閉じますが、サイコロはありません。もちろん、使用している先行標準プリプロセッサに応じて、YMMV。率直に言って、協力していないプリプロセッサで立ち往生している場合は、おそらく標準Cプリプロセッサの代わりに標準Cプリプロセッサを使用する方が簡単です(通常、コンパイラを適切に構成する方法があります)仕事をする方法を考え出そうと多くの時間を費やす。

205
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

正直なところ、これがなぜ機能するのか知りたくありません。なぜそれが機能するかを知っているなら、あなたは仕事でこの種のことを知っているその人になり、誰もがあなたに質問するようになります。 =)

編集:実際に動作する理由を知りたい場合は、誰も私に勝てないと仮定して、喜んで説明を投稿します。

32
Stephen Canon