C 为什么将指针与未定义的行为进行比较仍然可以得到正确的结果?

C 为什么将指针与未定义的行为进行比较仍然可以得到正确的结果?,c,pointers,comparison,C,Pointers,Comparison,我试图理解c程序中的指针比较运算符 ISO/IEC 9899:2011规定,使用>或比较指针时,请注意未定义的行为并不意味着会崩溃或做坏事。这意味着对将要发生的事情没有定义;事实上,任何事情都是允许发生的。当优化进入画面时,实际上任何事情都可能发生 关于您的观察:您可能已经在x86或x86_64体系结构上对此进行了测试。在这些情况下,你仍然有可能得到你观察到的行为,即使它在技术上没有定义。但是,请记住,C规范适用于所有可使用C的平台和体系结构,包括此类平台上的外来嵌入式平台、专用硬件等,我不太确

我试图理解c程序中的指针比较运算符


ISO/IEC 9899:2011规定,使用>或比较指针时,请注意未定义的行为并不意味着会崩溃或做坏事。这意味着对将要发生的事情没有定义;事实上,任何事情都是允许发生的。当优化进入画面时,实际上任何事情都可能发生


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

注意,未定义的行为并不意味着会崩溃或做坏事。这意味着对将要发生的事情没有定义;事实上,任何事情都是允许发生的。当优化进入画面时,实际上任何事情都可能发生

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

总是这样吗?如果是这样,为什么这部分不是标准的一部分

大多数时候,但不一定。有各种各样的奇怪的体系结构和分段的内存区域。C标准还希望允许指针是一些抽象项,它们不一定等同于物理地址

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

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

if (pa < pb) // undefined behavior
    puts("less"); 
else 
    puts("more");
然后我得到了更好的机器代码——比较现在是在编译时计算的,整个程序被一个简单的putsless调用所取代

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

总是这样吗?如果是这样,为什么这部分不是标准的一部分

大多数时候,但不一定。有各种各样的奇怪的体系结构和分段的内存区域。C标准还希望允许指针是一些抽象项,它们不一定等同于物理地址

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

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

if (pa < pb) // undefined behavior
    puts("less"); 
else 
    puts("more");
然后我得到了更好的机器代码——比较现在是在编译时计算的,整个程序被一个简单的putsless调用所取代

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

总是这样吗

大多数情况下,在具有平坦内存空间的流行体系结构上。 或者至少过去是这样。正如一条评论提醒我的那样,这是另一个例子,这类事情过去是没有定义的,但你可能会侥幸逃脱,但现在正朝着没有定义的方向发展,不要用十英尺高的杆子碰它

如果是这样,为什么这部分不是标准的一部分

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

特别是,分段内存体系结构曾经非常流行,比如MS-DOS,根据您使用的内存模型,异构指针比较肯定不起作用

总是这样吗

大多数情况下,在具有平坦内存空间的流行体系结构上。 或者至少过去是这样。正如一条评论提醒我的那样,这是另一个例子,这类事情过去是没有定义的,但你可能会侥幸逃脱,但现在正朝着没有定义的方向发展,不要用十英尺高的杆子碰它

如果是这样,为什么这部分不是标准的一部分

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

特别是,分段内存体系结构曾经非常流行,比如MS-DOS,根据您使用的内存模型,异构指针比较肯定不起作用

总是这样吗

不保证单独的物体将被放置在任何PAR中。 特殊顺序。不能保证所有对象都占用相同的内存段

如果是这样,为什么这部分不是标准的一部分

见上文

未定义的行为恰恰意味着:

3.4.3 1未定义的行为 使用不可移植或错误的程序结构或错误的数据时的行为, 本国际标准对其无任何要求 2注:可能的未定义行为包括完全忽略不可预测的情况 结果,在翻译或程序执行过程中,以文件化的方式表现为 有或没有发出诊断消息的环境,以终止翻译或 通过发出诊断消息执行。 3示例未定义行为的一个示例是整数溢出行为 简单地说,编译器和运行时环境都不需要以任何特定的方式来处理这种情况,结果可以是任何东西。您的代码可能会立即崩溃。您可能会进入一个糟糕的状态,以至于您的程序在其他地方崩溃。让我告诉您,调试这些问题很有趣。您可能会损坏其他数据。或者,您的代码可能看起来运行正常,没有明显的不良影响,这是最糟糕的结果

总是这样吗

不,不能保证单独的物体会按特定的顺序排列。不能保证所有对象都占用相同的内存段

如果是这样,为什么这部分不是标准的一部分

见上文

未定义的行为恰恰意味着:

