宏定义及相关用法

宏定义及相关用法

欢迎各位补充

目录

一些成熟软件中常用的宏定义:

1,防止一个头文件被重复包含

#ifndef COMDEF_H
#define COMDEF_H

//头文件内容 …
#endif

2,重新定义一些类型,防止由于各种平台和编译器的不同,而产生的类型字节数差异,方便移植。

typedef  unsigned long int  uint32;      	/* Unsigned 32 bit value */

3,得到指定地址上的一个字节或字

#define  MEM_B( x )  ( *( (byte *) (x) ) )
#define  MEM_W( x )  ( *( (word *) (x) ) )

4,求最大值和最小值

#define  MAX( x, y )  ( ((x) > (y)) ? (x) : (y) )
#define  MIN( x, y )  ( ((x) < (y)) ? (x) : (y) )

5,得到一个field在结构体(struct)中的偏移量

#define FPOS( type, field )   ( (dword) &(( type *) 0)-> field )

6, 得到一个结构体中field所占用的字节数

#define FSIZ( type, field ) sizeof( ((type *) 0)->field )

7,按照LSB格式把两个字节转化为一个word

#define  FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )

8,按照LSB格式把一个word转化为两个字节

#define  FLOPW( ray, val ) \
(ray)[0] = ((val) / 256); \
(ray)[1] = ((val) & 0xFF)

9,得到一个变量的地址(word宽度)

#define  B_PTR( var )  ( (byte *) (void *) &(var) )
#define  W_PTR( var )  ( (word *) (void *) &(var) )

10,得到一个字的高位和低位字节

#define  WORD_LO(xxx)  ((byte) ((word)(var) & 255))
#define  WORD_HI(xxx)  ((byte) ((word)(var) >> 8))

11,返回一个比X大的最接近的8的倍数

#define RND8( x )       ((((x) + 7) / 8 ) * 8 )

12,将一个字母转换为大写

#define  UPCASE( ch ) ( ((ch) >= ’a' && (ch) <= ’z') ? ((ch) - 0×20) : (ch) )

13,判断字符是不是10进值的数字

#define  DECCHK( ch ) ((ch) >= ’0′ && (ch) <= ’9′)

14,判断字符是不是16进值的数字

#define  HEXCHK( ch ) \
(((ch) >= ’0′ && (ch) <= ’9′) || \
((ch) >= ’A' && (ch) <= ’F') || \
((ch) >= ’a' && (ch) <= ’f') )

15,防止溢出的一个方法

#define  INC_SAT( val )  (val = ((val)+1 > (val)) ? (val)+1 : (val))

16,返回数组元素的个数

#define  ARR_SIZE( a )  ( sizeof( (a) ) / sizeof( (a[0]) ) )

17,对于IO空间映射在存储空间的结构,输入输出处理

#define inp(port)         (*((volatile byte *) (port)))
#define inpw(port)        (*((volatile word *) (port)))
#define inpdw(port)       (*((volatile dword *)(port)))

#define outp(port, val)   (*((volatile byte *) (port)) = ((byte) (val)))
#define outpw(port, val)  (*((volatile word *) (port)) = ((word) (val)))
#define outpdw(port, val) (*((volatile dword *) (port)) = ((dword) (val)))

18,字符串拼接打印日志
两个相邻字符串会被拼接成一个字符串的技巧。每行都需要使用双引号将字符串引起来再加反斜杠,否则字符串将会把每一行前面的空格包含进去。

#define DEBUG_PRINT printf("File %s line %d :"\
                           "x=%d,y=%d,z=%d",\
                           __FILE__,__LINE,x,y,z)

使用一些内置宏跟踪调试:

ANSI标准定义了几个个预定义的宏名。它们包括但不止于:

__LINE__
__FILE__
__DATE__
__TIME__
__STDC__

注: 常用的还有__FUNCTION__等【非标准】,详细信息可查看: Predefined Macros,如果编译不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译程序 也许还提供其它预定义的宏名。可以定义宏,例如:当定义了_DEBUG,输出数据信息和所在文件所在行

