##プリプロセッサオペレーターと考慮すべき問題のアプリケーションは何ですか?


87

以前の質問の多くで述べたように、私はK&Rを通じて作業しており、現在プリプロセッサーに取り組んでいます。さらに興味深いことの1つは、これまでCを学ぼうとした試みのどれからもこれまで私が知らなかったものですが、##プリプロセッサーオペレーターです。K&Rによると:

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

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

そのpaste(name, 1)トークンを作成します name1

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

回答:


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

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

L隣に置く__ 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;

3
コンパイル時に文字列リテラルを連結できるため、BuildDate式を減らして、std::wstring BuildDate = WIDEN(__DATE__) L" " WIDEN(__TIME__); 文字列全体を一度に暗黙的に構築できます。
user666412

49

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

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

#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

1
このプリプロセッサの動作の説明については、stackoverflow.com / questions / 8231966 /…を
Adam Davis

@MichaelBurr私はあなたの答えを読んでいました、そして私は疑いがあります。このLINEが行番号を出力するのはなぜですか?
HELP PLZ 2014年

3
@AbhimanyuAryan:これがあなたが求めているものかどうかはわかりませんが__LINE__、プリプロセッサによってソースファイルの現在の行番号に置き換えられる特別なマクロ名です。
Michael Burr 2014年

言語仕様は、のように、リンク/引用することができれば、それはクールになる、ここで
アントニオ

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文書の章では、トークン貼り付けるオペレータのより有用な情報を持っています。


ありがとう-私はこれに気づいていませんでした(しかし、これらの前処理演算子をあまり使用しません...)。
マイケルバー

3
これは、理由により「トークンの貼り付け」演算子と呼ばれます。目的は、完了時に単一のトークンになることです。いい記事です。
マークランサム

トークン貼り付け演算子の結果が有効なプリプロセッサトークンでない場合、動作は未定義です。
alecov 2014年

16進浮動小数点数や(C ++では)桁区切り記号やユーザー定義リテラルなどの言語の変更により、「有効な前処理トークン」の構成要素が継続的に変更されるため、このように乱用しないでください。(適切な言語の)トークンを分離する必要がある場合は、2つの別個のトークンとしてそれらを綴り、プリプロセッサの文法と適切な言語の間の偶発的な相互作用に依存しないでください。
Kerrek SB 2016

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"(そして、そのうちの1つをスペルミスする危険があります)。


4

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

リンク

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

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

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

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

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


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

同様に、リスト走査のための関数テンプレートを書くことができます。


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 )

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


2

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


2

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



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



3
コンパイラが文字列の貼り付けを行わなかったが、トークンの貼り付けを行ったという「非標準」とは、あなたが意味するものだと思います##
PJTraill 2015年

1

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

UNITTEST(test_name)

展開する:

void __testframework_test_name ()

1

主な用途は、命名規則があり、マクロにその命名規則を利用させたい場合です。おそらく、いくつかのメソッドファミリーがあります。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

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)

0

ロギングに非常に役立ちます。できるよ:

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

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

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

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

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


1
それで何をしようとしたの?「、」を「msg」にトークン貼り付けする必要がないため、「##」がなくても同様に機能します。メッセージを文字列化しようとしていましたか?また、FILELINEは小文字ではなく大文字でなければなりません。
bk1e 2008年

あなたは本当に正しいです。##の使用方法を確認するには、元のスクリプトを見つける必要があります。恥ずかしいです、今日はクッキーはありません!
ya23 2008
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.