我使用 clang
和 gcc
编译了以下代码,并使用 -O3
调用了它们:
#include <stdio.h>
#include <stdlib.h>
static void a(int n) {
if (n == 0) return;
printf("descending; a=%i\n", n);
a(n-1);
}
int main() {
a(5);
return 0;
}
这是 gcc
生成的主函数(末尾没有 NOP):
08048310 <main>:
8048310: 55 push %ebp
8048311: 89 e5 mov %esp,%ebp
8048313: 83 e4 f0 and $0xfffffff0,%esp
8048316: 83 ec 10 sub $0x10,%esp
8048319: c7 44 24 04 05 00 00 movl $0x5,0x4(%esp)
8048320: 00
8048321: c7 04 24 14 85 04 08 movl $0x8048514,(%esp)
8048328: e8 c7 ff ff ff call 80482f4 <printf@plt>
804832d: c7 44 24 04 04 00 00 movl $0x4,0x4(%esp)
8048334: 00
8048335: c7 04 24 14 85 04 08 movl $0x8048514,(%esp)
804833c: e8 b3 ff ff ff call 80482f4 <printf@plt>
8048341: c7 44 24 04 03 00 00 movl $0x3,0x4(%esp)
8048348: 00
8048349: c7 04 24 14 85 04 08 movl $0x8048514,(%esp)
8048350: e8 9f ff ff ff call 80482f4 <printf@plt>
8048355: c7 44 24 04 02 00 00 movl $0x2,0x4(%esp)
804835c: 00
804835d: c7 04 24 14 85 04 08 movl $0x8048514,(%esp)
8048364: e8 8b ff ff ff call 80482f4 <printf@plt>
8048369: c7 44 24 04 01 00 00 movl $0x1,0x4(%esp)
8048370: 00
8048371: c7 04 24 14 85 04 08 movl $0x8048514,(%esp)
8048378: e8 77 ff ff ff call 80482f4 <printf@plt>
804837d: 31 c0 xor %eax,%eax
804837f: c9 leave
8048380: c3 ret
这是来自 clang
的那个:
080483d0 <main>:
80483d0: 55 push %ebp
80483d1: 89 e5 mov %esp,%ebp
80483d3: 56 push %esi
80483d4: 83 ec 0c sub $0xc,%esp
80483d7: be 05 00 00 00 mov $0x5,%esi
80483dc: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
80483e0: 89 74 24 04 mov %esi,0x4(%esp)
80483e4: c7 04 24 e0 84 04 08 movl $0x80484e0,(%esp)
80483eb: e8 04 ff ff ff call 80482f4 <printf@plt>
80483f0: 4e dec %esi
80483f1: 75 ed jne 80483e0 <main+0x10>
80483f3: 31 c0 xor %eax,%eax
80483f5: 83 c4 0c add $0xc,%esp
80483f8: 5e pop %esi
80483f9: 5d pop %ebp
80483fa: c3 ret
我的问题是:是否有充分的理由说明它们都生成了一遍又一遍地在堆栈上写入静态字符串地址的代码?例如,为什么 clang
生成的代码不是这样的?
080483d0 <main>:
80483d0: 55 push %ebp
80483d1: 89 e5 mov %esp,%ebp
80483d3: 56 push %esi
80483d4: 83 ec 0c sub $0xc,%esp
80483d7: be 05 00 00 00 mov $0x5,%esi
80483dc: 8d 74 26 00 lea 0x0(%esi,%eiz,1),%esi
80483e0: c7 04 24 e0 84 04 08 movl $0x80484e0,(%esp)
80483e7: 89 74 24 04 mov %esi,0x4(%esp)
80483eb: e8 04 ff ff ff call 80482f4 <printf@plt>
80483f0: 4e dec %esi
80483f1: xx xx jne 80483e7 <main+0x17>
80483f3: 31 c0 xor %eax,%eax
80483f5: 83 c4 0c add $0xc,%esp
80483f8: 5e pop %esi
80483f9: 5d pop %ebp
80483fa: c3 ret
最佳答案
在 Mac OS X 应用程序二进制接口(interface)中,堆栈上传递的参数可能会被调用的例程修改。因此,在调用例程将格式字符串的地址放入堆栈的位置,允许被调用例程写入堆栈中的那个位置。 (通常不允许写入堆栈上更高[较早]的位置。)因此,调用例程无法知道,在被调用例程返回后,调用例程写入堆栈的参数没有改变。
这对于在使用它们执行计算时可能会修改其参数的例程来说是一种便利。例如,您可以编写如下代码:
int foo(int x)
{
x *= x;
return x+3;
}
显然,对于这么简单的代码,编译器实际上不需要将乘积存储在 x 中来完成返回值的计算。但是,对于更复杂的例程,您可以看到编译器可能决定将值存储到 x 的位置。
作为 ABI 设计问题,您可能想知道禁止被调用例程使用此空间是否更好,以便调用者可以依赖值不变。但是,以这种方式重复使用一个参数并不常见,而且编写新副本的成本很小。
另请注意,只有写入堆栈的格式字符串的地址可以更改。 C 语义要求格式字符串的内容 被调用的例程保持不变,因为它是 const char。
关于c - 为什么编译器生成的代码会一遍又一遍地在同一内存位置写入相同的内容?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11970378/