次のようなマクロを使用して、一部の関数の名前が特定のマクロ変数の値に依存するプログラムを作成しようとしています。
#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
すべてのケースを個別にリストすることができますが、それを実行する賢い方法はあるでしょうか?
ベストアンサー1
標準 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つのレベルの間接性
別の回答へのコメントで、ケイド・ルー 尋ねたなぜ 2 レベルの間接参照が必要なのか。軽率な答えは、それが標準で要求されている動作方法だからです。文字列化演算子でも同等のトリックが必要になることがよくあります。
C99 標準のセクション 6.10.3 では「マクロの置換」について説明し、6.10.3.1 では「引数の置換」について説明します。
関数のようなマクロの呼び出しの引数が識別された後、引数の置換が行われます。置換リスト内のパラメータは、その前に
#
または##
プリプロセッサ トークンが付いているか、##
プリプロセッサ トークン (下記参照) が付いていない限り、そこに含まれるすべてのマクロが展開された後、対応する引数に置き換えられます。置換される前に、各引数のプリプロセッサ トークンは、プリプロセッサ ファイルの残りの部分を形成するかのように完全にマクロ置換されます。他のプリプロセッサ トークンは使用できません。
呼び出しではNAME(mine)
、引数は「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);
近いですが、ダメです。もちろん、使用しているプレ標準プリプロセッサに応じて結果は異なります。率直に言って、協力しないプリプロセッサに困っている場合は、作業を実行する方法を見つけようと多くの時間を費やすよりも、プレ標準プリプロセッサの代わりに標準 C プリプロセッサを使用するように手配する方がおそらく簡単です (通常、コンパイラを適切に構成する方法があります)。