/* * 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
解决方法
介绍
可以在Linux Kernel的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)))
它是在2018-04-05的Linux Kernel v4.17,commit 3c8ba0d61d04的合并窗口中引入的;虽然围绕它的讨论开始于一个月前.
该宏值得注意的是利用C标准的细微细节:条件运算符确定其返回类型的规则(6.5.15.6)和空指针常量的定义(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):
An integer constant expression with the value 0,or such an expression cast to type void *,is called a null pointer constant
如果x不是整数常量表达式,则(void *)((long)(x)* 0l)不是空指针常量,无论其值如何.
知道了,我们可以看到之后会发生什么:
8 ? ((void *)((long)(x) * 0l)) : (int *)8
注意:第二个8字面是intended,以避免编译器警告有关创建指向未对齐地址的指针.前8个文字可能只是1.但是,这些细节对于理解宏的关键点并不重要.
这里的关键是条件运算符返回一个不同的类型,具体取决于其中一个操作数是否为空指针常量(6.5.15.6):
[…] if one operand is a null pointer constant,the result has the type of the other operand; otherwise,one operand is a pointer to void or a qualified version of void,in which case the result type is a pointer to an appropriately qualified version of 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):
Constant expressions shall not contain assignment,increment,decrement,function-call,or comma operators,except when they are contained within a subexpression that is not evaluated.
An integer constant expression shall have integer type and shall only have operands that are integer constants,enumeration constants,character constants,sizeof expressions whose results are integer constants,and floating constants that are the immediate operands of casts. Cast operators in an integer constant expression shall only convert arithmetic types to integer types,except as part of an operand to the sizeof operator.
因此,总之,如果参数是整数常量表达式,__ 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一个特别聪明的方法,没有使用builtins(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代码.