今天整理了一下C语言一些奇奇怪怪的宏,给大家瞧一瞧。
TO_STR 添加双引号
通过在一个标识符前添加 # 号,可以给这个标识符加上双引号。
1 |
|
1 | hello world |
TO_CHAR 添加单引号
1 |
|
1 | a |
a
并不是变量,啥也不是,只是通过预处理器给它套了一个单引号,它就变成字符了。
PRIMITIVE_CAT 字符拼接
符号 ## 可以拼接两个标识符。
至于为什么这里给它取名叫PRIMITIVE_CAT宏,是因为宏扩展的一个机制,往下看就知道了。
1 |
|
1 | age: 10 |
CAT 字符拼接
可能你会感到奇怪,这不就是套了一层皮吗,有啥区别呢?
1 |
|
1 | 10 |
但是如果把上面的 CAT
替换成 PRIMITIVE_CAT
,看看会发生啥?
1 |
|
1 | 找不到变量 Xge |
报错了
预处理器直接把 X
拿来拼接了,并没有把 X
替换成 a
.
那原因是什么呢?
首先宏函数展开有两种情况:
- 参数参与了 ## 或者 # 符号的操作
此时,由于PRIMITIVE_CAT
宏函数中的参数X Y
参与了## 拼接,那么就不会对宏X Y
进行展开后传入,而是直接传入。所以拼接结果是1
2
3
4
5
printf("%d\n", PRIMITIVE_CAT(X,Y));PRIMITIVE_CAT(X,Y) -> XY -> 2
.
拼接后得到了宏XY
,直接展开成2
.1
2
- 参数没有参与 ## 或者 # 符号的操作
此时,CAT
宏函数的参数并没有参与拼接,则先对参数X Y
进行展开,分别展开为0 1
,然后传入宏函数。所以拼接结果是1
2
3
4
printf("%d\n", CAT(X,Y));CAT(X,Y) -> PRIMITIVE_CAT(0,1) -> 01
可以看出这两个拼接宏函数,都是很常用的。1
1
OFFSET_OF 得到一个字段在结构体struct中的偏移量(字节数)
1 |
|
1 | 24 |
在我的电脑上,int
类型是4
字节的,char
类型是1
字节的。所以字段 age
排在 name
和 score
的后面,他到结构体首地址偏移就是 24
字节。
什么?你要问这个宏函数原理是啥?
这个很简单的,首先上面那个宏函数展开的结果是这个:
1 | (size_t) &((struct Person *)0)->age |
他把地址为0
,强转为一个struct Person *
类型,所以它变成了一个指针,然后就可以访问结构体中的 age
字段了,再然后通过取地址符 &
得到age
字段的地址,很显然,因为结构体首地址是0
,所以age
字段的地址就是偏移量啦。
FILED_SIZE 得到一个结构体中字段的大小(字节数)
1 |
|
1 | 4 |
在我的电脑上int
类型4
个字节.
CONTAINER_OF 根据结构体变量字段地址获取整个结构体的存储空间首地址
ptr
:结构体字段的指针type
:结构体类型filed
: 字段名称1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{
struct Person p = {
.name = "dog",
.score = 30,
.age = 2332432
};
struct Person *pp = CONTAINER_OF(&p.age, struct Person, age);
printf("name: %s\n", pp->name);
}虽然这个样例看起来多此一举,但是这个宏函数在操作系统底层用是很常用的。1
dog
可以用于C的面向对象设计,类的继承,从父类访问子类。
原理是啥?
其实只要理解了C语言的内存布局,这个就不是问题。
UPPERCASE 字母大小写转换
1 |
|
1 | Uppercase: x -> X |
ARRAY_SIZE 获取一个数组元素的个数
1 |
|
1 | p_arr size: 10 |
LOG 打印日志
1 |
|
1 | [FILE: D:\xx.c] [FUNCTION: main] [LINE: 72] 一次debug测试 |
FOR_EACH 函数宏递归实现For循环
count
: 循环次数,我的这个实现最多只能循环三次,不过你可以继续增加marco
: 相当于回调宏idx
: 当前枚举下标的,顺序是count, count - 1, count - 2, ..., 2, 1
, 倒序下标x
:这个是枚举的值
...
:可变参数,枚举的值宏展开后真实的样子是:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
int main()
{
// IF_ELSE (0) ( \
// printf("YES\n"); \
// )( \
// printf("NO\n"); \
// )
FOR_EACH(3, INT_DEFINES, dog, cat, pig);
FOR_EACH(3, PRINT, dog, cat, pig);
return 0;
}1
2int a_dog = 3; int a_cat = 2; int a_pig = 1;
printf("dog"": %d idx: %d\n", a_dog, 3); printf("cat"": %d idx: %d\n", a_cat, 2); printf("pig"": %d idx: %d\n", a_pig, 1);不得不说,宏的代码可读性确实太差了,哈哈哈!1
2
3dog: 3 idx: 3
cat: 2 idx: 2
pig: 1 idx: 1注意!!! 我用
MSVC
编译上述FOR_EACH
宏代码出现了奇怪的问题:x86
的Microsoft (R) C/C++
优化编译器19.39.33521
版。
但是在GNU C
,Clang
上没有问题,大家也可以自己实验一下。