0%

C语言 宏的高级用法

字符串转换符

使用#运算符可以将宏参数替换为一个字符串,并用双引号括起来,例如:

1
2
3
4
5
6
#define PRINT_STR(x) printf("The string is: %s\n", #x)

int main() {
PRINT_STR(Hello World);
return 0;
}

在这个示例程序中,定义了一个宏PRINT_STR,它的参数x通过#运算符被转换为一个字符串,并被传递给printf函数进行输出。运行这个程序会输出:The string is: Hello World

字符串拼接

在C语言中,宏定义可以使用##运算符进行字符串拼接,称为连接运算符(Token Pasting Operator)。##运算符可以将两个标记(Token)连接成一个标记,从而实现字符串拼接的功能。例如:

1
2
3
4
5
6
7
#define CONCAT(x, y) x##y

int main() {
int xy = 10;
printf("%d\n", CONCAT(x, y));
return 0;
}

在这个示例程序中,定义了一个宏CONCAT,它使用##运算符将两个参数x和y连接成一个标记。在main函数中定义了一个变量xy,并将连接后的标记xy作为参数传递给printf函数进行输出。输出结果是:10

变长参数宏

C语言中的变长参数宏(Variadic Macro)可以接受可变数量的参数。变长参数宏是通过使用特殊的预处理符号__VA_ARGS__来实现的,它表示可变参数的列表。下面是一个简单的示例,展示了如何使用变长参数宏:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

#define PRINTF(...) printf(__VA_ARGS__)

int main() {
int x = 10;
char* str = "hello";
PRINTF("x = %d, str = %s\n", x, str);
return 0;
}

在这个示例程序中,定义了一个宏PRINTF,它使用printf函数打印可变数量的参数。使用__VA_ARGS__表示可变参数的列表。在main函数中,PRINTF宏被调用,传递了三个参数,包括一个整数和一个字符串。输出结果是:x = 10, str = hello

宏:取最大值

一种方式:#define MAX(a, b) ((a) > (b) ? (a) : (b))
这种方式需要注意参数如果有自增运算,需要在传参的时候加括号,否则自增运算会重复两次。
正确调用方法如:z = MAX((x++), (y++));

使用下面一种方式可以避免此类问题

1
2
3
4
5
6
#define MAX(a, b) ({ \
typeof(a) _a = (a); \
typeof(b) _b = (b); \
(void)(&_a == &_b); \
_a > _b ? _a : _b; \
})

typeof是GNU C编译器内置的一种类型描述符,用于表示表达式的类型。在编译时,编译器会根据typeof(x)对象的类型生成一个类型的值,并将其插入到代码中。

比较难理解的是(void)(&_a == &_b);,看起来很多余,仔细分析一下,你会发现这条语句很有意思。它的作用有两个:

  1. 用来给用户提示一个警告,对于不同类型的指针比较,编译器会发出一个警告, 提示两种数据的类型不同。warning: comparison of distinct pointer types lacks a cast.
  2. 两个数进行比较运算,运算的结果却没有用到,有些编译器可能会给出一个warning,加一个(void)后,就可以消除这个警告。

宏:offsetof

offsetof是一个宏,用于获取结构体中成员的偏移量。实现是使用指针运算来计算结构体成员的偏移量。具体实现:

1
#define offsetof(type, member) ((size_t) &((type *)0)->member)

其中,type表示结构体类型,member表示结构体成员。在宏展开时,(type *)0将一个空指针强制转换为结构体指针类型,然后通过指针运算获取成员的地址。由于空指针的地址为0,因此可以确保这个地址不会指向任何实际的内存位置,避免了访问非法内存的风险。最后,将成员的地址转换为size_t类型的偏移量,并返回。

需要注意的是,offsetof宏只能用于标准布局的结构体,即结构体中的成员按照其定义顺序依次存储,没有嵌套、位域、虚函数等。对于非标准布局的结构体,offsetof可能无法正确计算成员的偏移量。

宏:container_of

container_of是一个宏,用于从结构体的成员指针计算出结构体的地址。其实现通常基于offsetof宏和指针运算。实现如下:

1
2
#define container_of(ptr, type, member) \
((type *)((char *)(ptr) - offsetof(type, member)))

其中,ptr表示结构体成员的指针,type表示结构体类型,member表示结构体成员名。offsetof(type, member)用于计算结构体成员在结构体中的偏移量,(char *)(ptr)将成员指针转换为char类型指针,以便进行指针运算。通过成员指针的地址减去成员在结构体中的偏移量,可以得到结构体的地址。最后,将地址转换为type类型的指针,并返回。

container_of宏常用于实现Linux内核中的数据结构,如链表、哈希表等。它可以方便地从链表节点或哈希桶中获取对应的数据结构。