考虑以下C函数:
void f1(int i)
{
int j=i+a;
}
int f2(int i)
{
return i+a;
}
以汇编语言翻译(由讲师提供):
#f1 translation :
subl $8, %esp
movl 12(%esp), %eax
movl %eax, 4(%esp)
movl 4(%esp), %eax
addl a, %eax
movl %eax, (%esp)
addl $8, %esp
ret
#f2 translation :
subl $8, %esp
movl 12(%esp), %eax
movl %eax, 4(%esp)
movl a, %eax
movl %eax, (%esp)
movl (%esp), %eax
addl 4(%esp), %eax
addl $8, %esp
ret
我试图绘制并记下两个汇编代码的每一步,但我根本无法看到两者如何导致不同的C代码。
按照惯例,寄存器%eax 包含函数的返回值。如果我没弄错的话,寄存器%eax 在 BOTH 汇编代码的末尾包含值(i + a),尽管 f1 不返回任何内容。
1)为什么?究竟是什么告诉函数返回一个值?
此外,在这两个代码中,我们有两行,如下两行:
movl %eax, (%esp)
movl (%esp), %eax
最后一个似乎是多余的, 2)或者不是吗?
答案 0 :(得分:5)
如果ABI说EAX
包含返回值,则返回某些内容的函数将返回值。如果函数没有返回任何内容,则寄存器可能包含任何内容。在这种情况下,它可能是相同的值,我没有阅读代码。
如果调用函数没有读取返回值,则该寄存器包含的内容无关紧要。所以关于调用者和被调用函数都是如此。他们必须遵守ABI。如果调用void函数,调用代码将永远不会尝试将该寄存器用作任何东西。
因此汇编代码中没有任何内容表示该函数返回了一些内容。它全部在C代码中。
至于2,MOV
是多余的。这是因为你没有使用优化进行编译,所以编译器只会输出它想要的任何简单的东西,而且非常不理想。
答案 1 :(得分:1)
如果您在启用优化的情况下查看编译器输出,则更容易理解差异:
gcc 5.3 with -O3 -m32
on the Godbolt Compiler Explorer:
int a = 1234; // global, not static or const, so it has to get loaded from memory
void f1(int i) { int j=i+a; }
// 3 : warning: unused variable 'j' [-Wunused-variable]
ret
int f2(int i) { return i+a; }
movl a, %eax # load a
addl 4(%esp), %eax # add i from its arg-passing location on the stack
ret
完全优化 f1
,因为它没有外部可见效果(没有返回值,没有副作用)。当函数返回时,局部变量消失(超出范围),因此根本不需要计算它。 (因为它不是volatile
)
可能你的教授试图说明本地人如何存储在堆栈上。 (C调用"自动"存储,而不是动态(malloc)或静态(全局和static
)。)
gcc -O0
太吵了,不能很好地说明这一点,尤其是它将args从返回地址复制到本地人的方式。
gcc -O0
主要只是将每个C语句直接转换为asm,而不考虑函数的其余部分。而且,变量在语句之间存储/重新加载,而不是保持在寄存器中。 (除非有时他们确实留在现场作为大表达的一部分)。
gcc -Og
仅略微优化,并且相当好地对应于来源。它仍然将f1
优化为空函数。 -O1
也是如此。