字符串转换符
使用#
运算符可以将宏参数替换为一个字符串,并用双引号括起来,例如:
1 |
|
在这个示例程序中,定义了一个宏PRINT_STR,它的参数x通过#运算符被转换为一个字符串,并被传递给printf函数进行输出。运行这个程序会输出:The string is: Hello World
字符串拼接
在C语言中,宏定义可以使用##运算符进行字符串拼接,称为连接运算符(Token Pasting Operator)。##
运算符可以将两个标记(Token)连接成一个标记,从而实现字符串拼接的功能。例如:
1 |
|
在这个示例程序中,定义了一个宏CONCAT,它使用##
运算符将两个参数x和y连接成一个标记。在main函数中定义了一个变量xy,并将连接后的标记xy作为参数传递给printf函数进行输出。输出结果是:10
变长参数宏
C语言中的变长参数宏(Variadic Macro)可以接受可变数量的参数。变长参数宏是通过使用特殊的预处理符号__VA_ARGS__
来实现的,它表示可变参数的列表。下面是一个简单的示例,展示了如何使用变长参数宏:
1 |
|
在这个示例程序中,定义了一个宏PRINTF,它使用printf函数打印可变数量的参数。使用__VA_ARGS__
表示可变参数的列表。在main函数中,PRINTF宏被调用,传递了三个参数,包括一个整数和一个字符串。输出结果是:x = 10, str = hello
宏:取最大值
一种方式:#define MAX(a, b) ((a) > (b) ? (a) : (b))
这种方式需要注意参数如果有自增运算,需要在传参的时候加括号,否则自增运算会重复两次。
正确调用方法如:z = MAX((x++), (y++));
使用下面一种方式可以避免此类问题
1 |
typeof
是GNU C编译器内置的一种类型描述符,用于表示表达式的类型。在编译时,编译器会根据typeof(x)
对象的类型生成一个类型的值,并将其插入到代码中。
比较难理解的是(void)(&_a == &_b);
,看起来很多余,仔细分析一下,你会发现这条语句很有意思。它的作用有两个:
- 用来给用户提示一个警告,对于不同类型的指针比较,编译器会发出一个警告, 提示两种数据的类型不同。
warning: comparison of distinct pointer types lacks a cast.
- 两个数进行比较运算,运算的结果却没有用到,有些编译器可能会给出一个warning,加一个
(void)
后,就可以消除这个警告。
宏:offsetof
offsetof
是一个宏,用于获取结构体中成员的偏移量。实现是使用指针运算来计算结构体成员的偏移量。具体实现:
1 |
其中,type表示结构体类型,member表示结构体成员。在宏展开时,(type *)0将一个空指针强制转换为结构体指针类型,然后通过指针运算获取成员的地址。由于空指针的地址为0,因此可以确保这个地址不会指向任何实际的内存位置,避免了访问非法内存的风险。最后,将成员的地址转换为size_t类型的偏移量,并返回。
需要注意的是,offsetof宏只能用于标准布局的结构体,即结构体中的成员按照其定义顺序依次存储,没有嵌套、位域、虚函数等。对于非标准布局的结构体,offsetof可能无法正确计算成员的偏移量。
宏:container_of
container_of
是一个宏,用于从结构体的成员指针计算出结构体的地址。其实现通常基于offsetof
宏和指针运算。实现如下:
1 |
其中,ptr表示结构体成员的指针,type表示结构体类型,member表示结构体成员名。offsetof(type, member)用于计算结构体成员在结构体中的偏移量,(char *)(ptr)将成员指针转换为char类型指针,以便进行指针运算。通过成员指针的地址减去成员在结构体中的偏移量,可以得到结构体的地址。最后,将地址转换为type类型的指针,并返回。
container_of宏常用于实现Linux内核中的数据结构,如链表、哈希表等。它可以方便地从链表节点或哈希桶中获取对应的数据结构。