我有一个类,我在==
运算符上用memcmp()
重载了特定成员。由于在代码中执行了错误的副本(memcpy
调用了比它应该更大的大小)我在调用==
运算符时遇到了段错误。
我明白UB是神秘的,显然是未定义的,但仍然有一些我注意到的东西引起了我的兴趣。
在调试时,我将==
调用与其实现交换(即a==b
与memcmp(a.member_x, b.member_x, SIZE)
交换)并且没有段错误!
那么,使用运算符本身并将其替换为实现之间是否存在差异,或者这仅仅是UB?
澄清:是的,此代码包含UB。这很糟糕,结果未定义。我想知道的是:在调用操作员或调用它的主体时会发生什么不同的事情? UB让我觉得可能存在差异(显然是固定的)
答案 0 :(得分:2)
未定义的行为意味着“任何事情都可能发生”。 “任何事情”包括“按预期工作”。这可能意味着您可以在不改变任何内容的情况下获得不同的行为,这意味着即使您更改了某些内容,也会获得相同的行为。
过去,关于依赖未定义行为的警告往往包括众所周知的“发射核导弹”。
然而,通过现代积极优化编译器,行为可以更加微妙。在过去,未定义的行为通常会导致“无论发生什么事”。例如。在您的示例中,如果您被允许访问它,您将在内存中读取“垃圾”,如果不允许,则读取段错误。但是操作(即“比较这两个内存块”)仍然会以某种方式发生。
这不再是“保证”(并不是说,当涉及到UB时, 有任何保证)现代积极优化编译器。编译器将不再只是做无意义的事情。
对于现代优化编译器,编译器必须经常决定(或证明)某个优化是安全的,即它不会改变可观察的指定行为。而且由于UB意味着“任何事情都可能发生”,这意味着优化器中证明某些优化是安全的部分可以“假设它想要的任何东西”。从本质上讲,它可以假设所有优化都是安全的,然后继续,但它希望提供最积极的优化。
因此,UB比以前更难以预测,也不那么明显。例如,UB在程序的一个位置可以导致优化器以某种方式优化某些东西,它改变了以某种方式连接到这段代码的程序的不同部分中的其他东西的行为(例如,它调用它,或两者都操纵相同的状态。)
假设我们有两个操作共享可变状态的线程。两个线程中的一个展示了UB。然后,优化器可以决定该线程不操纵状态(“任何事情都可能发生”,记得吗?)并且因为它现在可以证明状态只会被一个线程访问,它可以优化所有锁! [注意:我不知道现实中是否有任何编译器会这样做,但会被允许!]
这是另一个证明“任何事情都可能发生”的例子,真正 意味着“任何事情”:我们假设有两种可能的优化可以应用于堆栈上方的某些代码中调用你的operator==
。一个优化仅在编译器能够证明operator==
始终为真时才有效。另一个优化仅在编译器可以证明它始终为false时才有效。当然,这意味着既不能应用优化,因为通常情况下,operator==
可能返回true或false。
但是!我们有UB。因此,编译器可以决定假设它始终为真并应用优化#1。或者它可以决定它总是错误并应用优化#2。好的,公平的。但是,它也可以决定应用两个优化!记住:“任何事都可能发生”。不只是“根据C ++规范的逻辑框架有意义的东西”,而是“任何”时期。如果编译器需要同时为true和false,则可以在UB存在的情况下自行假设。
您可以将现代优化编译器视为试图证明有关代码的定理,然后根据这些证明应用优化。 UB允许它证明任何和所有定理。