C ++:调用运算符和调用它的实现之间有区别吗

C++: Is there a difference between calling an operator and calling it's implementation

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

我明白 UB 是神秘的,而且显然是未定义的,但仍然有一些我注意到的东西引起了我的兴趣。

在调试时,我将 == 调用与其实现交换(即 a==bmemcmp(a.member_x, b.member_x, SIZE) 交换)并且没有段错误!

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

澄清一下:是的,此代码包含 UB。这很糟糕,它的结果是不确定的。我想知道的是:调用运算符或调用它的主体时会发生什么不同吗?UB 只是让我认为可能存在差异(并且显然已修复)

未定义的行为意味着"anything can happen"。 "Anything" 包括 "working just as intended"。这可能意味着您可以在不更改任何内容的情况下获得不同的行为,也可能意味着即使您更改了某些内容也可以获得相同的行为。

过去,关于依赖未定义行为的警告通常包括众所周知的 "launching of the nuclear missiles"。

但是,使用现代积极优化的编译器,行为可能会更加微妙。过去,未定义的行为通常会导致 "whatever happens happens"。例如。在您的示例中,如果您被允许访问它,您将在内存中读取 "junk" ,如果不允许,您将读取段错误。但是操作(即 "compare these two chunks of memory")仍然会以某种方式发生。

这不再是 "guaranteed"(在涉及 UB 时 没有 任何保证)与现代​​积极优化的编译器。编译器不会再胡闹了

对于现代优化编译器,编译器必须经常决定(或证明)某种优化是安全的,即它不会改变可观察到的指定行为。由于 UB 意味着 "anything can happen",这意味着证明某些优化是安全的优化器部分可以 "assume anything it wants"。本质上,它可以假设所有优化都是安全的,然后继续进行它想要提供最积极的优化。

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

假设我们有两个线程在处理共享的可变状态。两个线程之一显示 UB。然后,优化器可以决定该线程 不会 操纵状态("anything can happen",还记得吗?)并且因为它现在可以证明该状态只会被访问一个线程,它可以优化掉所有的锁! [注意:我不知道现实中是否有任何编译器会这样做,但 会被允许这样做!]

这是另一个例子来证明 "anything can happen" 确实 确实 意味着 "anything":让我们假设有两种可能的优化可以应用于某些调用您的 operator== 的堆栈上层代码。一种优化只有在编译器可以证明 operator== 永远为真时才有效。另一个优化只有在编译器可以证明它永远为假时才有效。当然,这意味着这两种优化都不能应用,因为一般来说,您的 operator== 可以 return 为真或假。

但是!我们有 UB。因此,编译器可以决定假设它始终为真并应用优化 #1。或者它可以决定它始终为假并应用优化#2。好吧,很公平。但是,它也可以决定应用 both 优化!记住:"anything can happen"。不只是 "anything that makes sense according to the logical framework of the C++ spec",还有 "anything" 时期。如果编译器需要某些东西同时为真和假,可以在 UB 存在的情况下自由假设。

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