Linux 内核的 __is_constexpr 宏 [英] Linux Kernel's __is_constexpr Macro

查看:16
本文介绍了Linux 内核的 __is_constexpr 宏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

/** 这将返回一个常量表达式,同时确定一个参数是否为* 一个常量表达式,最重要的是不评估参数.* 荣耀归于 Martin Uecker <Martin.Uecker@med.uni-goettingen.de>*/#define __is_constexpr(x) (sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

<小时>

讨论解决同一问题的不同方法,请参阅:检测宏中的整数常量表达式

解决方案

Linux Kernel 的 __is_constexpr

简介

__is_constexpr(x) 宏可以在 Linux Kernel 的 include/kernel/kernel.h:

/** 这将返回一个常量表达式,同时确定一个参数是否为* 一个常量表达式,最重要的是不评估参数.* 荣耀归于 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 的合并窗口中引入的,在 2018 年 4 月 5 日提交 3c8ba0d61d04;尽管围绕它的讨论是在一个月前开始的.

该宏以利用 C 标准的微妙细节而著称:条件运算符确定其返回类型的规则 (6.5.15.6) 和 null 的定义指针常量 (6.3.2.3.3).

此外,它依赖于 sizeof(void) 被允许(并且不同于 sizeof(int)),这是一个 GNU C 扩展.

<小时>

它是如何工作的?

宏的主体是:

(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

让我们专注于这一部分:

((void *)((long)(x) * 0l))

注意:(long)(x) 演员是 打算允许 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 文字是 旨在 避免编译器发出有关创建指向未对齐地址的指针的警告.第一个 8 文字可以简单地为 1.但是,这些细节对于理解宏的要点并不重要.

这里的关键是条件运算符返回一个不同类型,这取决于操作数之一是否为空指针常量(6.5.15.6):

<块引用>

[...] 如果一个操作数是空指针常量,则结果具有另一个操作数的类型;否则,一个操作数是指向 voidvoid 的限定版本的指针,在这种情况下,结果类型是指向 void<的适当限定版本的指针./strong>.

所以,如果x 一个整数常量表达式,那么第二个操作数是一个空指针常量因此表达式的类型是第三个操作数的类型,它是一个指向 int 的指针.

否则,第二个操作数是一个指向void的指针,因此表达式的类型是一个指向void的指针.

因此,我们最终有两种可能:

sizeof(int) == sizeof(*((int *) (NULL)))//如果 `x` 是一个整数常量表达式sizeof(int) == sizeof(*((void *)(....)))//否则

根据 GNU C 扩展sizeof(void) == 1.因此,如果x 是一个整数常量表达式,则宏的结果是1;否则,0.

此外,由于我们只是比较两个 sizeof 表达式的相等性,结果本身就是另一个整数常量表达式 (6.6.3, 6.6.6):<块引用>

常量表达式不应包含赋值、递增、递减、函数调用或逗号运算符,除非它们包含在未计算的子表达式中.

整数常量表达式应具有整数类型,并且只能具有整数常量、枚举常量、字符常量、结果为整数常量的sizeof表达式的操作数,以及作为转换的直接操作数的浮动常量.整数常量表达式中的强制转换运算符只能将算术类型转换为整数类型,除非作为 sizeof 运算符的操作数的一部分.

因此,总而言之,如果参数是整数常量,__is_constexpr(x) 宏返回值为 1整数常量表达式表达.否则,它返回值为 0整数常量表达式.

<小时>

为什么要引入它?

宏变成了在努力删除所有来自 Linux 内核的可变长度数组 (VLA).

为了方便,需要启用 GCC 的 -Wvla 警告 内核范围的;以便编译器标记所有 VLA 实例.

当警告被启用时,事实证明 GCC 报告了许多阵列是 VLA 的情况,这并非有意为之.例如在 fs/btrfs/tree-checker.c:

#define BTRFS_NAME_LEN 255#define XATTR_NAME_MAX 255char 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 代码,即使它知道大小是编译-时间常数.相比之下,Clang 没有.

由于内核中的 max(x, y) 宏不是常量表达式,因此 GCC 触发了警告并生成了内核开发人员不打算这样做的 VLA 代码.

因此,一些内核开发人员试图开发 max 和其他宏的替代版本,以避免出现警告和 VLA 代码.一些尝试试图利用 GCC 的 __builtin_constant_p 内置,但没有一种方法适用于当时内核支持的所有 GCC 版本(gcc >= 4.4).

在某个时候,Martin Uecker 提出了一种特别聪明的方法使用内置函数(rg"5rg">

#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))

虽然该方法使用了 GCC 扩展,它仍然很受欢迎并被用作 __is_constexpr(x) 宏背后的关键思想,该宏在与其他开发人员进行几次迭代后出现在内核中.然后使用该宏来实现 max 宏和其他需要为常量表达式的宏,以避免 GCC 生成 VLA 代码.

How does the __is_constexpr(x) macro of the Linux Kernel work? What is its purpose? When was it introduced? Why was it introduced?

/*
 * 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)))


For a discussion on different approaches to solve the same problem, see instead: Detecting Integer Constant Expressions in Macros

解决方案

Linux Kernel's __is_constexpr macro

Introduction

The __is_constexpr(x) macro can be found in Linux Kernel's 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)))

It was introduced during the merge window for Linux Kernel v4.17, commit 3c8ba0d61d04 on 2018-04-05; although the discussions around it started a month before.

The macro is notable for taking advantage of subtle details of the C standard: the conditional operator's rules for determining its returned type (6.5.15.6) and the definition of a null pointer constant (6.3.2.3.3).

In addition, it relies on sizeof(void) being allowed (and different than sizeof(int)), which is a GNU C extension.


How does it work?

The macro's body is:

(sizeof(int) == sizeof(*(8 ? ((void *)((long)(x) * 0l)) : (int *)8)))

Let's focus on this part:

((void *)((long)(x) * 0l))

Note: the (long)(x) cast is intended to allow x to have pointer types and to avoid warnings on u64 types on 32-bit platforms. However, this detail is not important for understanding the key points of the macro.

If x is an integer constant expression (6.6.6), then it follows that ((long)(x) * 0l) is an integer constant expression of value 0. Therefore, (void *)((long)(x) * 0l) is a null pointer constant (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

If x is not an integer constant expression, then (void *)((long)(x) * 0l) is not a null pointer constant, regardless of its value.

Knowing that, we can see what happens afterwards:

8 ? ((void *)((long)(x) * 0l)) : (int *)8

Note: the second 8 literal is intended to avoid compiler warnings about creating pointers to unaligned addresses. The first 8 literal could simply be 1. However, these details are not important for understanding the key points of the macro.

The key here is that the conditional operator returns a different type depending on whether one of the operands is a null pointer constant (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.

So, if x was an integer constant expression, then the second operand is a null pointer constant and therefore the type of the expression is the type of the third operand, which is a pointer to int.

Otherwise, the second operand is a pointer to void and thus the type of the expression is a pointer to void.

Therefore, we end up with two possibilities:

sizeof(int) == sizeof(*((int *) (NULL))) // if `x` was an integer constant expression
sizeof(int) == sizeof(*((void *)(....))) // otherwise

According to the GNU C extension, sizeof(void) == 1. Therefore, if x was an integer constant expression, the result of the macro is 1; otherwise, 0.

Moreover, since we are only comparing for equality two sizeof expressions, the result is itself another integer constant expression (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.

Therefore, in summary, the __is_constexpr(x) macro returns an integer constant expression of value 1 if the argument is an integer constant expression. Otherwise, it returns an integer constant expression of value 0.


Why was it introduced?

The macro came to be during the effort to remove all Variable Length Arrays (VLAs) from the Linux kernel.

In order to facilitate so, it was desirable to enable GCC's -Wvla warning kernel-wide; so that all instances of VLAs were flagged by the compiler.

When the warning was enabled, it turned out that GCC reported many cases of arrays being VLAs which was not intended to be so. For instance in fs/btrfs/tree-checker.c:

#define BTRFS_NAME_LEN 255
#define XATTR_NAME_MAX 255

char namebuf[max(BTRFS_NAME_LEN, XATTR_NAME_MAX)];

A developer may expect that max(BTRFS_NAME_LEN, XATTR_NAME_MAX) was resolved to 255 and therefore it should be treated as a standard array (i.e. non-VLA). However, this depends on what the max(x, y) macro expands to.

The key issue is that GCC generates VLA-code if the array's size is not an (integer) constant expression as defined by the C standard. For instance:

#define not_really_constexpr ((void)0, 100)

int a[not_really_constexpr];

According to the C90 standard, ((void)0, 100) is not a constant expression (6.6), due to the comma operator being used (6.6.3). In this case, GCC opts to issue VLA code, even when it knows the size is a compile-time constant. Clang, in contrast, does not.

Since the max(x, y) macro in the kernel was not a constant expression, GCC triggered the warnings and generated VLA code where kernel developers did not intend it.

Therefore, a few kernel developers tried to develop alternative versions of the max and other macros to avoid the warnings and the VLA code. Some attempts tried to leverage GCC's __builtin_constant_p builtin, but no approach worked with all the versions of GCC that the kernel supported at the time (gcc >= 4.4).

At some point, Martin Uecker proposed a particularly clever approach that did not use builtins (taking inspiration from glibc's tgmath.h):

#define ICE_P(x) (sizeof(int) == sizeof(*(1 ? ((void*)((x) * 0l)) : (int*)1)))

While the approach uses a GCC extension, it was nevertheless well-received and was used as the key idea behind the __is_constexpr(x) macro which appeared in the kernel after a few iterations with other developers. The macro was used then to implement the max macro and other macros that are required to be constant expressions in order to avoid GCC generating VLA code.

这篇关于Linux 内核的 __is_constexpr 宏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