90

以前の質問の多くで述べたように、私はK&Rを介して作業しており、現在プリプロセッサを使用しています。より興味深いことの1つ、つまりCを学習しようとした以前の試みからは知らなかったことは、##プリプロセッサ演算子です。K&Rによると:

プリプロセッサ演算子## は、マクロ展開中に実際の引数を連結する方法を提供します。置換テキストのパラメーターがに隣接している##場合、パラメーターは実際の引数に置き換えられ、 ##周囲の空白が削除され、結果が再スキャンされます。たとえば、マクロはpaste その2つの引数を連結します。

#define paste(front, back) front ## back

したがってpaste(name, 1)、トークンを作成します name1

誰かがこれを現実の世界でどのようにそしてなぜ使うのでしょうか?その使用の実際的な例は何ですか、そして考慮すべき落とし穴はありますか?

4

13 に答える 13

51

##トークンの貼り付け (' ') または文字列化 (' ') の前処理演算子を使用する場合に注意すべきことの 1 つ#は、それらがすべての場合に適切に機能するために、余分なレベルの間接化を使用する必要があることです。

これを行わず、トークン貼り付け演算子に渡されたアイテムがマクロ自体である場合、おそらく望んでいない結果が得られます。

#include <stdio.h>

#define STRINGIFY2( x) #x
#define STRINGIFY(x) STRINGIFY2(x)
#define PASTE2( a, b) a##b
#define PASTE( a, b) PASTE2( a, b)

#define BAD_PASTE(x,y) x##y
#define BAD_STRINGIFY(x) #x

#define SOME_MACRO function_name

int main() 
{
    printf( "buggy results:\n");
    printf( "%s\n", STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( BAD_PASTE( SOME_MACRO, __LINE__)));
    printf( "%s\n", BAD_STRINGIFY( PASTE( SOME_MACRO, __LINE__)));

    printf( "\n" "desired result:\n");
    printf( "%s\n", STRINGIFY( PASTE( SOME_MACRO, __LINE__)));
}

出力:

buggy results:
SOME_MACRO__LINE__
BAD_PASTE( SOME_MACRO, __LINE__)
PASTE( SOME_MACRO, __LINE__)

desired result:
function_name21
于 2008-10-19T23:49:02.930 に答える
47

CrashRpt: ## を使用してマクロのマルチバイト文字列を Unicode に変換する

CrashRpt (クラッシュ レポート ライブラリ) の興味深い使用法は次のとおりです。

#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
//Note you need a WIDEN2 so that __DATE__ will evaluate first.

ここでは、1 文字あたり 1 バイトの文字列ではなく、2 バイトの文字列を使用したいと考えています。これはおそらく本当に無意味に見えますが、正当な理由があります。

 std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);

彼らは、日付と時刻を含む文字列を返す別のマクロでそれを使用します。

La の隣に置く__ DATE __と、コンパイル エラーが発生します。


Windows: 一般的な Unicode またはマルチバイト文字列に ## を使用する

Windows は次のようなものを使用します。

#ifdef  _UNICODE
    #define _T(x)      L ## x
#else
    #define _T(x) x
#endif

そして_T、コードのあらゆる場所で使用されます


クリーンなアクセサ名とモディファイヤ名に使用するさまざまなライブラリ:

また、アクセサーと修飾子を定義するためにコードで使用されていることも確認しました。

#define MYLIB_ACCESSOR(name) (Get##name)
#define MYLIB_MODIFIER(name) (Set##name)

同様に、他の種類の巧妙な名前作成にも同じ方法を使用できます。


一度に複数の変数宣言を行うために使用するさまざまなライブラリ:

#define CREATE_3_VARS(name) name##1, name##2, name##3
int CREATE_3_VARS(myInts);
myInts1 = 13;
myInts2 = 19;
myInts3 = 77;
于 2008-10-19T19:58:51.897 に答える
14

