C 为什么';编译器没有优化这些无法访问的指令吗?

C 为什么';编译器没有优化这些无法访问的指令吗?,c,gcc,optimization,assembly,clang,C,Gcc,Optimization,Assembly,Clang,鉴于此代码: int x; int a (int b) { b = a (b); b += x; return b; } int r (int x) { return r (x); } 为什么GCC返回此输出(英特尔语法):-Godbolt的GCC浏览器 a: sub rsp, 8 call a mov edx, DWORD PTR x[rip] add rsp, 8 lea eax, [rax+rdx*8]

鉴于此代码:

int x;

int a (int b) {
    b = a (b);
    b += x;
    return b;
}
int r (int x) {
    return r (x);
}
为什么GCC返回此输出(英特尔语法):-Godbolt的GCC浏览器

a:
    sub rsp, 8
    call    a
    mov edx, DWORD PTR x[rip]
    add rsp, 8
    lea eax, [rax+rdx*8]
    add eax, edx
    ret
和Clang返回此输出(AT&T语法):-Godbolt的clangexplorer

a:                                      # @a
    pushq   %rax
    callq   a
    addl    x(%rip), %eax
    popq    %rdx
    ret
当部分代码明显无法访问时?因为函数的第一个语句是

b = a (b);
该函数将永远递归地调用自己(直到堆栈溢出,您得到一个segfault)。这意味着您永远不会超过这一行,因此,代码的其余部分是不可访问的。可达性优化理论上应该删除代码,对吗


这两个编译器都在x64上运行,并具有以下标志

  • -O3
    -最大优化
  • -march=native
    -[不必要的]尽可能使用特定于机器的优化
  • -xc
    -假设输入语言是c

我在想,他们应该返回一些更符合这句话(双关语)的东西:

GCC(英特尔语法):

叮当声(AT&T语法):

注:这些样本是根据之前观察的记忆手写的,可能不正确


所以总的来说,为什么两个编译器都不将函数折叠成一个递归跳转,因为剩下的代码是不可访问的


编辑: 回应Jack关于语义对等的评论: 对于以下代码:

int j (int x) {
    while (1) {};
    x++;
    return x;
}
GCC返回:

叮当声返回:

j:                                      # @j
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
    jmp .LBB0_1

对Adam关于炸掉烟囱的评论的回应: 对于此代码:

int x;

int a (int b) {
    b = a (b);
    b += x;
    return b;
}
int r (int x) {
    return r (x);
}
GCC生成递归跳转:

Clang很早就回来了:


您正在使用的编译器可能只在单个函数框架内的块级别实现数据流分析,而不考虑递归。(或者,可能只是有趣的递归,即尾部递归。)因为递归调用不是尾部调用,所以从优化的角度来看它不有趣

您的函数有一个问题:它的编译方式会破坏堆栈。它是这样编译的,因为调用不是尾部调用;将其视为一种优化是不合法的


调用可以被视为“伪尾部调用”,因为调用后的代码从未被调用,因此如果我们删除该代码,那么递归调用是函数执行的最后一项操作。然后,我们可以将堆栈吹扫代码简化为一个纯粹的无限循环。不过,这不能称为真正的优化;这是用一个不同的bug表现形式替换一个bug表现形式。

Hmm。我愚蠢的问题:为什么在递归调用激活后,死代码消除会决定这些内容?@tmyklebu-因为它不符合语法,而且很难解析。将“decision”改为“decision that”,意思就更清楚了。@tmyklebu-答案是它并不决定它是活着的。相反,它没有注意到它已经死了。@StephenC:这确实会改善评论。不幸的是,现在对我来说修复它已经太晚了。为了确定死代码,编译器正在分析一个不跨函数调用的程序局部图。@StephenC:但这是编译器为所欲为的借口。无声地将“blow the stack”转换为“loop ever”是完全可以的。@HotLicks-事实上,在涉及错误/病态代码的情况下拼命优化几乎肯定是浪费编译器编写者的精力。对于OP-优化的重点是什么。。。考虑到这两种情况下,代码显然都不起作用。@StephenC:没有意义。然而,检查和理解这样毫无意义的情况可以帮助了解编译器在非平凡的情况下会做什么。(例如,在这里,您可能会了解到以前不知道的gcc和clang消除死代码的限制,以及如何处理这些限制。)您的更新是否显示了尾部调用消除。您忽略了一点,即无法对原始代码执行优化。。。因为这不是尾声。(即使忽略原始示例并没有终止。)从技术角度来说,作为一个once优化器设计器,除非编译器被设置为识别/优化递归,否则它不会注意到调用是递归调用,因此也不会发现以下代码无法访问。即使观察到递归,处理上述情况也没有任何好处。
int r (int x) {
    return r (x);
}
r:
.L2:
    jmp .L2
r:                                      # @r
    ret