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

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

问题描述

Linux内核的__is_constexpr(x)宏如何工作?目的是什么?什么时候推出的?为什么要引入?

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

简介

__is_constexpr(x)宏可以在Linux内核的

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

它是在Linux内核v4.17的合并窗口期间引入的,

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.

该宏值得注意地利用了C标准的微妙细节:条件运算符的用于确定其返回类型的规则(6.5.15.6)和 null的定义指针常量(6.3.2.3.3).

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).

此外,它依赖于允许使用sizeof(void)(与sizeof(int)不同),这是

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

宏的主体是:

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

让我们专注于这一部分:

Let's focus on this part:

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

注意:(long)(x)强制转换为打算允许x具有指针类型,并避免在32位平台上的u64类型上出现警告.但是,此详细信息对于理解宏的关键点并不重要.

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.

如果x 整数常量表达式(6.6.6),则得出((long)(x) * 0l)整数常量表达式值0.因此,(void *)((long)(x) * 0l)空指针常量(6.3.2.3.3):

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):

值为0的整数常量表达式,或强制转换为 void * 类型的表达式,称为空指针常量

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)不是空指针常量无论如何价值.

知道了这一点,我们可以看到之后发生的事情:

Knowing that, we can see what happens afterwards:

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

注意:第二个8文字是打算以避免编译器警告有关创建指向未对齐地址的指针.第一个8文字可以简单地是1.但是,这些细节对于理解宏的关键点并不重要.

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.

此处的关键是条件运算符返回不同类型,具体取决于操作数之一是否为空指针常量(6.5 .15.6):

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):

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

[...] 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 整数常量表达式,则第二个操作数是 null指针常量,因此,表达式是第三个操作数的类型,它是指向int的指针.

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.

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

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

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

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.

此外,由于我们仅比较两个sizeof表达式,因此结果本身就是另一个整数常量表达式(6.6.3,6.6.6):

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.

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

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整数常量表达式.

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.

该宏在操作过程中成为 删除了所有可变长度数组(VLA).

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

为方便起见,最好启用 GCC的-Wvla警告整个内核;以便所有VLA实例都由编译器标记.

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.

启用警告后,事实证明,GCC报告了许多阵列为VLA的情况,但并非如此.例如在 fs/btrfs/tree中-checker.c :

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)];

开发人员可能希望将max(BTRFS_NAME_LEN, XATTR_NAME_MAX)解析为255,因此应将其视为标准数组(即非VLA).但是,这取决于max(x, y)宏扩展到的内容.

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.

关键问题是,如果数组的大小不是C标准定义的(整数)常量表达式,则GCC会生成VLA代码.例如:

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];

根据C90标准,由于使用了逗号((void)0, 100)不是恒定表达式(6.6) 6.6.3).在这种情况下,即使GCC知道大小是编译的,它也会选择发布VLA代码时间常数.相比之下,不是.

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.

由于内核中的max(x, y)宏不是常量表达式,因此GCC触发了警告并在内核开发人员不希望的地方生成了VLA代码.

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.

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

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).

在某个时候,马丁·乌克(Martin Uecker)提出了一种特别聪明的方法,不要使用内置插件(获得灵感 sourceware.org/git/?p=glibc.git;a=blob;f=math/tgmath.h;h=a709a59d0fa1168ef03349561169fc5bd27d65aa;hb=d8742dd82f6a00601155c69bad3012e905591e1f"rel =" nofollow noreferr>

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

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

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天全站免登陆