Lun*_*din 9 c clang language-lawyer unspecified-behavior
我正在讨论使用具有不确定值的变量导致未指定的行为,而不是未定义的行为,如此处所述。这假设具有自动存储持续时间的变量已获取其地址并且陷阱表示不适用。
在具体情况下,讨论了ptr之后发生的情况free(ptr),在这种情况下 C17 6.2.4 适用:
当指针指向(或刚刚过去)的对象到达其生命周期结束时,指针的值变得不确定。
我做了这个例子:
#include <stdlib.h>
#include <stdio.h>
int main (void)
{
int* ptr = malloc(sizeof *ptr);
int* garbage;
int*volatile* dummy = &garbage; // take the address
free(ptr);
puts("This should always print");
fflush(stdout);
if(ptr == garbage)
{
puts("Didn't see that one coming.");
}
else
{
puts("I expect this to happen");
}
puts("This should always print");
}
Run Code Online (Sandbox Code Playgroud)
我提出的论点是,从理论上讲,我们无法知道是ptr == garbage真是假,因为此时它们都是不确定的。因此编译器甚至不需要读取这些内存位置 - 因为它可以推断两个指针都保存不确定的值,所以在优化期间可以根据需要自由地将表达式评估为 true 或 false。(实际上大多数编译器可能不会这样做。)
我在 x86_64 编译器 gcc、icx 和 clang 14 上尝试了代码-std=c17 -pedantic-errors -Wall -Wextra -O3,在所有情况下我都得到了输出:
This should always print
I expect this to happen
This should always print
Run Code Online (Sandbox Code Playgroud)
然而,特别是在 clang 15 中,我得到:
This should always print
This should always print
Run Code Online (Sandbox Code Playgroud)
随后是错误代码 139 段故障。
https://godbolt.org/z/E6xTzc156
如果我注释掉“这应该总是打印”/fflush行,clang 15 会生成一个虚拟可执行文件,反汇编仅包含一个标签:
main: # @main
Run Code Online (Sandbox Code Playgroud)
尽管 main() 包含一些副作用。
问题:
为什么 clang 15 的行为与旧版本/其他编译器不同?它是否实现了我正在使用的 x86_64 上的指针陷阱表示或类似的东西?
假设没有陷阱表示,则此代码不应包含未定义的行为。
编辑
关于不属于陷阱表示的不确定值应如何(不)表现,这已在DR 260和DR 451中详细讨论。如果整个事情被斥为“这是未定义的行为”,委员会就不会进行这些漫长而详细的讨论。
clang 为什么要这样做?Clang 正在将其变为无法访问,因为它将其视为未定义的行为。我们可以使用将其变成一个显式陷阱-mllvm -trap-unreachable,如果我们尝试使用您的示例clang 确实会ud2为我们生成一个。
这是 clang 社区内更大讨论的一部分,您可以在有符号整数溢出导致程序跳过尾声并陷入另一个函数的讨论中看到部分内容。其中讨论了签名溢出情况的问题,在底部我们可以看到围绕无限循环的链接讨论,而没有前进的进展。
我对您的挫败感表示同情,因为 WG14 似乎讨论了不确定值的问题,并且似乎确实有一些关于使用“不稳定值”等内容来减轻影响的讨论。最近的 C++ 提案“自动存储持续时间的零初始化对象”是这样说的:
WG14 C 标准委员会对“摆动值”和“摆动位”进行了广泛的讨论,特别是围绕 [DR451] 和 [N1793],在 [Seacord] 中进行了总结。
C 标准委员会尚未就 C23 得出结论,并且摆动位继续不确定地摆动。
因此,虽然这个问题已经讨论了很多次,但尚未达成共识,如果我们进一步阅读Uninitialized Reads: Understanding the suggest revisions to the C language中引用的文章,它会说:
根据现任 WG14 召集人 David Keaton 的说法,读取任何存储持续时间的不确定值是 C 中隐含的未定义行为,并且附件 J.2(非规范性)中的描述不完整。未定义行为的修订定义可以表述为“对象的值在不确定时被读取”。
不幸的是,委员会或更广泛的社区对于未初始化的读取没有达成共识。
因此,虽然这个领域有各种各样的想法,但他们还没有得出结论。
有人正在努力改善这种情况,但我们还没有做到这一点。编译器社区内部也一直在讨论我们应该如何积极地对待各种未定义的行为,但同样也没有得出结论。