1

我有一个类,其中我在特定成员上重载了==运算符。memcmp()由于在代码中完成了错误的副本(memcpy调用的大小比它应该的大),我在调用==操作员时遇到了段错误。

我知道 UB 是神秘的,显然是未定义的,但我仍然注意到一些让我感兴趣的东西。

在调试时,我==用它的实现交换了调用(即a==b交换了memcmp(a.member_x, b.member_x, SIZE))并且没有段错误!

那么,使用运算符本身和用实现替换它之间有区别吗?或者这只是 UB?

澄清一下:是的,此代码包括 UB。这很糟糕,它的结果是不确定的。我想知道的是:调用运算符或调用它的主体时会发生不同的事情吗?UB 只是让我觉得可能存在差异(显然是固定的)

4

1 回答 1

2

未定义的行为意味着“任何事情都可能发生”。“任何事情”包括“按预期工作”。这可能意味着您可以在不更改任何内容的情况下获得不同的行为,也可能意味着即使您更改了某些内容,您也会获得相同的行为。

过去,关于依赖未定义行为的警告通常包括众所周知的“发射核导弹”。

然而,随着现代积极优化的编译器,行为可能会更加微妙。在过去,未定义的行为通常会导致“无论发生什么”。例如,在您的示例中,如果您被允许访问它,您将在内存中读取“垃圾”,或者如果您不允许访问,您将读取内存中的“垃圾”。但是操作(即“比较这两个内存块”)仍然会以某种方式发生。

对于现代积极优化的编译器,这不再是“有保证的”(对于 UB 没有任何保证)。编译器将不再只是做无意义的事情。

对于现代优化编译器,编译器必须经常决定(或证明)某种优化是安全的,即它不会改变可观察到的指定行为。而且由于 UB 的意思是“任何事情都可能发生”,这意味着优化器中证明某些优化是安全的部分可以“假设它想要的任何事情”。本质上,它可以假设所有优化都是安全的,然后继续进行,但它希望提供最激进的优化。

因此,与以前相比,UB 的可预测性和明显性都大大降低了。例如,程序某个地方的 UB 可以导致优化器以某种方式优化某些东西,它会改变以某种方式连接到这段代码的程序的不同部分中其他东西的行为(例如,它调用它,或两者都操纵相同的状态)。

假设我们有两个线程操作共享可变状态。两个线程之一显示 UB。然后,优化器可以决定该线程操纵状态(“任何事情都可能发生”,还记得吗?),并且由于它现在可以证明该状态只能被一个线程访问,它可以优化所有锁![注意:我不知道现实中是否有任何编译器这样做,但它被允许!]

这是另一个例子来证明“任何事情都可能发生”真的,真的意味着“任何事情”:让我们假设有两种可能的优化可以应用在调用你的operator==. 一种优化只有在编译器能够证明它operator==总是正确的情况下才有效。其他优化仅在编译器可以证明它总是错误的情况下才有效。当然,这意味着这两种优化都不能应用,因为一般情况下,您operator==可以返回 true 或 false。

但!我们有 UB。因此,编译器可以决定只假设它总是正确的并应用优化#1。或者它可以决定它总是错误的并应用优化#2。好吧,够公平的。但是,它也可以决定应用这两种优化!记住:“任何事情都可能发生”。不仅仅是“根据 C++ 规范的逻辑框架有意义的任何事情”,而是“任何事情”时期。如果编译器需要同时为真和假的东西,那么在 UB 存在的情况下可以自由地假设。

您可以将现代优化编译器视为尝试证明有关您的代码的定理,然后根据这些证明应用优化。UB 允许它证明任何所有定理。

于 2017-07-03T08:03:10.477 回答