#ifdef _DEBUG
#define DEBUGMSG(msg,date) printf(msg);printf("%d%d%d", date, __LINE__, __FILE__)
#else
#define DEBUGMSG(msg,date)
#endif

宏定义防止使用时错误:

用小括号包含。

//例如:
#define ADD(a,b) ((a)+(b))

用do{}while(0)语句包含多语句防止错误(注意while(0)后无分号).

//例如:
#difne DO(a,b) a+b; a++;
//应写成:
#difne DO(a,b) do{a+b; a++;}while(0)

为什么需要do{…}while(0)形式?大致有以下几个原因:

  • 空的宏定义避免warning:
    #define foo() do{}while(0)

  • 存在一个独立的block,可以用来进行重复性变量定义,进行比较复杂的实现。

  • 如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现:

#define foo(x)
action1();
action2();
//在以下情况下:
if(NULL == pPointer)
    foo();
//就会出现action2必然被执行的情况,而这显然不是程序设计的目的。
  • 以上的第3种情况用单独的{}也可以实现,但是为什么一定要一个do{}while(0)呢,看以下代码:
#define switch(x,y) {int tmp; tmp=x;x=y;y=tmp;}
if(x>y)
   switch(x,y);
else        //error, parse error before else
   otheraction();

在把宏引入代码中,会多出一个分号,从而会报错。使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低,【但是并非所有情况都用这种形式,有些情况不需要,有些情况则不能够够】。


宏与函数

宏相比函数而言的优势主要在于:

  1. 宏因为是文本替换,没有函数栈的维护代价
  2. 宏参数不带类型,可以做函数不能做的工作。

解释一下,第一点 函数调用是利用函数栈帧来实现的,这个需要一定的系统资源。但是宏是直接文本替换,无函数调用过程。
第二点是因为函数的参数列表要求每个参数必须带类型,宏不需要(亦可代替函数重载),当我们想实现把类型也当作参数传入时,函数是无法做到。例如下面的宏:

#define MALLOC(size,type) ((type*)malloc(sizeof(type) * (size)))
int * p = MALLOC(10, int);

这个宏有点厉害了,传入大小和类型两个参数,然后动态分配出所需要的内存。这个肯定是无法用函数实现的。这个是不是与 C++ 的模版有点相像?模版能够实现的技术基础就是能够把参数类型当作一种参数,而宏正好就支持!!!

然而,宏的文本替换大法这么厉害,为什么还会有函数的存在嘞?
最主要的原因是:宏的文本替换大法在处理带副作用的宏参数时会导致不可预料的后果。

带副作用的宏参数

什么叫带副作用的宏参数?副作用是指某些运算符在求值的同时也会永久地改变操作数,比如:i+1

这个表达式求值没有副作用,这个式子不管运算多少次,式子的值不变。但是:
++i

却不一样了,它运算 100 次与运算 1 次的结果是不一样的,因为每次运算都会永久改变 i 的值。

那当副作用遇上宏参数会发生什么呢?看一个经典例子:

#define MAX(a,b) ((a)>(b)?(a):(b))
int a = 0;
int b = 1;
int c = MAX(a++, b++);

根据宏替换大法,我们看c被预编译器处理后的样子:

int c = ((a++)>(b++)?(a++):(b++));

ok,副作用一目了然。因为 a++

int max(a, b)
{
    return a>b?a:b;
}
int a = 0;
int b = 1;
int c = max(a++, b++);

这么执行的结果就是
c = 1,a = 1,b = 2;

这是我们想要的。
上面两者的区别在于,使用函数时,只在参数列表里面进行一次带副作用的运算;使用宏时,在宏的列表,宏的内容都执行了带副作用的运算。而我们通常只希望副作用运算执行一次。


特殊符号:’#’、’##’

1、一般用法

我们使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起.例如:

#include<cstdio>
#include<climits>
using namespace std;

#define STR(s)     #s
#define CONS(a,b)  int(a##e##b)

int main()
{
	// 输出字符串vck
	printf(STR(vck));						

	// 2e3 输出:2000,科学计数法
	printf("%d\n", CONS(2,3));					
	return 0;
}

