##プリプロセッサ演算子と考慮すべき問題の用途は何ですか?
-
03-07-2019 - |
質問
以前の質問の多くで述べたように、私はK& Rを使用しており、現在はプリプロセッサに取り組んでいます。より興味深いものの1つ— C—を学習しようとする以前の試みで、これまで知らなかった何か。 ##
プリプロセッサ演算子です。 K& Rによると:
プリプロセッサ演算子
##
実際の連結方法を提供します マクロ展開中の引数。もし 置換テキストのパラメーターは##
に隣接して、パラメーターは 実引数に置き換えられ、##
および周囲の空白は 削除され、結果が再スキャンされます。 たとえば、マクロpaste
2つの引数を連結します:
#define paste(front、back)front ## back
so
paste(name、1)
はトークンを作成しますname1
。
実世界でこれをどのように、そしてなぜ使用するのでしょうか?その使用の実際的な例は何ですか?また、考慮すべき問題はありますか?
解決
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;
他のヒント
トークン貼り付け( ' ##
')または文字列化( '#
')の前処理演算子を使用している場合に注意すべきことの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
コンパイラの新しいバージョンにアップグレードするときに遭遇した落とし穴は次のとおりです。
トークン貼り付け演算子( ##
)の不必要な使用は移植性がなく、望ましくない空白、警告、またはエラーが発生する可能性があります。
トークン貼り付け演算子の結果が有効なプリプロセッサトークンではない場合、トークン貼り付け演算子は不要であり、おそらく有害です。
たとえば、トークン貼り付け演算子を使用して、コンパイル時に文字列リテラルを構築しようとする場合があります。
#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バージョン(&gt; = 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の連結に関する章にはトークン貼り付け演算子に関するより有用な情報。
これは、不必要に自分を繰り返さないために、あらゆる状況で役立ちます。以下は、Emacsソースコードからの例です。ライブラリから多くの関数をロードしたいと思います。関数&quot; foo&quot; 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
と&quot; XpmFreeAttributes&quot;
の両方を記述する必要がないことです(そして、どちらか一方のスペルを間違えるリスクがあります)。
Stack&nbsp; Overflowに関する以前の質問では、エラーが発生しやすい再入力をせずに、列挙定数の文字列表現を生成するスムーズな方法を求めました。
その質問に対する私の答えは、小さなプリプロセッサマジックを適用することで、列挙型をこのように定義できることを示しました(たとえば)...;
ENUM_BEGIN( Color )
ENUM(RED),
ENUM(GREEN),
ENUM(BLUE)
ENUM_END( Color )
...マクロ展開は列挙(.hファイル内)を定義するだけでなく、一致する文字列(.cファイル内)も定義するという利点があります。
const char *ColorStringTable[] =
{
"RED",
"GREEN",
"BLUE"
};
文字列テーブルの名前は、##演算子を使用してマクロパラメーター(つまり、色)をStringTableに貼り付けることに由来します。このようなアプリケーション(トリック?)では、#および##演算子が非常に貴重です。
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 )
上記のヘッダーファイルなど。また、定義を変更したり、「オブジェクト」にメソッドを追加したりする場合にも、メンテナンスに役立ちます。
SGlib は、##を使用して基本的にCのテンプレートを処理します。関数のオーバーロードがないため、##が使用されます。生成された関数の名前に型名を接着します。 list_tというリストタイプがある場合、sglib_list_t_concatなどの名前の関数を取得します。
埋め込み用の非標準Cコンパイラでホームロールされたアサートに使用します:
#define ASSERT(exp) if(!(exp)){ \
print_to_rs232("Assert failed: " ## #exp );\
while(1){} //Let the watchdog kill us
マクロパラメーターを他のものと連結する必要がある場合、トークンの貼り付けを使用できます。
テンプレートに使用できます:
#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;
};
同様に、リストトラバーサル用の関数テンプレートを作成できます。
マクロで定義された変数にカスタムプレフィックスを追加するために使用します。次のようなものです:
UNITTEST(test_name)
展開先:
void __testframework_test_name ()
主な用途は、命名規則があり、マクロにその命名規則を利用させたい場合です。おそらく、いくつかのメソッドファミリがあります: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())
もちろん、一種の「オブジェクトの最小バージョン」これは、これが適用される唯一の種類の命名規則ではありません。ほとんどの命名規則は、共通のサブストリングを使用して名前を形成します。関数名(上記のように)、フィールド名、変数名、またはその他のほとんどのものがあります。
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)
ロギングに非常に便利です。できること:
#define LOG(msg) log_msg(__function__, ## msg)
または、コンパイラが function および func をサポートしていない場合:
#define LOG(msg) log_msg(__file__, __line__, ## msg)
上記の「関数」メッセージを記録し、どの関数がメッセージを記録したかを正確に示します。
私のC ++構文はまったく正しくない可能性があります。