在我们的遗留代码以及现代代码中,我们使用宏来执行漂亮的解决方案,如代码生成等。我们同时使用#
和##
运算符。
我很好奇其他开发人员如何使用宏来做很酷的事情,如果他们使用它们的话。
在我们的遗留代码以及现代代码中,我们使用宏来执行漂亮的解决方案,如代码生成等。我们同时使用#
和##
运算符。
我很好奇其他开发人员如何使用宏来做很酷的事情,如果他们使用它们的话。
在 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));
}
还有 X Macro 成语可用于 DRY 和简单的代码生成:
一个使用尚未定义的宏在标头 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
}
最酷的宏是:断言、包含守卫、__FILE__、__LINE__。
避免在代码中使用其他宏。
编辑:
仅当您没有没有它们的合法解决方案时才使用宏。
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; );
是合法的。Assert/Unless 宏可以自动添加各种有用的信息,例如 CodeLocation、堆栈跟踪或抛出异常/核心转储/优雅退出。
使 errno 更简单:
#define ERRNO_FORMAT "errno= %d (\"%s\")"
#define ERRNO_ARGS errno, strerror(errno)
#define ERRNO_STREAM "errno= " << errno << " (\"" << strerror(errno) << "\") "
例如 printf("打开失败。" ERRNO_FORMAT, ERRNO_ARGS );
您可以查看Boost.Preprocessor以找到预处理器的许多有趣用途...
我最喜欢的技巧之一是将可变数量的参数传递给宏,以便稍后用于调用类似 printf 的函数。为此,我指定宏只有一个参数,并在不带 () 的宏主体中使用它,但将所有参数传递给 (( 和 )) 中的宏,因此列表看起来像单个参数。例如,
#define TRACE( allargs) do { printf allargs; } while ( 0)
...
TRACE(( "%s %s\n", "Help", "me"));
我把这个有趣的归功于 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 LOG(log) \
if (!log.enabled()) {} \
else log.getStream() << __FILE__ << "@" << __LINE__ << ": "
log_t errorlog;
...
LOG(errorlog) << "This doesn't look good:" << somedata;
对于嵌入式代码,embeddedgurus.com 的一个很好的技巧使 您能够处理二进制值:
B8(01010101) // 85
B16(10101010,01010101) // 43,605
B32(10000000,11111111,10101010,01010101) // 2,164,238,93
这实现了与@Ferruccio 先前关于 BOOST_BINARY 的响应类似的目标,尽管有点扩展。
这是代码(复制粘贴,未经测试,请参阅链接了解更多详细信息)
// 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))
我喜欢宏。调试时非常有趣!
我使用宏的主要地方是在我自己的测试框架中。例如,当我想断言某些代码必须抛出时,我使用这个宏:
#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 );
....
};
这并没有做任何特别的事情,但会引起人们的注意并且很容易被搜索到。
使用 C99 可变参数宏构造具有默认值(非零)的字面量
struct Example {
int from;
int to;
const char *name;
}
#define EXAMPLE(...) ((struct Example){.from=0, .to=INT_MAX, .name="", __VA_ARGS__})
usingEXAMPLE(.name="test")
使用默认值,但显式覆盖name
. 后面提到的同一成员的这种阴影在标准中得到了很好的定义。
我经常将调试声纳之类的东西包装在一个简单的宏中,允许它从发布版本中编译出来:
#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,如果断言条件为假,则抛出错误,否则不抛出错误。在 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);
...
生成的错误消息通常是模糊的,但将包含标识符片段,使违规行能够被蛮力发现。
我一直在编写代码生成实用程序可能是首选答案的地方使用预处理器而感到内疚,就像另一个答案中的代码根据枚举成员名称的唯一部分生成大量样板。这在编写大量要在 C 中编译的消息调度胶水时特别方便。
从 CrashRpt 项目中,需要技巧来扩大宏并定义:
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
std::wstring BuildDate = std::wstring(WIDEN(__DATE__)) + L" " + WIDEN(__TIME__);
可以简化重复的事情,即。枚举列表
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;
}
在这个例子中,定义了三个函数(bits_str_uchar()
, bits_str_uint()
, bits_str_int()
)来处理三种不同的数据类型(unsigned char
, unsigned int
, int
)。但是,所有都返回一个字符串,其中包含传递的值的位。
当您实现 COM 服务器时,您必须注意代码可能抛出的所有异常——让异常通过 COM 方法边界通常会使调用应用程序崩溃。
方法括号对此很有用。有一个左括号是一个包含“try”的宏和一个包含一组“catch”的右括号,将异常包装到 ErrorInfo 并产生 HRESULT。
大多数(全部?)C++ 单元测试框架都是基于宏构建的。我们使用UnitTest++。查看它以查看各种精美的宏。
BOOST_BINARY宏执行一些 clevel 预处理器技巧,使 C++ 能够以二进制形式表示数字常量。但是,它仅限于 0-255。
在微控制器上,通常使用 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
是的,它很粗糙而且不安全。它只在错误被隔离之前应用,所以这个宏没有害处。
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);
我经常用这个。我有一个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;
}