这种编译器优化的不一致性是否完全由未定义的行为解释?

时间:2016-04-13 23:42:10

标签: c++ compiler-optimization undefined-behavior compiler-options observable-behavior

在讨论过程中,我曾与几位同事一起在C ++中汇总了一段代码,以说明内存访问违规。

我目前正处于慢慢回归C ++的过程中,经过长时间的几乎完全使用垃圾收集的语言,我想,我的失去触摸显示,因为我对我的短程序的行为感到非常困惑展出。

有问题的代码是这样的:

#include <iostream>

using std::cout;
using std::endl;

struct A
{
    int value;
};

void f()
{
    A* pa;    // Uninitialized pointer
    cout<< pa << endl;
    pa->value = 42;    // Writing via an uninitialized pointer
}

int main(int argc, char** argv)
{   
    f();

    cout<< "Returned to main()" << endl;
    return 0;
}

我在Ubuntu 15.04上使用GCC 4.9.2编译它,并设置了-O2编译器标志。我在运行它时的期望是,当我的评论表示为“通过未初始化的指针写入”的行被执行时,它会崩溃。

然而,与我的期望相反,程序成功运行到最后,产生以下输出:

0
Returned to main()

我使用-O0标志重新编译代码(禁用所有优化)并再次运行程序。这一次,行为就像我预期的那样:

0
Segmentation fault

(好吧,几乎:我没想到指针被初始化为0.)基于这个观察,我假设在用-O2集编译时,致命的指令得到了优化。这是有道理的,因为在由违规行设置之后没有其他代码访问pa->value,因此,可能,编译器确定其删除不会修改程序的可观察行为。

我重复了这几次,每当程序在没有优化的情况下编译时会崩溃并奇迹般地工作,当使用-O2进行编译时。

当我添加一条输出pa->value的行到f()身体的末尾时,我的假设得到进一步证实:

cout<< pa->value << endl;

正如预期的那样,有了这条线,无论优化级别如何,程序都会一直崩溃。

如果我的假设到目前为止是正确的,那么这一切都是有道理的。 但是,在我的理解有所突破的情况下,如果我将代码从f()的主体直接移动到main(),就像这样:

int main(int argc, char** argv)
{   
    A* pa;
    cout<< pa << endl;
    pa->value = 42;
    cout<< pa->value << endl;

    return 0;
}

禁用优化后,此程序崩溃,正如预期的那样。但是,对于-O2,程序成功运行到最后并生成以下输出:

0
42

这对我来说毫无意义。

This answer提到“取消引用尚未明确初始化的指针”,这正是我正在做的,是C ++中未定义行为的来源之一。

那么,与main()中的代码相比,优化会影响f()中的代码的方式有何不同,完全由我的程序包含UB的事实解释,因此编译器在技术上是免费的为了“疯狂”,或者在main()中的代码优化方式和其他例程中的代码之间存在一些我不知道的根本区别?

2 个答案:

答案 0 :(得分:2)

您的程序有未定义的行为。这意味着可能发生任何事情。 C ++标准根本不涉及该程序。你不应该满怀期望。

经常说未定义的行为可能会发射导弹&#34;或者&#34;让恶魔飞出你的鼻子,以加强这一点。后者更加牵强,但前者是可行的,想象你的代码是在核发射场上,并且狂野的指针碰巧写下了一块内存,开始了全球的热战......

答案 1 :(得分:1)

编写未知指针一直是可能产生未知后果的东西。什么样的恶作剧是一种当前时尚的哲学,它表明编译器应该假设程序永远不会接收导致UB的输入,因此应该优化任何代码来测试这些输入,如果这样的测试不会阻止UB发生

因此,例如,给定:

uint32_t hey(uint16_t x, uint16_t y)
{
  if (x < 60000)
    launch_missiles();
  else
    return x*y;
}
void wow(uint16_t x)
{
  return hey(x,40000);
}

32位编译器可以通过无条件调用合法地替换wow launch_missiles x,不考虑x的价值,因为import UIKit import Alamofire import SwiftyJSON protocol UserModelDelegate { func didReceiveUserInfo(userInfo: JSON) } class UserModel: NSObject { // Instantiate the delegate var delegate: UserModelDelegate? override init() { super.init() } func getUserInfo(token: String) { let url = "http://test.app/api/userInfo" let headers = [ "Authorization":"Bearer \(token)", "Content-Type": "application/x-www-form-urlencoded" ] Alamofire.request(.GET, url, headers: headers).responseJSON { response in switch response.result { case .Success(let data): if (data as? [String:AnyObject])!["error"] == nil { let json = JSON(data) self.delegate?.didReceiveUserInfo(json) } else { self.delegate?.didReceiveUserInfo(nil) } case .Failure(let error): print("Request failed with error: \(error)") } } } } &#34;可能&#34;大于53687(除此之外的任何值都会导致x * y的计算溢出。即使C89的作者指出那个时代的大多数编译器会在上述情况下计算出正确的结果,因为标准并没有对编译器施加任何要求,超现代哲学将其视为“更高效”,并且编制者认为程序永远不会接收需要依赖此类事物的输入。