マクロで使用できるいくつかのトリックは何ですか? [閉まっている]
-
19-08-2019 - |
質問
従来のコードおよび最新のコードでは、マクロを使用してコード生成などの気の利いたソリューションを実行します。また、#
および##
演算子の両方を使用します。
他の開発者がマクロを使用してクールなことを行う場合、マクロを使用する方法に興味があります。
解決
Cでは、逐語的な引数を取得する何らかの処理を行うマクロを定義すると同時に、そのアドレスを透過的に取得できる関数を定義するのが一般的です。
// could evaluate at compile time if __builtin_sin gets
// special treatment by the compiler
#define sin(x) __builtin_sin(x)
// parentheses avoid substitution by the macro
double (sin)(double arg) {
return sin(arg); // uses the macro
}
int main() {
// uses the macro
printf("%f\n", sin(3.14));
// uses the function
double (*x)(double) = &sin;
// uses the function
printf("%f\n", (sin)(3.14));
}
他のヒント
クールなマクロ:アサート、ガードを含める、__ FILE __、__ LINE__。
コード内で他のマクロを使用しないでください。
編集:
マクロを使用するのは、それらを使用しない法的解決策がない場合のみです。
DRYと簡単なコード生成に役立つXマクロのイディオムもあります:
未定義マクロを使用して、ヘッダーgen.xで一種のテーブルを定義します:
/** 1st arg is type , 2nd is field name , 3rd is initial value , 4th is help */
GENX( int , "y" , 1 , "number of ..." );
GENX( float , "z" , 6.3 , "this value sets ..." );
GENX( std::string , "name" , "myname" , "name of ..." );
それから彼は、通常は異なる定義で各#includeに対して定義するさまざまな場所でそれを使用できます:
class X
{
public :
void setDefaults()
{
#define GENX( type , member , value , help )\
member = value ;
#include "gen.x"
#undef GENX
}
void help( std::ostream & o )
{
#define GENX( type , member , value , help )\
o << #member << " : " << help << '\n' ;
#include "gen.x"
#undef GENX
}
private :
#define GENX( type , member , value , help )\
type member ;
#include "gen.x"
#undef GENX
}
Boost.Preprocessor プリプロセッサの多くの興味深い用途を見つける...
デバッグ用のSHOW():
#define SHOW(X) cout << # X " = " << (X) << endl
引数を拡張するための二重評価のトリック:(例:<!> quot; __ LINE __ <!> quot;ではなく、実際の行番号を使用します。)
/* Use CONCATENATE_AGAIN to expand the arguments to CONCATENATE */
#define CONCATENATE( x,y) CONCATENATE_AGAIN(x,y)
#define CONCATENATE_AGAIN(x,y) x ## y
静的なコンパイル時アサーション。
例:
#define CONCATENATE_4( a,b,c,d) CONCATENATE_4_AGAIN(a,b,c,d)
#define CONCATENATE_4_AGAIN(a,b,c,d) a ## b ## c ## d
/* Creates a typedef that's legal/illegal depending on EXPRESSION. *
* Note that IDENTIFIER_TEXT is limited to "[a-zA-Z0-9_]*". *
* (This may be replaced by static_assert() in future revisions of C++.) */
#define STATIC_ASSERT( EXPRESSION, IDENTIFIER_TEXT) \
typedef char CONCATENATE_4( static_assert____, IDENTIFIER_TEXT, \
____failed_at_line____, __LINE__ ) \
[ (EXPRESSION) ? 1 : -1 ]
経由で使用:
typedef int32_t int4;
STATIC_ASSERT( sizeof(int4) == 4, sizeof_int4_equal_4 );
クラスCodeLocationのインスタンスの初期化:(呼び出しポイントからのファイル/行/関数の保存-これは、マクロを使用するか、ソースポイントで__FILE __ / __ LINE __ / etcマクロに直接アクセスすることでのみ実行できます。 )
/* Note: Windows may have __FUNCTION__. C99 defines __func__. */
#define CURRENT_CODE_LOCATION() \
CodeLocation( __PRETTY_FUNCTION__, __FILE__, __LINE__ )
その後、MESSAGE / WARN / FAILマクロによって、便利なソースロケーション印刷メカニズムとして使用されます。例:
#define WARN_IF_NAN(X) \
do \
{ \
if ( isnan(X) != 0 ) \
WARN( # X " is NaN (Floating Point NOT-A-NUMBER)" ); \
if ( isinf(X) != 0 ) \
WARN( # X " is INF (Floating Point INFINITY)" ); \
} while ( false )
Assert / Unlessマクロ。マクロを介して、「==」などの演算子を含む任意のトークンを渡すことができます。次のような構成:
ASSERT( foo, ==, bar )
または
UNLESS( foo, >=, 0, value=0; return false; );
合法です。 Assert / Unlessマクロは、CodeLocation、スタックトレース、例外のスロー/コアダンプ/正常終了などの便利な情報をすべての種類に自動的に追加できます。
errnoを単純化する:
#define ERRNO_FORMAT "errno= %d (\"%s\")"
#define ERRNO_ARGS errno, strerror(errno)
#define ERRNO_STREAM "errno= " << errno << " (\"" << strerror(errno) << "\") "
E.g。 printf(<!> quot; Open failed。<!> quot; ERRNO_FORMAT、ERRNO_ARGS);
私のお気に入りのトリックの1つは、可変数の引数をマクロに渡す方法です。これは、後でprintfのような関数を呼び出すときに使用するためのものです。これを行うには、マクロにパラメーターが1つだけあることを指定し、()なしでマクロの本文で使用しますが、すべてのパラメーターを((および))のマクロに渡すため、リストは単一の引数のように見えます。たとえば、
#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s\n", "Help", "me"));
ロギングは、マクロが特に頻繁に使用される1つの場所です。
#define LOG(log) \
if (!log.enabled()) {} \
else log.getStream() << __FILE__ << "@" << __LINE__ << ": "
log_t errorlog;
...
LOG(errorlog) << "This doesn't look good:" << somedata;
このおもしろいものは、Sean Barrettに感謝します:
#ifndef blah
#define blah(x) // something fun
#include __FILE__
#undef blah
#endif
#ifndef blah
#define blah(x) // something else that is also fun
#include __FILE__
#undef blah
#endif
#ifdef blah
blah(foo)
blah(bar)
#endif
マクロを介して表現できるいくつかのより高いレベルの構造に基づいて、プリプロセッサにコードを生成させるハックな方法。
マクロを使用する主な場所は、独自のテストフレームワークです。たとえば、いくつかのコードをスローする必要があると断言したい場合は、次のマクロを使用します。
#define MUST_THROW( expr )
try {
(expr);
(myth_suite_).Fail( #expr +
std::string( " should throw but didn't" ) );
}
catch( ... ) {
}
次のように使用します:
MUST_THROW( some_bogus_stuff() );
MUST_THROW( more_bogus_stuff() );
これらを使用する他の唯一の場所は、クラス宣言です。マクロがあります:
#define CANNOT_COPY( cls ) \
private: \
cls( const cls & ); \
void operator=( const cls & ) \
クラスをコピー(または割り当て)できないことを指定するために使用します:
class BankAccount {
CANNOT_COPY( BankAccount );
....
};
これは特別なことはしませんが、人々の注意を引き、簡単に検索できます。
埋め込みコードについては、 embeddedgurus.comからの素敵なトリック バイナリ値を処理できます:
B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,93
これにより、BOOST_BINARYについての@Ferruccioからの以前の応答と同様の目標が達成されますが、少し拡張されています。
コードは次のとおりです(コピーして貼り付けられていますが、テストされていません。詳細についてはリンクを参照してください)
// Internal Macros
#define HEX__(n) 0x##n##LU
#define B8__(x) ((x&0x0000000FLU)?1:0) \
+((x&0x000000F0LU)?2:0) \
+((x&0x00000F00LU)?4:0) \
+((x&0x0000F000LU)?8:0) \
+((x&0x000F0000LU)?16:0) \
+((x&0x00F00000LU)?32:0) \
+((x&0x0F000000LU)?64:0) \
+((x&0xF0000000LU)?128:0)
// User-visible Macros
#define B8(d) ((unsigned char)B8__(HEX__(d)))
#define B16(dmsb,dlsb) (((unsigned short)B8(dmsb)<<8) + B8(dlsb))
#define B32(dmsb,db2,db3,dlsb) \
(((unsigned long)B8(dmsb)<<24) \
+ ((unsigned long)B8(db2)<<16) \
+ ((unsigned long)B8(db3)<<8) \
+ B8(dlsb))
マクロが好きです。デバッグするときとても楽しい!
デバッグソナーのようなものを、リリースビルドからコンパイルできるようにする単純なマクロでよくラップします。
#ifdef DEBUG
#define D(s) do { s; } while(0)
#else
#define D(s) do {/**/} while(0)
#endif
後の使用方法は通常次のようなものです:
D(printf("level %d, condition %s\n", level, condition));
do{}while(0)
イディオムは、誤ってD(...)
を条件またはループの唯一のコンテンツとして使用することによって生じる問題を回避するためにあります。結局のところ、このようなコードが間違ったことを意味するのは望ましくありません:
for(i=1;i<10;++i) D(printf("x[%d]=%f\n",i,x[i]));
SomeReallyExpensiveFunction(x);
そのケースでエラーをスローできれば、プリプロセッサは完全なコンパイラであり、D()
マクロがループ本体の唯一のコンテンツであることを伝える必要があります。
私はコンパイル時のアサーションの大ファンでもあります。私の定式化はわずかに異なりますが、私が見た他のものに対する本当の利点はありません。重要なのは、アサートされた条件がfalseの場合にエラーをスローする一意の名前のtypedefを形成することであり、そうでない場合はそうではありません。 cassert.hには以下があります。
/*! \brief Compile-time assertion.
*
* Note that the cassert() macro generates no code, and hence need not
* be restricted to debug builds. It does have the side-effect of
* declaring a type name with typedef. For this reason, a unique
* number or string of legal identifier characters must be included
* with each invocation to avoid the attempt to redeclare a type.
*
* A failed assertion will attempt to define a type that is an array
* of -1 integers, which will throw an error in any standards
* compliant compiler. The exact error is implementation defined, but
* since the defined type name includes the string "ASSERTION" it
* should trigger curiosity enough to lead the user to the assertion
* itself.
*
* Because a typedef is used, cassert() may be used inside a function,
* class or struct definition as well as at file scope.
*/
#define cassert(x,i) typedef int ASSERTION_##i[(x)?1:-1]
そして、あるソースファイルでは、typedefが有効な場所ならどこでも:
#include "cassert.h"
...
cassert(sizeof(struct foo)==14, foo1);
...
結果のエラーメッセージはしばしばあいまいですが、識別子の断片が含まれているため、ブルートフォースによって問題のある行を発見できます。
enumメンバーの一意の部分に基づいて大量のボイラープレートを生成した別の回答のコードのように、コード生成ユーティリティの作成が好ましい回答である可能性のある場所でプリプロセッサを使用して罪を犯しました名。これは、Cでコンパイルするメッセージディスパッチグルーを大量に作成する場合に特に便利です。
デフォルト値(ゼロではない)を持つ構造リテラル、C99可変長マクロを使用
struct Example {
int from;
int to;
const char *name;
}
#define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__})
using EXAMPLE(.name="test")
は、name
の明示的なオーバーライドを除き、デフォルト値を使用します。同じメンバーの後で言及するこのシャドーイングは、標準で明確に定義されています。
つまり、反復的なことを単純化できます。列挙リスト
enum {
kOneEnum,
kTwoEnum,
kThreeEnum,
kFourEnum
};
...そして後で構造化された方法でスイッチケースを行う
#define TEST( _v ) \
case k ## _v ## Enum: \
CallFunction ## _v(); \
break;
switch (c) {
TEST( One );
TEST( Two );
TEST( Three );
TEST( Four );
}
注:確かにこれは関数ポインター配列で実行できますが、パラメーターを追加し、単一のハッシュで文字列展開を使用するためにもう少し柔軟性が開きます。
...または文字列をテストして正しい列挙値を取得する
int value = -1;
char *str = getstr();
#define TEST( _v ) \
if (!strcmp(# _v, str)) \
value = k ## _v ## Enum
TEST( One );
TEST( Two );
TEST( Three );
TEST( Four );
マクロを使用して、異なるデータ型で同じ機能を定義できます。例:
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#define DEFINE_BITS_STR(name, type) \
char *bits_str_##name(type value) \
{ \
int len = sizeof(type) * CHAR_BIT; \
char *result; \
type n; \
int i; \
\
result = (char *)calloc(len+1, sizeof(type)); \
if(result == NULL) \
return NULL; \
\
memset(result, '0', len); \
result[len] = 0x00; \
\
n = value; \
i = len; \
while(n) \
{ \
if(n & 1) \
result[i] = '1'; \
\
n >>= 1; \
--i; \
} \
\
return result; \
}
DEFINE_BITS_STR(uchar, unsigned char)
DEFINE_BITS_STR(uint, unsigned int)
DEFINE_BITS_STR(int, unsigned int)
int main()
{
unsigned char value1 = 134;
unsigned int value2 = 232899;
int value3 = 255;
char *ret;
ret = bits_str_uchar(value1);
printf("%d: %s\n", value1, ret);
ret = bits_str_uint(value2);
printf("%d: %s\n", value2, ret);
ret = bits_str_int(value3);
printf("%d: %s\n", value3, ret);
return 1;
}
この例では、3つの異なるデータ型(bits_str_uchar()
、bits_str_uint()
、bits_str_int()
)を処理する3つの関数(unsigned char
、unsigned int
、int
)を定義しています。ただし、すべて渡された値のビットを含む文字列を返します。
COMサーバーを実装する場合、コードがスローする可能性のあるすべての例外を処理する必要があります。COMメソッドの境界を介して例外を許可すると、呼び出し元のアプリケーションがクラッシュすることがよくあります。
これには、メソッドブラケットが便利です。 <!> quot; try <!> quot;を含むマクロである開始ブラケットがあります。 <!> quot; catch <!> quot; esのセットを含む閉じ括弧、例外をErrorInfoにラップしてHRESULTを生成します。
CrashRptプロジェクトから、マクロを広げて定義するためのトリックが必要です:
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
ほとんどの(すべて?)C ++単体テストフレームワークはマクロに基づいて構築されています。 UnitTest ++ を使用します。あらゆる種類の派手なマクロを確認してください。
BOOST_BINARY マクロは、 clevelプリプロセッサトリックにより、C ++がバイナリで数値定数を表現できるようになります。ただし、0〜255に制限されています。
pthreadsユーティリティマクロは特に印象的です。
3GPP RRC / NBAP / RNSAPに使用されるような巨大なc / c ++ネスト構造で作業する場合、このトリックに従ってコードをきれいに見せます。
struct leve1_1
{
int data;
struct level2
{
int data;
struct level3
{
int data;
} level_3_data;
} level_2_data;
} level_1_data;
level_1_data.data = 100;
#define LEVEL_2 leve1_1_data.level_2_data
LEVEL_2.data = 200;
#define LEVEL_3 LEVEL_2.level_3_data
LEVEL_3.data = 300;
#undef LEVEL_2
#undef LEVEL_3
これにより、メンテナンス時の作業が楽になります。設計時もコードが読みやすくなります。
それらを言語の構造に変換して、型の安全性とデバッグ能力を向上させます。
void _zero_or_die(int v, const char* filename, int line)
{
if (v != 0)
{
fprintf(stderr, "error %s:%d\n", filename, line);
exit(1);
}
}
#define ZERO_OR_DIE_ for (int _i=1; _i == 1; _zero_or_die(_i, __FILE__, __LINE__)) _i=
ZERO_OR_DIE_ pipe(fd);
ZERO_OR_DIE_ close(0);
ZERO_OR_DIE_ sigaction(SIGSEGV, &sigact, NULL);
ZERO_OR_DIE_ pthread_mutex_lock(&mt);
ZERO_OR_DIE_ pthread_create(&pt, NULL, func, NULL);
マイクロコントローラーでは、ハードウェアブレークポイントには多くの欠点があるため、UARTを使用してコードをデバッグするのが一般的です。
これは非常に有用であることが証明されている単純なマクロです。
#define DEBUG_OUT(value) sprintf(uartTxBuf, "%s = 0x%04X\n", #value, value);\
puts_UART((uint16_t *) uartTxBuf)
使用例:
for (i=0; i < 4; i++)
{
DEBUG_OUT(i);
DEBUG_OUT(i % 3);
}
受信したストリーム:
i = 0x0000
i % 3 = 0x0000
i = 0x0001
i % 3 = 0x0001
i = 0x0002
i % 3 = 0x0002
i = 0x0003
i % 3 = 0x0000
はい、粗野で安全ではありません。バグが特定されるまで適用されるだけなので、このマクロは問題ありません。
これはよく使用します。 debug.h
ヘッダーを次のように定義しています:
#ifndef DEBUG_H
#define DEBUG_H
#ifdef DEBUG
#define debuf if(1)
#else
#define debug if(0)
#endif
#endif
そして:
debug {
printf("message from debug!");
}
"message from debug!"
メッセージを取得する場合は、次を使用してコンパイルします。
gcc -D DEBUG foo.c
それ以外の場合、何も起こりません。 Gccは非常にスマートなコンパイラです。 DEBUG
が定義されていない場合、生成されたif(0)
(デッドコード)は、最適化を有効にしてコードから削除されます。
さらに次のことができます:
debug
{
pritnf("I'm in debug mode!\n");
}
else
{
printf("I'm not in debug mode\n");
}
先日、 Dプログラミング言語が非常によく似た機能を提供しているのを見ました。
上記の文脈なしで考える場合、思考を次のように定義できます
#define in_debug if(1)
#define not_debug else
そして
in_debug {
printf("I'm in debug mode!");
}
not_debug {
printf("Not in debug mode!");
}
マクロでは、単なるテキスト置換であるため、フローの制御は非常に簡単です。 forループの例を次に示します。
#include <stdio.h>
#define loop(i,x) for(i=0; i<x; i++)
int main(int argc, char *argv[])
{
int i;
int x = 5;
loop(i, x)
{
printf("%d", i); // Output: 01234
}
return 0;
}