混淆了简单的汇编代码(IA32)

Confused with simple assembly code (IA32)

考虑以下 C 函数:

void f1(int i) 
{ 
    int j=i+a; 
}

int f2(int i) 
{
    return i+a; 
}

及其汇编语言的翻译(由教师提供):

#f1 translation :

subl , %esp 
movl 12(%esp), %eax 
movl %eax, 4(%esp) 
movl 4(%esp), %eax 
addl a, %eax 
movl %eax, (%esp) 
addl , %esp 
ret

#f2 translation :

subl , %esp 
movl 12(%esp), %eax 
movl %eax, 4(%esp) 
movl a, %eax 
movl %eax, (%esp) 
movl (%esp), %eax 
addl 4(%esp), %eax 
addl , %esp 
ret

我试图画出并记下这两个汇编代码的每一步,但我根本看不出这两个代码如何导致不同的 C 代码。

按照惯例,寄存器%eax 包含函数的返回值。如果我没记错的话,寄存器 %eaxBOTH[ 的末尾包含值 (i+a) =38=]汇编代码虽然f1returns什么都没有

1) 这是为什么呢?究竟是什么告诉一个函数正在返回一个值?

此外,在这两个代码中,我们都有两行,如下所示:

movl %eax, (%esp) 
movl (%esp), %eax

最后一个好像是多余的,2)是不是?

如果 ABI 说 EAX 包含 return 值,那么函数 return 某些东西将在那里具有 return 值。如果函数没有 return 任何内容,则寄存器可能包含任何内容。在这种情况下它可能是相同的值,我没有阅读代码。

如果调用函数不读取 return 值,则该寄存器包含的内容无关紧要。所以这都是关于调用者和被调用函数的。他们必须遵守 ABI。如果调用 void 函数,调用代码将永远不会尝试将该寄存器用作任何东西。

所以汇编代码中没有任何内容说明函数 return 是什么。都在C代码里了。

至于2,MOV是多余的。这是因为您没有进行优化编译,所以编译器只会输出它想要的任何简单的东西,而且非常不理想。

如果您在启用优化的情况下查看编译器输出,就会更容易理解其中的区别:

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 被完全优化掉了,因为它没有外部可见的效果(没有 return 值,也没有副作用)。当函数 returns 时,局部变量消失(超出范围),因此根本不需要计算它。 (因为它不是 volatile

可能您的教授试图说明局部变量是如何存储在堆栈中的。 (C 称之为 "automatic" 存储,与动态(malloc)或静态(全局变量和 static)相对。)

gcc -O0 太吵了,无法很好地说明这一点,尤其是。它将 args 从 return 地址上方复制到 locals.

的方式

gcc -O0 大多只是将每个 C 语句直接翻译成 asm,而不考虑函数的其余部分。而且,变量在语句之间存储/重新加载,而不是留在寄存器中。 (除了有时它们确实作为大型表达式的一部分保持活动状态)。

gcc -Og 仅略微优化,与源代码相对应。它仍然将 f1 优化为一个空函数。 -O1.

也是