Linux Kernel的__is_constexpr(x)
宏如何工作?它的目的是什么?什么时候介绍?为什么要介绍?
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
* Glory to Martin Uecker <Martin.Uecker@med.uni-goettingen.de>
*/
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
要解决相同问题的讨论不同方法,请参阅:Detecting Integer Constant Expressions in Macros
答案 0 :(得分:15)
__is_constexpr
宏可以在Linux Kernel&#39; s include/kernel/kernel.h中找到__is_constexpr(x)
宏:
/*
* This returns a constant expression while determining if an argument is
* a constant expression, most importantly without evaluating the argument.
* Glory to Martin Uecker <Martin.Uecker@med.uni-goettingen.de>
*/
#define __is_constexpr(x) \
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
它是在Linux Kernel v4.17的合并窗口期间引入的,commit 3c8ba0d61d04是在2018-04-05;虽然围绕它的讨论开始于一个月前。
该宏值得注意的是利用C标准的细微细节:条件运算符确定其返回类型的规则(6.5.15.6)和<的定义< em>空指针常量(6.3.2.3.3)。
此外,它依赖sizeof(void)
被允许(并且不同于sizeof(int)
),这是GNU C extension。
宏观主体是:
(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))
让我们关注这一部分:
((void *)((long)(x) * 0l))
注意:(long)(x)
强制转换为intended,以允许x
具有指针类型并避免在32位u64
类型上发出警告平台。但是,这个细节对于理解宏的关键点并不重要。
如果x
整数常量表达式(6.6.6),那么((long)(x) * 0l)
是整数常量值0
的表达式。因此,(void *)((long)(x) * 0l)
是空指针常量(6.3.2.3.3):
值为0的整型常量表达式,或者类型为 void * 的表达式,称为空指针常量
如果x
不 整数常量表达式,则(void *)((long)(x) * 0l)
不是空指针常量, 无论其价值如何。
知道了,我们可以看到之后会发生什么:
8 ? ((void *)((long)(x) * 0l)) : (int *)8
注意:第二个8
文字是intended,以避免编译器警告创建指向未对齐地址的指针。第一个8
字面值可能只是1
。但是,这些细节对于理解宏的关键点并不重要。
这里的关键是条件运算符返回不同类型,具体取决于其中一个操作数是空指针常量(6.5 .15.6):
[...]如果一个操作数是空指针常量,则结果具有另一个操作数的类型;否则,一个操作数是 void 的指针或 void 的限定版本,在这种情况下,结果类型是指向 void <的适当限定版本的指针/强>
因此,如果x
整数常量表达式,那么第二个操作数是空指针常量,因此表达式的类型是第三个操作数的类型,它是指向int
的指针。
否则,第二个操作数是指向void
的指针,因此表达式的类型是指向void
的指针。
因此,我们最终有两种可能性:
sizeof(int) == sizeof(*((int *) (NULL))) // if `x` was an integer constant expression
sizeof(int) == sizeof(*((void *)(....))) // otherwise
根据GNU C extension,sizeof(void) == 1
。因此,如果x
是整数常量表达式,则宏的结果为1
;否则,0
。
此外,由于我们只是比较两个sizeof
表达式的相等性,结果本身是另一个整数常量表达式(6.6.3,6.6.6):
常量表达式不应包含赋值,递增,递减,函数调用或逗号运算符,除非它们包含在未评估的子表达式中。
整型常量表达式应具有整数类型,并且只能具有整数常量的操作数,枚举常量,字符常量,结果为整数常量的 sizeof 表达式,以及浮动常量,是强制转换的直接操作数。整数常量表达式中的转换运算符只能将算术类型转换为整数类型,除非作为 sizeof 运算符的操作数的一部分。
因此,总而言之,如果参数是整数常量表达式,__is_constexpr(x)
宏将返回值为1
的整数常量表达式。否则,它返回值为0
的整数常量表达式。
宏来自during the effort以从Linux内核中删除所有Variable Length Arrays (VLAs)。
为了方便起见,最好在内核范围内启用GCC's -Wvla
warning;以便编译器标记所有VLA实例。
当启用警告时,结果发现GCC报告了许多阵列为VLA的情况,而这些情况并非如此。例如在fs/btrfs/tree-checker.c中:
#define BTRFS_NAME_LEN 255
#define XATTR_NAME_MAX 255
char namebuf[max(BTRFS_NAME_LEN, XATTR_NAME_MAX)];
开发人员可能希望max(BTRFS_NAME_LEN, XATTR_NAME_MAX)
已解析为255
,因此应将其视为标准数组(即非VLA)。但是,这取决于max(x, y)
宏扩展到的内容。
关键问题是,如果数组的大小不是C标准定义的(整数)常量表达式,GCC会生成VLA代码。例如:
#define not_really_constexpr ((void)0, 100)
int a[not_really_constexpr];
根据C90标准,由于逗号运算符,((void)0, 100)
不 常量表达式(6.6)使用(6.6.3)。在这种情况下,GCC选择发布VLA代码even when it knows the size is a compile-time constant。相比之下,Clang没有。
由于内核中的max(x, y)
宏不是常量表达式,因此GCC触发了警告并生成了内核开发人员不想要的VLA代码。
因此,一些内核开发人员尝试开发max
和其他宏的替代版本以避免警告和VLA代码。有些尝试试图利用GCC's __builtin_constant_p
builtin,但没有一种方法适用于当时内核支持的所有GCC版本(gcc >= 4.4
)。
在某些时候,Martin Uecker proposed一个特别聪明的方法,没有使用内置(taking inspiration来自glibc's tgmath.h):
#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))
虽然该方法使用了GCC扩展,it was nevertheless well-received并且被用作__is_constexpr(x)
宏背后的关键思想,它在与其他开发人员进行几次迭代后出现在内核中。然后使用该宏来实现max
宏和其他需要为常量表达式的宏,以避免GCC生成VLA代码。