Linux内核中实现 __is_defined(x)宏的技巧

__is_defined在内核代码中使用场景非常多,是许多宏定义的构成组件,__is_defined具体定义如下,内核版本为6.1.19

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* Getting something that works in C and CPP for an arg that may or may
* not be defined is tricky. Here, if we have "#define CONFIG_BOOGER 1"
* we match on the placeholder define, insert the "0," for arg1 and generate
* the triplet (0, 1, 0). Then the last step cherry picks the 2nd arg (a one).
* When CONFIG_BOOGER is not defined, we generate a (... 1, 0) pair, and when
* the last step cherry picks the 2nd arg, we get a zero.
*/
#define __is_defined(x) ___is_defined(x)
#define ___is_defined(val) ____is_defined(__ARG_PLACEHOLDER_##val)
#define ____is_defined(arg1_or_junk) __take_second_arg(arg1_or_junk 1, 0)
#define __ARG_PLACEHOLDER_1 0,
#define __take_second_arg(__ignored, val, ...) val

代码说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>

// 复制内核的宏定义
#define __take_second_arg(__ignored, val, ...) val
#define ____is_defined(arg1_or_junk) __take_second_arg(arg1_or_junk 1, 0)
#define ___is_defined(val) ____is_defined(__ARG_PLACEHOLDER_##val)
#define __is_defined(x) ___is_defined(x)
#define __ARG_PLACEHOLDER_1 0, //注意这里有一个逗号

// 测试两种情况
#define CONFIG_TEST 1
// #undef CONFIG_TEST2 // 未定义

int main() {
printf("CONFIG_TEST is %s\n",
__is_defined(CONFIG_TEST) ? "defined" : "undefined");

printf("CONFIG_TEST2 is %s\n",
__is_defined(CONFIG_TEST2) ? "defined" : "undefined");
return 0;
}

宏定义展开

  1. 当 CONFIG_BOOGER被定义为 1
1
#define CONFIG_BOOGER 1
1
2
3
4
5
6
7
8
9
10
__is_defined(CONFIG_BOOGER)
// 展开 CONFIG_BOOGER → 1
→ ___is_defined(1)
// 连接:__ARG_PLACEHOLDER_##1 → __ARG_PLACEHOLDER_1
// __ARG_PLACEHOLDER_1 被定义为 0,
→ ____is_defined(0,)
// 展开:0, 1, 0
→ __take_second_arg(0, 1, 0)
// __take_second_arg 返回第二个参数
→ 1
  1. 当 CONFIG_BOOGER被定义为 0或者未定义
1
#define CONFIG_BOOGER 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
__is_defined(CONFIG_BOOGER)
// CONFIG_BOOGER 保持原样(因为未定义,预处理器不替换)
→ ___is_defined(CONFIG_BOOGER)
// 连接:__ARG_PLACEHOLDER_##CONFIG_BOOGER → __ARG_PLACEHOLDER_CONFIG_BOOGER
// 这个宏未定义,所以保持不变
→ ____is_defined(__ARG_PLACEHOLDER_CONFIG_BOOGER)
// 展开:__ARG_PLACEHOLDER_CONFIG_BOOGER 1, 0
// 现在有三个令牌:__ARG_PLACEHOLDER_CONFIG_BOOGER, 1, 0
→ __take_second_arg(__ARG_PLACEHOLDER_CONFIG_BOOGER 1, 0)
// __take_second_arg 接收两个参数:
// 第一个参数是 __ARG_PLACEHOLDER_CONFIG_BOOGER 1 (一个整体)
// 第二个参数是 0
// 返回第二个参数
→ 0

为什么不适用define()?

因为 defined()不能在宏展开中使用:

1
2
3
// 这会导致编译错误
#define MY_CHECK(x) (defined(x) ? 1 : 0)
#if MY_CHECK(CONFIG_FOO) // 错误!

__is_defined()可以在任何地方使用

总结

这个看似复杂的宏实际上是预处理器编程的经典技巧,它通过:

  • 令牌粘贴:##操作符创建新的宏名
  • 参数重排:利用逗号创建不同的参数序列
  • 选择性返回:__take_second_arg挑选想要的参数
  • 占位符技巧:0,改变参数计数