これは、新しいバージョンのコンパイラにアップグレードするときに遭遇した落とし穴です。

トークン貼り付け演算子 ( ##) を不必要に使用すると、移植性がなくなり、望ましくない空白、警告、またはエラーが生成される可能性があります。

トークン貼り付け演算子の結果が有効なプリプロセッサ トークンでない場合、トークン貼り付け演算子は不要であり、有害である可能性があります。

たとえば、トークン貼り付け演算子を使用して、コンパイル時に文字列リテラルを構築しようとする場合があります。

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a##+##b)
#define NS(a, b) STRINGIFY(a##::##b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

一部のコンパイラでは、これにより期待される結果が出力されます。

1+2 std::vector

他のコンパイラでは、これには望ましくない空白が含まれます。

1 + 2 std :: vector

GCC の最新バージョン (>=3.3 程度) では、このコードのコンパイルに失敗します。

foo.cpp:16:1: pasting "1" and "+" does not give a valid preprocessing token
foo.cpp:16:1: pasting "+" and "2" does not give a valid preprocessing token
foo.cpp:16:1: pasting "std" and "::" does not give a valid preprocessing token
foo.cpp:16:1: pasting "::" and "vector" does not give a valid preprocessing token

解決策は、プリプロセッサ トークンを C/C++ 演算子に連結するときにトークン貼り付け演算子を省略することです。

#define STRINGIFY(x) #x
#define PLUS(a, b) STRINGIFY(a+b)
#define NS(a, b) STRINGIFY(a::b)
printf("%s %s\n", PLUS(1,2), NS(std,vector));

連結に関するGCC CPP ドキュメントの章には、トークン貼り付け演算子に関するより有用な情報があります。

于 2008-10-19T21:08:52.130 に答える
6

これは、不必要に同じことを繰り返さないようにするために、あらゆる状況で役立ちます。以下は、Emacs ソース コードの例です。ライブラリから多数の関数をロードしたいと考えています。関数「foo」は などに割り当てる必要がありますfn_foo。次のマクロを定義します。

#define LOAD_IMGLIB_FN(lib,func) {                                      \
    fn_##func = (void *) GetProcAddress (lib, #func);                   \
    if (!fn_##func) return 0;                                           \
  }

次に、それを使用できます。

LOAD_IMGLIB_FN (library, XpmFreeAttributes);
LOAD_IMGLIB_FN (library, XpmCreateImageFromBuffer);
LOAD_IMGLIB_FN (library, XpmReadFileToImage);
LOAD_IMGLIB_FN (library, XImageFree);

利点は、両方を書く必要がないことですfn_XpmFreeAttributes("XpmFreeAttributes"そして、どちらかのスペルを間違えるリスクがあります)。

于 2008-10-19T20:09:48.163 に答える
4

スタック オーバーフローに関する以前の質問では、エラーが発生しやすい再入力を行わずに、列挙型定数の文字列表現をスムーズに生成する方法を求めていました。

リンク

その質問に対する私の答えは、小さなプリプロセッサ マジックを適用することで、列挙型を次のように定義できることを示しました (たとえば) ...;

ENUM_BEGIN( Color )
  ENUM(RED),
  ENUM(GREEN),
  ENUM(BLUE)
ENUM_END( Color )

... マクロ展開が (.h ファイルで) 列挙を定義するだけでなく、一致する文字列の配列 (.c ファイルで) も定義するという利点があります。

const char *ColorStringTable[] =
{
  "RED",
  "GREEN",
  "BLUE"
};

文字列テーブルの名前は、## 演算子を使用してマクロ パラメータ (つまり、Color) を StringTable に貼り付けたものです。このようなアプリケーション (トリック?) では、# および ## 演算子が非常に重要です。

于 2008-10-19T22:41:18.310 に答える
3

マクロ パラメーターを別のものと連結する必要がある場合は、トークンの貼り付けを使用できます。

テンプレートに使用できます。

#define LINKED_LIST(A) struct list##_##A {\
A value; \
struct list##_##A *next; \
};

