1.防止一个头文件被重复包含
#ifndef __HEAD_H_
#define __HEAD_H_
#endif
2.得到指定地址上的一个字节或字
#define MEM_BYTE(x) (*((char *)(x)))
#define MEM_HWORD(x) (*((short *)(x)))
#define MEM_WORD(x) (*((long *)(x)))
3. 得到一个field在结构体(struct)中的偏移量
#define GET_OFFSET_OF_STRUCT(type,field) (&(((type *)0)->field))
这里涉及到结构体中成员边界对齐的问题,相同的结构体在不同的CPU和编译器上会得到不同的结果。
如下面的代码,所得到的偏移是不同的。
测试的环境是: MacBookPro OS 10.12.3 + MacOS gcc.
- sizeof(char)=1,sizeof(short)=2,sizeof(int)=4,sizeof(long)=8,sizeof(float)=4
- sizeof(struct Student)=40=4+4+8+4+4*5
- sizeof(struct StudentPacked)=37=1+4+8+4+4*5
struct Student {
char a; //offset=0
int b; //4
long c; //8
float d; //16
int e[5]; //20
};
struct StudentPacked {
char a; //offset=0
int b; //1
long c; //5
float d; //13
int e[5]; //17
}__attribute__((packed));
4.得到一个结构体中field所占用的字节数
#define GET_FIELD_SIZE(type,field) (sizeof(((type *)0)->field))
5.返回数组元素的个数
#define ARRAY_SIZE(a) (sizeof((a)) / sizeof((a[0])))
6.使用__LINE__
等宏输出日志
printf("%s %s %d %s %s\n",__FILE__,__FUNCTION__,__LINE__,__DATE__,__TIME__);
输出结果为: …/src/UsefulC.c main 58 Feb 23 2017 01:55:08
__LINE__
及__FILE__
宏:指示程序的当前行数和文件名__DATE__
宏指令含有形式为月/日/年的字符串,表示源文件被编译的日期。__TIME__
宏指令包含程序编译的时间。时间用字符串表示,其形式为: 分:秒__STDC__
宏指令的意义是编译时定义的。一般来讲,如果__STDC__
已经定义,编译器将仅接受满足C/C++标准的代码,不包含任何标准的扩展。如果实现是标准的,则宏__STDC__
为1。如果它含有任何其它数,则实现是非标准的。__cplusplus
与标准c++一致的编译器把它定义为一个至少为6位的数值。与标准c++不一致的编译器将使用5位或更少的数值- 如果编译器不是标准的,则可能仅支持以上宏名中的几个,或根本不支持。记住编译器也许还提供其它预定义的宏名
7.宏定义中#
,##
的用法
#
表示把宏定义中的参数转化为字符串, ##
表示把宏定义中的参数拼接起来,看实例:
#define STR(s) #s
#define CONS(a,b) ((int)(a##e##b))
printf(STR(vck)); // 输出"vck"
printf("%d\n", CONS(2,3)); // 输出2000,即2e3=2*10^2
注意上面宏的参数是不能加括号的,就是说不能这样定义:
#define STR(s) #(s)
#define CONS(a,b) ((int)((a)##e##(b)))
深入一点,当宏参数是另一个宏的情况下,宏参数是不会展开的,是不是不知所云,举一个例子,
#define A 2
#define STR(s) #s
#define CONS(a,b) ((int)(a##e##b))
printf(STR(A)); // 输出"A",不是"2"
printf("%d\n", CONS(A,A)); //CONS(A,A)被替换为AeA,而不是2e2
如何避免这个错误呢,很简单,再加一个中间转换宏即可,代码如下,
#define A 2
#define STR(s) _STR(s)
#define CONS(a,b) _CONS(a,b)
#define _STR(s) #s
#define _CONS(a,b) ((int)(a##e##b))
printf(STR(A));
printf("%d\n", CONS(A,A));
定义_STR
这个中间转换宏后,从STR
到_STR
的替换,就实现了A
到2的替换,_CONS
类似。
这里再举二个实际的应用:
7.1 定义匿名变量
#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);
,如果这条语句定义在源码里的第70行,那么该宏相当于如下语句:
static int _anonymous70;
宏展开过程如下:
-
ANONYMOUS(static int) --> __ANONYMOUS0(static int, LINE)
-
__ANONYMOUS0(static int, LINE) --> ___ANONYMOUS1(static int, _anonymous, 70)
-
___ANONYMOUS1(static int, _anonymous, 70) --> static int _anonymous70
这里分三个步骤展开,即每次只能展开当前层的宏,所以__LINE__
在第二步才能被展开。
7.2 记录文件名
#define _GET_FILE_NAME(f) #f
#define GET_FILE_NAME(f) _GET_FILE_NAME(f)
static char FILE_NAME[] = GET_FILE_NAME(__FILE__);
8 利用#error在预处理阶段做提示
#ifdef __cplusplus
#error "you use c++ compiler now, please use c compiler"
#endif
字符串两边的双引号可不加,但是一般会加上,提高可读性。
9 得到一个变量的地址
#define B_PTR(var) ((byte *) (void *) &(var))
#define W_PTR(var) ((word *) (void *) &(var))
10 得到一个字的高位和低位字节
#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))
#define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))
11 返回一个比X大的,最接近的8的倍数
#define ROUND8(x) ((((x) + 7)/8) * 8)
12 将一个字母转换为大写
#define UPCASE(c) (((c)>='a' && (c) <= 'z') ? ((c) – 0x20) : (c))
13 判断字符是不是10进值的数字
#define DECIMAL_CHK(c) ((c)>='0' && (c)<='9')
14 判断字符是不是16进值的数字
#define HEX_CHK(c) (((c) >= '0' && (c)<='9') || ((c)>='A' && (c)<= 'F') || \
((c)>='a' && (c)<='f'))
15 求最大值
#define MAX2(a,b) ((a) > (b) ? (a) : (b))
#define MAX3(a,b,c) ((a) > (b) ? ((a) > (c) ? (a) : (c)) : ((b) > (c) ? (b) : (c)))
16 位操作
#define SET(n,i) ((n) | (1u<<i)) //置n的bit i为1
#define CLEAR(n,i) ((n) & (~(1u<<i))) //清n的bit i
#define TOGGLE(n,i) ((n) ^ (1u<<i)) //bit i取反
#define TEST(n,i) !!((n)&(1u<<i)) //测试bit i是否为1
17 循环移位
//循环左移n位,假设val为无符号数
#define ROTL(val,n) (((val)<<n) | ((val)>>(sizeof(val)*8-n)))
//循环右移n位,假设val为无符号数
#define ROTR(val,n) (((val)>>n) | ((val)<<(sizeof(val)*8-n)))
18 宏实现SWAP交换
#define SWAP(x,y) ((x)==(y) ? NULL:((x)^=(y),(y)^=(x),(x)^=(y)))