为什么比较具有未定义行为的指针仍会给出正确的结果?
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
的地址。因为可以自由推导出pa
和pb
不能比较,或者比较总是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
用简单的英语来说,编译器和 运行time 环境都 不需要以任何特定方式处理这种情况,结果可能是任何字面意思.您的代码可能会立即崩溃。您可能会进入一个糟糕的状态,以至于您的程序在其他地方崩溃(这些问题调试起来 很有趣 ,让我告诉您)。您可能会损坏其他数据。或者您的代码可能看起来 运行 很好 并且没有明显的不良影响,这是最糟糕的结果。
我想了解 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
的地址。因为可以自由推导出pa
和pb
不能比较,或者比较总是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
用简单的英语来说,编译器和 运行time 环境都 不需要以任何特定方式处理这种情况,结果可能是任何字面意思.您的代码可能会立即崩溃。您可能会进入一个糟糕的状态,以至于您的程序在其他地方崩溃(这些问题调试起来 很有趣 ,让我告诉您)。您可能会损坏其他数据。或者您的代码可能看起来 运行 很好 并且没有明显的不良影响,这是最糟糕的结果。