この場合、 LINKED_LIST(int) はあなたに与えるでしょう

struct list_int {
int value;
struct list_int *next;
};

同様に、リスト トラバーサル用の関数テンプレートを作成できます。

于 2008-10-19T20:09:45.540 に答える
2

私はこれを C プログラムで使用して、ある種の呼び出し規則に準拠する必要がある一連のメソッドのプロトタイプを正しく強制するのに役立てています。ある意味では、これは単純な C での貧弱なオブジェクト指向に使用できます。

SCREEN_HANDLER( activeCall )

次のように展開します。

STATUS activeCall_constructor( HANDLE *pInst )
STATUS activeCall_eventHandler( HANDLE *pInst, TOKEN *pEvent );
STATUS activeCall_destructor( HANDLE *pInst );

これにより、次の場合にすべての「派生」オブジェクトに対して正しいパラメーター化が強制されます。

SCREEN_HANDLER( activeCall )
SCREEN_HANDLER( ringingCall )
SCREEN_HANDLER( heldCall )

上記をヘッダーファイルなどに追加します。定義を変更したり、「オブジェクト」にメソッドを追加したりしたい場合のメンテナンスにも役立ちます。

于 2008-10-19T20:15:18.137 に答える
2

SGlibは ## を使用して、基本的に C のテンプレートをごまかします。関数のオーバーロードがないため、生成された関数の名前に型名を接着するために ## が使用されます。list_t という名前のリスト型があれば、sglib_list_t_concat などの名前の関数を取得します。

于 2008-10-19T20:16:43.467 に答える
2

組み込み用の非標準 C コンパイラでのホーム ロール アサートに使用します。



#define ASSERT(exp) if(!(exp)){ \
                      print_to_rs232("Assert failed: " ## #exp );\
                      while(1){} //Let the watchdog kill us 


于 2008-10-19T21:10:19.973 に答える
1

マクロで定義された変数にカスタム プレフィックスを追加するために使用します。次のようなものです:

UNITTEST(test_name)

次のように展開します。

void __testframework_test_name ()
于 2008-10-19T20:02:51.390 に答える
1

主な用途は、命名規則があり、マクロでその命名規則を利用したい場合です。おそらく、メソッドにはいくつかのファミリがあります。

オブジェクトのライフサイクルを処理するためのマクロを作成できます。

#define LIFECYCLE(name, func) (struct name x = name##_create(); name##_activate(x); func(x); name##_release())

もちろん、これが適用される命名規則は、一種の「オブジェクトの最小バージョン」だけではありません。ほとんどの命名規則では、共通の部分文字列を使用して名前を形成しています。関数名 (上記のように)、フィールド名、変数名、またはその他のほとんどの名前を指定できます。

于 2008-10-19T20:15:02.100 に答える
1

WinCE の重要な用途の 1 つ:

#define BITFMASK(bit_position) (((1U << (bit_position ## _WIDTH)) - 1) << (bit_position ## _LEFTSHIFT))

レジスタ ビットの説明を定義する際に、次のことを行います。

#define ADDR_LEFTSHIFT                          0

#define ADDR_WIDTH                              7

また、BITFMASK を使用している間は、次を使用するだけです。

BITFMASK(ADDR)
于 2013-05-20T11:42:30.540 に答える
0

ロギングに非常に便利です。できるよ:

#define LOG(msg) log_msg(__function__, ## msg)

または、コンパイラがfunctionfuncをサポートしていない場合:

#define LOG(msg) log_msg(__file__, __line__, ## msg)

上記の「関数」はメッセージを記録し、どの関数がメッセージを記録したかを正確に示します。

私の C++ 構文は完全に正しくない可能性があります。

于 2008-10-19T20:31:23.923 に答える