当函数有警告 [-Wreturn-type] 时,为什么优化会忽略 if 语句?

陈泽霖*_*陈泽霖 4 c++ optimization assembly compiler-optimization undefined-behavior

操作系统:ubuntu2204

编译器:gcc 11.2 x86_64

这是一个简单的代码:


#include <cstdlib>

int func(int val) {
    if (val == 1) {
    } else {
        abort();
    }
}

int main(int argc, char* argv[]) {
    func(argc);
}

Run Code Online (Sandbox Code Playgroud)

当我在没有任何优化的情况下编译它并运行它时,它工作正常。

但是当我用 编译它时g++ tmp.cpp -O3,结果func会忽略输入值,而只调用abort.

当然,我可以通过在 的末尾添加 return 语句来修复它func,但是,为什么呢?

这是objdump -d a.out优化函数的一些输出func


0000000000001060 <_Z4funci>:
    1060:   f3 0f 1e fa             endbr64 
    1064:   50                      push   %rax
    1065:   58                      pop    %rax
    1066:   50                      push   %rax
    1067:   e8 e4 ff ff ff          call   1050 <abort@plt>
Run Code Online (Sandbox Code Playgroud)

Jan*_*tke 14

首先,在没有return语句的情况下从非 void 函数的末尾流出是 C++ 中的未定义行为。请参阅为什么在不返回值的情况下流出非 void 函数的末尾不会产生编译器错误?通常,编译器使用未定义的行为进行优化。也可以看看:

直观上,编译器可以看到分支中存在未定义的行为,if (val == 1)因为您从函数末尾流出。然后,编译器可以说该分支无法访问并始终调用abort(),就好像条件始终是一样false

更具体地说,LLVM 从以下 IR 开始:

entry:
  // ...
  br i1 %cmp, label %if.then, label %if.else

if.then:
  br label %if.end
if.else:
  call void @abort()
  unreachable
if.end:
  unreachable
Run Code Online (Sandbox Code Playgroud)

根据 的值val,控制流分支到if.then(立即转到if.end)或到if.else(调用函数[[noreturn]])。

SimplifyCFG(简化控制流图)过程中,该结构被简化为:

  %1 = xor i1 %cmp, true
  call void @llvm.assume(i1 %1)
  call void @abort()
  unreachable
Run Code Online (Sandbox Code Playgroud)

编译器现在假设val ^ 1始终为真,这意味着它val必须是false。不再有分支,而是控制直接流入abort(). 直观上,if 语句被更改以防止到达unreachable指令。具体来说,消除br跳转到包含该基本块的a。unreachable

经过进一步优化后,整个函数本质上变成:

  tail call void @abort()
  unreachable
Run Code Online (Sandbox Code Playgroud)

请访问https://godbolt.org/z/bnvh7KrG4查看整个优化流程

在调试版本中,如果没有优化,编译器不会简化 if 语句。该代码“按预期”工作。GCC 和 clang 都会输出类似以下内容的内容:

        je      .L2
        call    abort
.L2:
        ud2
Run Code Online (Sandbox Code Playgroud)

从函数末尾流出成为ud2伪指令(从 生成unreachable),执行它会立即停止程序。

这个答案特定于 clang 并且您正在使用 GCC,但是,这些编译器执行的优化相对相似,特别是在像这样的小情况下。


Yks*_*nen 6

这与警告无关,而是与警告的含义有关。每个执行分支都必须有一个有效的结束(对于非void函数,通常是return带值、抛出异常或终止程序),否则您的程序将出现未定义行为。未定义的行为可能会导致时间旅行(除其他外)

最有可能的是,优化器注意到,如果不调用未定义行为,就不可能val等于1,并且由于允许假设代码中没有 UB,因此它可以假设这val == 1永远不会成立,因此它将所有内容优化为合理的路径在代码中。

  • @MarcusMüller,它变得相当普遍,值得警惕。另请参阅[旧的新事物 - 未定义的行为可能导致时间旅行(除其他外,但时间旅行是最有趣的)](https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633) (3认同)
  • @MarcusMüller 编译器只是做了最小的假设来避免 UB _“调用者永远不会传递 `1`”_ 。执行 `ret` _"...马上..."_ 仍然是 UB,因为该函数不返回值。请注意,“abort”是“[[noreturn]]”,因此“abort”路径不是 UB 。 (3认同)

归档时间:

查看次数:

228 次

最近记录:

2 年,5 月 前