社区应用 最新帖子 精华区 社区服务 会员列表 统计排行 银行

  • 9681阅读
  • 1回复

C++宏定义高级技巧

级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
— 本帖被 usidc5 设置为精华(2010-07-29) —
1、防止一个头文件被重复包含
  1.   #ifndef COMDEF_H
  2.   #define COMDEF_H
  3.   //头文件内容
  4.   #endif
复制代码
2、重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。
  1.   typedef unsigned char boolean; /* Boolean value type. */
  2.    typedef unsigned long int uint32; /* Unsigned 32 bit value */
  3.    typedef unsigned short uint16; /* Unsigned 16 bit value */
  4.    typedef unsigned char uint8; /* Unsigned 8 bit value */
  5.    typedef signed long int int32; /* Signed 32 bit value */
  6.    typedef signed short int16; /* Signed 16 bit value */
  7.    typedef signed char int8; /* Signed 8 bit value */
  8.   //下面的不建议使用
  9.   typedef unsigned char byte; /* Unsigned 8 bit value type. */
  10.    typedef unsigned short word; /* Unsinged 16 bit value type. */
  11.    typedef unsigned long dword; /* Unsigned 32 bit value type. */
  12.    typedef unsigned char uint1; /* Unsigned 8 bit value type. */
  13.    typedef unsigned short uint2; /* Unsigned 16 bit value type. */
  14.    typedef unsigned long uint4; /* Unsigned 32 bit value type. */
  15.    typedef signed char int1; /* Signed 8 bit value type. */
  16.    typedef signed short int2; /* Signed 16 bit value type. */
  17.    typedef long int int4; /* Signed 32 bit value type. */
  18.    typedef signed long sint31; /* Signed 32 bit value */
  19.    typedef signed short sint15; /* Signed 16 bit value */
  20.    typedef signed char sint7; /* Signed 8 bit value */
复制代码
3、得到指定地址上的一个字节或字
  1.   #define MEM_B( x ) ( *( (byte *) (x) ) )
  2.    #define MEM_W( x ) ( *( (word *) (x) ) )
复制代码
4、求最大值和最小值
  1.    #define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) )
  2.    #define MIN( x, y ) ( ((x) field ) /*lint +e545 */
复制代码
6、得到一个结构体中field 所占用的字节数
  1.    #define FSIZ( type, field ) sizeof( ((type *) 0)->field )
复制代码
7、按照 LSB格式把两个字节转化为一个Word
  1.    #define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )
复制代码
8、按照 LSB格式把一个Word转化为两个字节
  1.   #define FLOPW( ray, val ) \
  2.    (ray)[0] = ((val) / 256); \
  3.   (ray)[1] = ((val) & 0xFF)
复制代码
9、得到一个变量的地址(word宽度)
  1.    #define B_PTR( var ) ( (byte *) (void *) &(var) )
  2.    #define W_PTR( var ) ( (word *) (void *) &(var) )
复制代码
10、得到一个字的高位和低位字节
  1.   #define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
  2.    #define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
复制代码
11、返回一个比X大的最接近的8的倍数
  1.   #define RND8( x ) ((((x) + 7) / 8 ) * 8 )
复制代码
12、将一个字母转换为大写
  1.    #define UPCASE( c ) ( ((c) >= ’a’ && (c) = ’0’ && (c) = ’0’ && (c) = ’A’ && (c) = ’a’ && (c) (val)) ? (val)+1 : (val))
复制代码
16、返回数组元素的个数
  1.    #define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )
复制代码
17、返回一个无符号数n尾的值MOD_BY_POWER_OF_TWO(X,n)=X%(2^n)
  1.    #define MOD_BY_POWER_OF_TWO( val, mod_by ) \
  2.    ( (dword)(val) & (dword)((mod_by)-1) )
复制代码
18、对于IO空间映射在存储空间的结构,输入输出处理
  1.   #define inp(port) (*((volatile byte *) (port)))
  2.    #define inpw(port) (*((volatile word *) (port)))
  3.    #define inpdw(port) (*((volatile dword *)(port)))
  4.    #define outp(port, val) (*((volatile byte *) (port)) = ((byte) (val)))
  5.   #define outpw(port, val) (*((volatile word *) (port)) = ((word) (val)))
  6.    #define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val)))
