web-dev-qa-db-ja.com

##プリプロセッサ演算子と考慮すべき問題の用途は何ですか?

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

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

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

paste(name, 1)はトークン_name1_を作成します。

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

86
John Rudy

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__);

日付と時刻の文字列を返す別のマクロで使用します。

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


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;
46
Brian R. Bondy

トークンペースト( '## ')または文字列化(' # ')前処理演算子は、すべての場合に適切に動作するために余分なレベルの間接参照を使用する必要があるということです。

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

#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
49
Michael Burr

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

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

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

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

#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ドキュメンテーションの連結に関する章 には、トークン貼り付け演算子に関するより有用な情報があります。

14
bk1e

これは、不必要に繰り返さないために、あらゆる種類の状況で役立ちます。以下は、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"の両方を記述する必要がないことです(そして、どちらか一方のスペルを間違えるリスクがあります)。

6
Vebjorn Ljosa

Stack Overflowに関する以前の質問では、エラーが発生しやすい再入力なしで、列挙定数の文字列表現を生成するスムーズな方法を求めました。

リンク

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

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

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

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

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

4
Bill Forster

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

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

#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;
};

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

2
qrdl

私は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 )

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

2
Tall Jeff

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

2
Jack

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



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

2
c0m4

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)
1
Keshava GN

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

UNITTEST(test_name)

展開先:

void __testframework_test_name ()
1
John Millikin

主な用途は、命名規則があり、マクロにその命名規則を利用させたい場合です。おそらく、いくつかのメソッドファミリがあります:image_create()、image_activate()、およびimage_release()また、file_create()、file_activate()、file_release()、およびmobile_create()、mobile_activate()、mobile_release()。

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

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

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

1
mcherm

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

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

または、コンパイラがfunctionおよびfuncをサポートしていない場合:

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

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

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

0
ya23