2、当宏参数是另一个宏的时候

需要注意的是凡宏定义里有用#或##的地方宏参数是不会再展开.

没有’#'和’##’的情况
#define TOW      (2)
#define MUL(a,b) (a*b)
printf("%d * %d = %d\n", TOW, TOW, MUL(TOW,TOW));

这行的宏会被展开为:
printf("%d * %d = %d\n", (2), (2), ((2) * (2)));
MUL 里的参数 TOW 会被展开为(2).

当有’#'或’##’的时候
#define A          (2)
#define STR(s)     #s
#define CONS(a,b)  int(a##e##b)
printf("int max: %s\n",  STR(INT_MAX));    // INT_MAX #include<climits>
printf("%s\n", CONS(A, A));               // compile error

第一个 printf() 这行会被展开为:
printf(“int max: %s\n”, #INT_MAX);

第二个 printf() 则是:
printf("%s\n", int(AeA)); //编译错误

INT_MAX 和 A 都不会再被展开, 然而解决这个问题的方法很简单. 加多一层中间转换宏;加这层宏的用意是把所有宏的参数在这层里全部展开, 那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数.

#define A           (2)
#define _STR(s)     #s
#define STR(s)      _STR(s)                 // 转换宏
#define _CONS(a,b)  int(a##e##b)
#define CONS(a,b)   _CONS(a,b)       		// 转换宏

printf(“int max: %s\n”, STR(INT_MAX));
输出为: int max: 0x7fffffff
STR(INT_MAX) –> _STR(0x7fffffff) 然后再转换成字符串;

printf("%d\n", CONS(A, A));
输出为:200
CONS(A, A) –> _CONS((2), (2)) –> int((2)e(2))
’#'和’##’的一些应用特例:
合并匿名变量名,例:
#define  __ANONYMOUS1(type, var, line)  type  var##line
#define  _ANONYMOUS0(type, line)  __ANONYMOUS1(type, _anonymous, line)
#define  ANONYMOUS(type)  _ANONYMOUS0(type, __LINE__)

ANONYMOUS(static int);
//即:
static int _anonymous70;  	//70表示该行行号;

① 第一层:ANONYMOUS(static int); –> __ANONYMOUS0(static int, __LINE__);
② 第二层:–> ___ANONYMOUS1(static int, _anonymous, 70);
③ 第三层:–> static int _anonymous70;
即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开;

填充结构
#define  FILL(a)   {a, #a}

enum IDD{OPEN, CLOSE};
typedef struct MSG{
	IDD id;
	const char * msg;
}MSG;

MSG _msg[] = {
	FILL(OPEN),
	FILL(CLOSE)
};
//相当于:
MSG _msg[] = {
	{OPEN, “OPEN”},
	{CLOSE, ”CLOSE“}
};
记录文件名
#define  _GET_FILE_NAME(f)   #f
#define  GET_FILE_NAME(f)    _GET_FILE_NAME(f)
static char  FILE_NAME[] = GET_FILE_NAME(__FILE__);
得到一个数值类型所对应的字符串缓冲大小
#define  _TYPE_BUF_SIZE(type)  sizeof #type
#define  TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type)
char buf[TYPE_BUF_SIZE(INT_MAX)];
char buf[_TYPE_BUF_SIZE(“0x7fffffff”)];
char buf[sizeof “0x7fffffff”];
这里相当于:
char buf[11];

__VA_ARGS__与##__VA_ARGS__

\_\_VA_ARGS__ 是一个可变参数的宏,很少人知道这个宏,这个可变参数的宏是新的 C99 规范中新增的,目前似乎只有 gcc 支持(VC6.0的编译器不支持)。实现思想就是宏定义中参数列表的最后一个参数为省略号(也就是三个点)。
##\_\_VA_ARGS__ 宏,在 \_\_VA_ARGS__ 前面加上 ## 的作用在于,当可变参数的个数为0时,这里的 ## 起到把前面多余的逗号去掉的作用,否则会编译出错。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值