3.4.3 1未定义的行为 使用不可移植或错误的程序结构或错误的数据时的行为, 本国际标准对其无任何要求 2注:可能的未定义行为包括完全忽略不可预测的情况 结果,在翻译或程序执行过程中,以文件化的方式表现为 有或没有发出诊断消息的环境,以终止翻译或 通过发出诊断消息执行。 3示例未定义行为的一个示例是整数溢出行为
简单地说,编译器和运行时环境都不需要以任何特定的方式来处理这种情况,结果可以是任何东西。您的代码可能会立即崩溃。您可能会进入一个糟糕的状态,以至于您的程序在其他地方崩溃。让我告诉您,调试这些问题很有趣。您可能会损坏其他数据。或者,您的代码可能运行得很好,没有明显的不良影响,这是最糟糕的结果。

未定义的行为包括显然按预期工作。@Jabberwocky:您的意思是“显然像未阅读预期标准而做出假设的人一样工作”。因为“预期”正如阅读标准所定义的那样,它意味着“任何东西”。@EricPostpischil是的,我应该写得很好。未定义的行为包括显然按照预期工作。@Jabberwocky:你的意思是“显然像一个没有阅读标准就做出假设的人一样工作。”因为“预期”正如阅读标准所定义的那样,它意味着“任何东西”。@EricPostpischil是的,我应该写得很好。架构具有平坦的内存地址空间这一事实并不意味着不相关对象之间的指针比较可以工作。编译器优化可能会破坏它。@EricPostpischil:事实上,编译器优化可以在支持限制指针的编译器上打破限制指针和其他指针之间的均衡比较,如果函数接收指向内存区域开始和结束的指针,后者除了用于确定基于前者的指针所涉及的循环何时停止之外,没有任何用途。我不认为该标准的作者打算禁止这种比较,但clang和gcc解释该标准的方式打破了它们。架构具有平坦内存地址空间的事实并不意味着不相关对象之间的指针比较可以工作。编译器优化可能会破坏它。@EricPostpischil:事实上,编译器优化可以在支持限制指针的编译器上打破限制指针和其他指针之间的均衡比较,如果函数接收指向内存区域开始和结束的指针,后者除了用于确定基于前者的指针所涉及的循环何时停止之外,没有任何用途。我不认为标准的作者打算禁止这样的比较,但是clang和gcc解释标准的方式打破了他们。谢谢你的回答。您提到过分段内存可能会导致意外结果。传呼怎么样?是否编译器会比较偏移量,从而导致意外的结果?@oxynoia Pagi
ng只是分段内存的另一个名称。这是一个很好的例子,说明什么时候会出问题:在具有分页功能的低端MCU上,指针平均为16位。在不包含页面地址的情况下比较指针可能会得到任何随机结果。除非您使用*far限定符或类似的限定符,给您一个24位指针,否则不会比较a页地址。谢谢您的回答。您提到过分段内存可能会导致意外结果。传呼怎么样?是否编译器会比较偏移量,从而导致意外的结果?@oxynoia Paging只是分段内存的另一个名称。这是一个很好的例子,说明什么时候会出问题:在具有分页功能的低端MCU上,指针平均为16位。在不包含页面地址的情况下比较指针可能会得到任何随机结果。除非您使用*far限定符或类似的限定符,给您一个24位指针,否则不会比较a页地址。Re:“几乎可以保证,对于任何一个健全的编译器,您都会得到您观察到的行为”:不,这是不正确的。GCC和Clang在指针来源方面越来越激进:他们注意到指针是如何派生的,并在优化中包含这些信息,或者只是一般地对程序的语义进行编码。结果是,这些指针的比较并不像比较地址那样简单;我已经弱化了这种说法。@EricPostPhischil:我不认为任何一个理智的编译器会处理构造X的语句与GCC和clang进程构造X有合理的矛盾。该标准的作者遵循的原则是,越是荒谬的事情,就越不需要禁止它。该标准的作者并没有试图适应所有的情况,至少从20世纪80年代后期的观点来看,编译器必须离经叛道地做出不有用的行为。在许多情况下,标准将操作保留为未定义的行为……这不是因为不希望实现对其进行有意义的处理,而是因为标准的作者认为没有理由想象在没有授权的情况下实现可能会做其他事情。Re:“几乎可以保证,对于任何理智的编译器,您都会得到您观察到的行为”:不,这是不正确的。GCC和Clang在指针来源方面越来越激进:他们注意指针是如何派生的,并在优化中包含这些信息,或者只是一般地对程序的语义进行编码。结果是,这些指针的比较并不像比较地址那样简单。@EricPostpischil谢谢;我削弱了这一说法。@EricPostphil:我不认为任何一个理智的编译器会处理构造X的语句与GCC和clang process construct X存在合理的矛盾。该标准的作者遵循的原则是,越是荒谬的东西,就越不需要禁止它标准的thors并没有试图适应编译器必须(至少从20世纪80年代后期的观点来看)远远超出其行为方式的所有情况。在许多情况下,标准将操作保留为未定义的行为……并不是因为实现不希望有意义地处理它们,而是因为标准的作者认为没有理由想象在没有授权的情况下实现可能会做其他事情。