【发布时间】:2014-08-19 19:16:40
【问题描述】:
鉴于此代码:
int x;
int a (int b) {
b = a (b);
b += x;
return b;
}
为什么 GCC 会返回此输出(英特尔语法):http://goo.gl/8D32F1 - Godbolt 的 GCC Explorer
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 语法):http://goo.gl/Zz2rKA - Godbolt 的 Clang Explorer
a: # @a
pushq %rax
callq a
addl x(%rip), %eax
popq %rdx
ret
当部分代码明显无法访问时?因为函数的第一条语句是
b = a (b);
该函数将永远递归地调用自身(直到堆栈溢出并且您得到一个段错误)。这意味着你永远不会超出那条线,因此,其余的代码是无法访问的。可达性优化理论上应该去掉代码,对吗?
两个编译器都在 x64 上运行并带有以下标志
-
-O3- 最大优化 -
-march=native- [不必要] 尽可能使用特定于机器的优化 -
-x c- 假设输入语言是 C
我在想他们应该返回更多类似以下内容(双关语)的东西:
GCC(英特尔语法):
a:
.L1:
jmp .L1
Clang(AT&T 语法):
a:
.LBB0_1:
jmp .LBB0_1
注意:这些样本是根据以前的观察结果手工编写的,可能不正确。
总体而言,由于其余代码无法访问,为什么任何一个编译器都不会将函数折叠为单个递归跳转?
编辑:
回应 Jack 关于语义等价的评论:
对于以下代码:
int j (int x) {
while (1) {};
x++;
return x;
}
GCC 返回:http://goo.gl/CYSUW2
j:
.L2:
jmp .L2
Clang 返回:
j: # @j
.LBB0_1: # =>This Inner Loop Header: Depth=1
jmp .LBB0_1
回应 Adam 关于爆栈的评论:
对于此代码:
int r (int x) {
return r (x);
}
GCC 生成递归跳转:http://goo.gl/eWo2Nb
r:
.L2:
jmp .L2
Clang 早早回归:http://goo.gl/CVJKiZ
r: # @r
ret
【问题讨论】:
-
@StephenC:但这是编译器为所欲为的借口。默默地将“吹堆栈”转换为“永远循环”是完全可以的。
-
@HotLicks - 事实上,为了优化涉及错误/病态代码的情况,几乎可以肯定编译器编写者的努力是一种浪费。对于 OP - 优化的 point 是什么......鉴于代码显然在任何一种情况下都不起作用。
-
@StephenC:没有意义。但是,检查和理解像这样毫无意义的情况可以揭示编译器在不平凡的情况下会做什么。 (例如,在这里,您可能会了解您以前不知道的 gcc 和 clang 死代码消除的限制,以及如何处理它们。)
-
您的更新显示了尾调用消除。您错过了不能对原始代码执行优化的观点......因为它不是尾调用。 (即使忽略原始示例不会终止。)
-
从技术角度来看,作为曾经的优化器设计者,除非编译器设置为识别/优化递归,否则它不会注意到调用是递归的,因此不会发现以下代码无法访问。即使观察到递归,处理上述情况也没有任何价值。
标签: c gcc optimization assembly clang