【问题标题】:Which of the following is the correct behavior (g++ vs clang++-12)?以下哪一项是正确的行为(g++ vs clang++-12)?
【发布时间】:2021-09-17 16:17:00
【问题描述】:

以下是代码:

#include <iostream>

const int& temp_func() {
    return 3;
}

int main() {
    std::cout << temp_func() << std::endl;
}

用g++编译时(Ubuntu 9.3.0-17ubuntu1~20.04),结果:

[1]    402809 segmentation fault ...

另一方面,当用clang++-12编译时,结果:

3

【问题讨论】:

  • 如果你启用警告(你应该总是这样做,没有例外)你会得到一个很好的警告:warning: returning reference to temporary [-Wreturn-local-addr] 在 gcc 和 clang 中

标签: c++ g++ clang++


【解决方案1】:

两者都是正确的。您的代码具有未定义的行为。当您执行return 3 时,会创建一个临时int 对象,并且函数返回的引用将绑定到该临时对象。在 return 语句完成后,该临时被销毁,使引用悬空。尽管该引用的任何访问都具有未定义的行为。

【讨论】:

  • 我同意。但是,使用一个编译器生成的可执行文件始终会导致分段错误,而使用另一个编译器,由于 const ref 而延长了临时文件的生命周期。这就是我提出问题的原因。并不是说使用一个编译器创建的可执行文件会导致未定义的结果。
  • @ArnabDe 很可能clang在临时所在的内存位置没有做任何事情,所以它只是访问那个位置并且3仍然在那里,所以这就是你得到的。使用 gcc,它可能会将该内存位置标记为错误,因为它是错误的,并且会导致崩溃。你打破了语言契约,所以编译器可以为所欲为,而这两个不同的编译器在做两件不同的事情,这是完全合理的。
  • @ArnabDe - 未定义的行为并不意味着编译器每次构建时都必须做一些创造性的事情。无法保证随机结果……如果有,UB就不会那么险恶了。出现在“工作”是通常的结果。
  • 看看clang (godbolt.org/z/GerhqWhdY) 和gcc (godbolt.org/z/Y1h43a53Y) 生成的asm,似乎clang用int 3的地址加载rax,然后访问它从函数返回后;而 gcc 不会加载 rax 但无论如何都会在从函数返回后尝试访问它(从而强制 segfault/UB)。我认为 clang 无法确保有人在访问 rax 之前已在该堆栈帧上写入。
  • @NathanOliver 不是真的。 gcc 明确地注意到未定义的行为并将 0 移动到 rax 中,而 clang 实际上将值复制到堆栈中并返回堆栈地址。两个编译器都只在调试模式下这样做。在发布模式下,gcc 显式发出 ud2,而 clang 对 rax 不执行任何操作(导致垃圾输出)。
【解决方案2】:

没有“正确的行为”。该程序通过返回对作为文字 3 的副本的 temporary 的悬空引用来展示未定义的行为。由于它是未定义的行为,任何事情都可能发生:程序可能崩溃,它可以静默成功,它可以显示 4,否则它可能会间歇性地失败。您看到的这两种行为都是 C++ 标准可接受的。

【讨论】:

    猜你喜欢
    • 2017-06-05
    • 2014-11-02
    • 1970-01-01
    • 2015-09-17
    • 1970-01-01
    • 2018-07-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多