c - Linux 内核的 __is_constexpr 宏

标签 c linux-kernel macros language-lawyer

__is_constexpr(x)怎么办Linux内核的宏工作?它的目的是什么?什么时候介绍的?为什么要介绍?

/*
 * 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内核的__is_constexpr

简介
__is_constexpr(x)宏可以在 Linux Kernel 的 include/kernel/kernel.h 中找到:

/*
 * 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) 和空指针常量的定义 (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) Actor 是 intended允许 x具有指针类型并避免在 u64 上出现警告32 位平台上的类型。但是,这个细节对于理解宏的关键点并不重要。

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)不是空指针常量,不管它的值(value) .

知道了这一点,我们可以看到之后会发生什么:
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删除所有 Variable Length Arrays (VLAs)来自 Linux 内核。

为了方便起见,希望启用 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 代码。

关于c - Linux 内核的 __is_constexpr 宏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49481217/

相关文章:

c - 在头文件的声明中定义结构的值

c - 如何确定 _POSIX_PATH_MAX 的系统值

c - 内核导出符号与全局符号与静态全局符号?

javascript - 什么是 JavaScript 宏?

visual-studio - 在预处理器中检测 ARM-64?

c - 如果请求超出可用物理内存,如何使malloc/calloc失败(即,不要使用swap)

c - 信号量和标准输出缓冲区

c - 遍历末尾为零的字符串数组

linux - 不使用 printk 写入 Linux 控制台

c - 将 uint64_t 位转换为 double 并返回到宏中