复制代码
19、使用一些宏跟踪调试
  ANSI标准说明了五个预定义的宏名。它们是:
  __LINE__
   __FILE__
  __DATE__
  __TIME__
  __STDC__
  C++中还定义了 __cplusplus
  如果编译器不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序也许还提供其它预定义的宏名。
  __LINE__ 及 __FILE__ 宏指示,#line指令可以改变它的值,简单的讲,编译时,它们包含程序的当前行数和文件名。
  __DATE__ 宏指令含有形式为月/日/年的串,表示源文件被翻译到代码时的日期。
  __TIME__ 宏指令包含程序编译的时间。时间用字符串表示,其形式为: 分:秒
  __STDC__ 宏指令的意义是编译时定义的。一般来讲,如果__STDC__已经定义,编译器将仅接受不包含任何非标准扩展的标准C/C++代码。如果实现是标准的,则宏__STDC__含有十进制常量1。如果它含有任何其它数,则实现是非标准的。
  __cplusplus 与标准c++一致的编译器把它定义为一个包含至少6为的数值。与标准c++不一致的编译器将使用具有5位或更少的数值。
  可以定义宏,例如:
  当定义了_DEBUG,输出数据信息和所在文件所在行
  1.    #ifdef _DEBUG
  2.    #define DEBUGMSG(msg,date) printf(msg);printf(“%d%d%d”,date,_LINE_,_FILE_)
  3.   #else
  4.   #define DEBUGMSG(msg,date) 
  5.   #endif
复制代码
20、宏定义防止错误使用小括号包含。
  例如:
  有问题的定义:#define DUMP_WRITE(addr,nr) {memcpy(bufp,addr,nr); bufp += nr;}
  应该使用的定义: #difne DO(a,b) do{a+b;a++;}while(0)
  例如:
  1.   if(addr)
  2.   DUMP_WRITE(addr,nr);
  3.   else 
  4.   do_somethong_else();
复制代码
宏展开以后变成这样:
  1.   if(addr)
  2.   {memcpy(bufp,addr,nr); bufp += nr;};
  3.    else
  4.   do_something_else();
  5.   gcc
复制代码
在碰到else前面的“;”时就认为if语句已经结束,因而后面的else不在if语句中。而采用do{}
  while(0)的定义,在任何情况下都没有问题。而改为 #difne DO(a,b) do{a+b;a++;}while(0)
  的定义则在任何情况下都不会出错。
