为什么比较具有未定义行为的指针仍会给出正确的结果?

Why does comparing pointers with undefined behavior still give correct results?

我想了解 C 程序中的指针比较运算符。

ISO/IEC 9899:2011 指定比较指向不同对象的指针(使用 ><)是未定义的行为。

然而,我发现当 "irrelevant" 指针被比较时,它们似乎被所有测试的 compilers/interpreters.
视为只是 "numbers that happen to represent a location in memory" 总是这样吗?如果是这样,为什么这部分不是标准?

换句话说,是否存在指针 p 指向比方说 0xffff 的虚拟内存地址的边缘情况,指针 b 指向 0x0000,然而 (p < b) returns 是真的吗?

请注意,"undefined behaviour" 并不意味着 "will crash" 或 "will do bad stuff." 它意味着 "there is no definition of what will happen; literally anything is allowed to happen." 当优化进入画面时,几乎任何东西 都可以 也确实发生了。

关于您的观察:您可能已经在 x86 或 x86_64 架构上对此进行了测试。在那些上,您仍然有可能得到您观察到的行为(即使它在技术上未定义)。但是,请记住,C 规范适用于 所有 可以使用 C 的平台和体系结构,包括奇异的嵌入式平台、专用硬件等。在此类平台上,我会不太确定此类指针比较的结果。

Is this always the case? If so, why isn't this part of the standard?

大部分时间,但不一定。有各种带有分段内存区域的古怪架构。 C标准也希望让指针成为一些抽象的项目,不一定等同于物理地址。

此外,理论上如果你有这样的东西

int a;
int b;
int* pa = &a;
int* pb = &b;

if (pa < pb) // undefined behavior
    puts("less"); 
else 
    puts("more");

那么理论上编译器可以用 puts("more") 替换整个 if-else,即使 pa 的地址低于 pb 的地址。因为可以自由推导出papb不能比较,或者比较总是false。这是未定义行为的危险 - 编译器生成的代码是任何人的猜测。

在实践中,上面代码片段中的未定义行为似乎导致代码效率较低,在 -O3 上使用 gcc 和 clang x86。它编译成两个地址负载,然后进行 运行 次比较。即使编译器应该能够在编译时计算出所有地址。

将代码更改为明确定义的行为时:

int a[2];
int* pa = &a[0];
int* pb = &a[1];

然后我得到更好的机器代码 - 现在在编译时计算比较,整个程序被对 puts("less").

的简单调用所取代

然而,在嵌入式系统编译器上,您几乎肯定能够访问任何地址,就好像它是一个整数一样——作为一个定义明确的非标准扩展。否则不可能编写闪存驱动程序、引导加载程序、CRC 内存检查等内容。

Is this always the case?

大部分时间,以及在具有 "flat" 内存空间的流行架构上。 (或者至少,这 曾经 是这种情况。正如评论提醒我的那样,这是过去未定义的事情的另一个例子 - 但你可以 -可能会逃避它,但正在向未定义的方向迁移,并且不要用十英尺高的杆子触摸它。)

If so, why isn't this part of the standard?

因为在当时 所有 这绝对不是真的,而且 C 从来没有兴趣以这种方式将自己限制在一组体系结构中。

特别是,"segmented" 内存架构曾经非常非常流行(想想 MS-DOS),根据您使用的内存模型,异构指针比较肯定行不通。

Is this always the case?

没有。不能保证单独的对象将以任何特定顺序排列。不能保证所有对象都占用相同的内存段。

If so, why isn't this part of the standard?

见上文。

“未定义的行为”就是这个意思:

3.4.3
1 undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements

2 NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).

3 EXAMPLE An example of undefined behavior is the behavior on integer overflow

C 2011 online draft

用简单的英语来说,编译器和 运行time 环境都 不需要以任何特定方式处理这种情况,结果可能是任何字面意思.您的代码可能会立即崩溃。您可能会进入一个糟糕的状态,以至于您的程序在其他地方崩溃(这些问题调试起来 很有趣 ,让我告诉您)。您可能会损坏其他数据。或者您的代码可能看起来 运行 很好 并且没有明显的不良影响,这是最糟糕的结果。