我可以用宏的一些技巧?

在我们的遗留代码中,以及我们的现代代码中,我们使用宏来执行代码生成等的漂亮解决方案。我们使用###运算符。

我很好奇其他开发人员如何使用宏来做很酷的事情,如果他们根本使用它们的话。

在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__。
避免在代码中使用其他宏。

编辑:
仅当您没有合法的解决方案时才使用宏。

还有X Macro习惯用法,它可用于DRY和简单的代码生成:

一个使用尚未定义的宏在头部gen.xa类中定义

 /** 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 

扩展参数技巧的双重评估:(例如使用实际行号而不是“__LINE__”。)

  /* 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( foo, ==, bar ) 

要么

 UNLESS( foo, >=, 0, value=0; return false; ); 

合法。 断言/除非宏可以自动添加所有排序好的有用信息,如CodeLocation,堆栈跟踪,或优雅地抛出exception/ coredumping / exiting。


使errno更简单:

 #define ERRNO_FORMAT "errno= %d (\"%s\")" #define ERRNO_ARGS errno, strerror(errno) #define ERRNO_STREAM "errno= " << errno << " (\"" << strerror(errno) << "\") " 

例如printf(“Open failed。”ERRNO_FORMAT,ERRNO_ARGS);

我最喜欢的技巧之一是将可变数量的参数传递给宏,以便稍后用于调用类似printf的函数。 为此,我指定宏只有一个参数并在宏的主体中使用它而没有(),但是将所有参数传递给((和))中的宏,因此列表看起来像一个参数。 例如,

 #define TRACE( allargs) do { printf allargs; } while ( 0) ... TRACE(( "%s %s\n", "Help", "me")); 

记录是经常使用宏的地方之一:

 #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 

一种hacky方法,让预处理器根据您可以通过宏表达的更高级别的结构为您生成代码。

我使用宏的主要地方是我自己的测试框架。 例如,当我想断言某些代码必须抛出时,我使用这个宏:

 #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 

这实现了类似于@Ferruccio之前关于BOOST_BINARY的响应的类似目标,尽管有点扩展。

这是代码(copy’n粘贴,未经测试,请参阅链接了解更多详情)

 // 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()宏是循环体的唯一内容。

我也是编译时断言的忠实粉丝。 我的表述略有不同,但与我见过的其他人相比没有真正的优势。 关键是要形成一个唯一命名的typedef,如果断言条件为false,则抛出错误,否则不会。 在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); ... 

产生的错误消息通常是模糊的,但是将包含标识符的片段,使得能够通过powershell发现违规行。

我曾经在编写代码生成实用程序可能是首选答案的地方使用预处理程序,就像另一个答案中的代码一样,它根据枚举成员名称的独特部分生成了大量的样板。 在编写大量用C语言编译的消息调度胶时,这一点特别方便。

人们可以简化重复的事情。 枚举列表

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

使用C99可变参数宏的结构文字,默认值(不为零)

 struct Example { int from; int to; const char *name; } #define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__}) 

使用EXAMPLE(.name="test")使用默认值,但显式覆盖name除外。 后来提到同一成员的阴影在标准中有明确定义。

您可以使用宏来定义具有不同数据类型的相同function。 例如:

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

在此示例中,定义了三个函数( bits_str_uchar()bits_str_uint()bits_str_int() ),它们处理三种不同的数据类型( unsigned charunsigned intint )。 但是,所有返回的字符串都包含传递的值的位。

实现COM服务器时,必须处理代码可能抛出的所有exception – 通过COM方法边界的exception通常会使调用应用程序崩溃。

方括号对此有用。 有一个开放括号,它是一个包含“try”的宏和一个包含一组“catch”的结束括号,将exception包装到ErrorInfo中并生成HRESULT。

从CrashRpt项目,需要技巧来扩展宏并定义:

 #define WIDEN2(x) L ## x #define WIDEN(x) WIDEN2(x) std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__); 

大多数(所有?)C ++unit testing框架都是基于宏构建的。 我们使用UnitTest ++ 。 看看各种花哨的宏。

BOOST_BINARY宏执行一些clevel预处理器技巧,使C ++能够以二进制表示数字常量。 但是它限制在0-255。

pthreads实用程序宏特别令人印象深刻恕我直言。

当我使用巨大的c / c ++嵌套结构(如用于3GPP RRC / NBAP / RNSAP的嵌套结构)时,我遵循这个技巧使代码看起来干净。

 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 

是的,这是粗糙和不安全的。 它只会在bug被隔离之前应用,所以这个宏没有任何危害。

我经常使用这个。 我有一个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编程语言也提供了非常相似的function。

如果你认为上面没有上下文,你可以将think定义为

 #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  #define loop(i,x) for(i=0; i