Linux Kernel的__is_constexpr宏

时间:2018-03-25 21:40:04

标签: c linux-kernel macros language-lawyer

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

1 个答案:

答案 0 :(得分:15)

Linux Kernel的__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 extensionsizeof(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代码。