关键词: C++ 编程
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
级别: 管理员
发帖
8532
金币
2762
威望
3231
贡献值
0
元宝
0
只看该作者 沙发  发表于: 2014-07-04
C语言宏定义##连接符和#符的用法和高级技巧
C语言中如何使用宏C(和C++)中的宏(Macro)属于编译器预处理的范畴,属于编译期概念(而非运行期概念)。下面对常遇到的宏的使用问题做了简单总结。
关于#和##
在C语言的宏中,#的功能是将其后面的宏参数进行字符串化操作(Stringfication),简单说就是在对它所引用的宏变量 通过替换后在其左右各加上一个双引号。比如下面代码中的宏:
[pre]
#define WARN_IF(EXP)    do{ if (EXP)    fprintf(stderr, "Warning: " #EXP "/n"); }   while(0)
[/pre]那么实际使用中会出现下面所示的替换过程:
[pre]WARN_IF (divider == 0);
被替换为
do {
if (divider == 0)
fprintf(stderr, "Warning" "divider == 0" "/n");
} while(0);
[/pre]这样每次divider(除数)为0的时候便会在标准错误流上输出一个提示信息。
而##被称为连接符(concatenator),用来将两个Token连接为一个Token。注意这里连接的对象是Token就行,而不一定 是宏的变量。比如你要做一个菜单项命令名和函数指针组成的结构体的数组,并且希望在函数名和菜单项命令名之间有直观的、名字上的关系。那么下面的代码就非常实用:
[pre]struct command
{
char * name;
void (*function) (void);
};
#define COMMAND(NAME) { NAME, NAME ## _command }
// 然后你就用一些预先定义好的命令来方便的初始化一个command结构的数组了:
struct command commands[] = {
COMMAND(quit),
COMMAND(help),
...
}
[/pre]COMMAND宏在这里充当一个代码生成器的作用,这样可以在一定程度上减少代码密度,间接地也可以减少不留心所造成的错误。我们还可以n个##符号连接 n+1个Token,这个特性也是#符号所不具备的。比如:
[pre]#define LINK_MULTIPLE(a,b,c,d) a##_##b##_##c##_##d
typedef struct _record_type LINK_MULTIPLE(name,company,position,salary);
// 这里这个语句将展开为:
//     typedef struct _record_type name_company_position_salary;
[/pre]关于...的使用
...在C宏中称为Variadic Macro,也就是变参宏。比如:
[pre]#define myprintf(templt,...) fprintf(stderr,templt,__VA_ARGS__)
// 或者
#define myprintf(templt,args...) fprintf(stderr,templt,args)
[/pre]第一个宏中由于没有对变参起名,我们用默认的宏__VA_ARGS__来替代它。第二个宏 中,我们显式地命名变参为args,那么我们在宏定义中就可以用args来代指变参了。同C语言的stdcall一样,变参必须作为参数表的最有一项出 现。当上面的宏中我们只能提供第一个参数templt时,C标准要求我们必须写成:
[pre]myprintf(templt,);
[/pre]的形式。这时的替换过程为:
[pre]myprintf("Error!/n",);
替换为:
fprintf(stderr,"Error!/n",);
[/pre]这是一个语法错误,不能正常编译。这个问题一般有两个解决方法。首先,GNU CPP提供的解决方法允许上面的宏调用写成:
[pre]myprintf(templt);
[/pre]而它将会被通过替换变成:
[pre]fprintf(stderr,"Error!/n",);
[/pre]很明显,这里仍然会产生编译错误(非本例的某些情况下不会产生编译错误)。除了这种方式外,c99和GNU CPP都支持下面的宏定义方式:
[pre]#define myprintf(templt, ...) fprintf(stderr,templt, ##__VAR_ARGS__)
[/pre]这时,##这个连接符号充当的作用就是当__VAR_ARGS__为空的时候,消除前面的那个逗号。那么此时的翻译过程如下:
[pre]myprintf(templt);
被转化为:
fprintf(stderr,templt);
[/pre]这样如果templt合法,将不会产生编译错误。 这里列出了一些宏使用中容易出错的地方,以及合适的使用方式。
错误的嵌套-Misnesting
宏的定义不一定要有完整的、配对的括号,但是为了避免出错并且提高可读性,最好避免这样使用。
由操作符优先级引起的问题-Operator Precedence Problem
由于宏只是简单的替换,宏的参数如果是复合结构,那么通过替换之后可能由于各个参数之间的操作符优先级高于单个参数内部各部分之间相互作用的操作符优先级,如果我们不用括号保护各个宏参数,可能会产生预想不到的情形。比如:
[pre]#define ceil_div(x, y) (x + y - 1) / y
[/pre]那么
[pre]a = ceil_div( b & c, sizeof(int) );
[/pre]将被转化为:
[pre]a = ( b & c  + sizeof(int) - 1) / sizeof(int);
// 由于+/-的优先级高于&的优先级,那么上面式子等同于:
a = ( b & (c + sizeof(int) - 1)) / sizeof(int);
[/pre]这显然不是调用者的初衷。为了避免这种情况发生,应当多写几个括号:
[pre]#define ceil_div(x, y) (((x) + (y) - 1) / (y))
[/pre]消除多余的分号-Semicolon Swallowing
通常情况下,为了使函数模样的宏在表面上看起来像一个通常的C语言调用一样,通常情况下我们在宏的后面加上一个分号,比如下面的带参宏:
[pre]MY_MACRO(x);
[/pre]但是如果是下面的情况:
[pre]#define MY_MACRO(x) {    /* line 1 */    /* line 2 */    /* line 3 */ }
//...
if (condition())
MY_MACRO(a);
else
{...}
[/pre]这样会由于多出的那个分号产生编译错误。为了避免这种情况出现同时保持MY_MACRO(x);的这种写法,我们需要把宏定义为这种形式:
[pre]#define MY_MACRO(x) do {
/* line 1 */    /* line 2 */    /* line 3 */ } while(0)
[/pre]这样只要保证总是使用分号,就不会有任何问题。
Duplication of Side Effects
这里的Side Effect是指宏在展开的时候对其参数可能进行多次Evaluation(也就是取值),但是如果这个宏参数是一个函数,那么就有可能被调用多次从而达到不一致的结果,甚至会发生更严重的错误。比如:
[pre]#define min(X,Y) ((X) > (Y) ? (Y) : (X))
//...
c = min(a,foo(b));
[/pre]这时foo()函数就被调用了两次。为了解决这个潜在的问题,我们应当这样写min(X,Y)这个宏:
[pre]#define min(X,Y) ({    typeof (X) x_ = (X);    typeof (Y) y_ = (Y);    (x_ < y_) ? x_ : y_; })
[/pre]({...})的作用是将内部的几条语句中最后一条的值返回,它也允许在内部声明变量(因为它通过大括号组成了一个局部Scope)。
QQ: 378890364 微信:wwtree(省短信费) 紧急事宜发短信到0061432027638  本站微博:http://t.qq.com/wwtree QQ群:122538123
描述
快速回复

您目前还是游客,请 登录注册
如果您在写长篇帖子又不马上发表,建议存为草稿