C语言 为什么函数堆栈帧中的参数、变量和帧指针之间存在间隙?



我有以下c程序:

void function(int a, int b, int c) {
char buffer1[]="aaaaa";
char buffer2[]="bbbbbbbbbb";
}
int main() {
function(1,2,3);
return 0;

}

当我在执行函数时打印帧信息时,我得到以下 gdb 输出:

(gdb) info frame
Stack level 0, frame at 0x7fffffffe1c0:
rip = 0x40119b in function (ss1.c:4); saved rip = 0x4011ca
called by frame at 0x7fffffffe1d0
source language c.
Arglist at 0x7fffffffe1b0, args: a=1, b=2, c=3
Locals at 0x7fffffffe1b0, Previous frame's sp is 0x7fffffffe1c0
Saved registers:
rbp at 0x7fffffffe1b0, rip at 0x7fffffffe1b8
(gdb) 

打印函数参数和局部变量的地址时,我得到:

(gdb) p/x &c
$65 = 0x7fffffffe184
(gdb) p/x &b
$66 = 0x7fffffffe188
(gdb) p/x &a
$67 = 0x7fffffffe18c
(gdb) p/x &buffer1
$68 = 0x7fffffffe197
(gdb) p/x &buffer2
$69 = 0x7fffffffe19d
  1. 为什么 arg a 的地址和 var buffer1 的地址之间有 11 个字节的间隙 - 而不仅仅是 a 大小的 4 个字节的间隙?

  2. 为什么 buffer2 的地址和帧指针 (0x7fffffffe1b0( 之间有 19 个字节的间隙,而不仅仅是 11 个字节的间隙,即缓冲区 2 的大小?

谢谢

这应该会让你走上正确的道路,但并不能回答实际的差距:

  • 堆栈向下增长,您正在尝试向上读取它
  • 您所看到的&a&b&c不是传递的参数,而是本地存储,如果您(例如(在function()内说a=1可以使用。我相信如果你不做 -O0,这些就会得到优化
  • abc通过寄存器传递到函数而不是堆栈,所以你不会在那里找到它们两次。 即调用者不会将它们推送到堆栈。
  • buffer1buffer2在堆栈中未对齐,而是作为字符串打包在一起。

例如,在buffer2之后(之前(,您可以找到保存的RBP值,然后找到退货地址。给我的:

(gdb) p &buffer1
$102 = (char (*)[6]) 0x7fffffffde82
(gdb) p &buffer2
$103 = (char (*)[11]) 0x7fffffffde77

(buffer1结束于0x7fffffffde87(

然后保存RBP

(gdb) p/x (char [8]) *0x7fffffffde88
$104 = {0xb0, 0xde, 0xff, 0xff, 0xff, 0x7f, 0x0, 0x0}

然后是退货地址:

(gdb) p/x (char [8]) *0x7fffffffde90
$105 = {0x80, 0x51, 0x55, 0x55, 0x55, 0x55, 0x0, 0x0}

你也可以从gdb中看到:

(gdb) info frame
Stack level 0, frame at 0x7fffffffde98:
rip = 0x55555555513f in function (c.c:3); saved rip = 0x555555555180
^^^^^^^^^^^^^^
called by frame at 0x7fffffffdec0
source language c.
Arglist at 0x7fffffffde88, args: a=1, b=2, c=3
Locals at 0x7fffffffde88, Previous frame's sp is 0x7fffffffde98
Saved registers:
rbp at 0x7fffffffde88, rip at 0x7fffffffde90
^^^^^^^^^^^^^^^^^^^^^^

您还可以通过查看汇编代码来查看以下内容:

gcc -S c.c -o c.s

或者,如果您更喜欢英特尔:

gcc -masm=intel -S c.c -o c.s

我不知道为什么 gcc 会留下这个空白:

mov     DWORD PTR -36[rbp], edi
mov     DWORD PTR -40[rbp], esi
mov     DWORD PTR -44[rbp], edx
mov     DWORD PTR -6[rbp], 1633771873  <-- aaaa
mov     WORD PTR -2[rbp], 97           <-- a
movabs  rax, 7089336938131513954       <-- bbbbbbbb
mov     QWORD PTR -17[rbp], rax
mov     WORD PTR -9[rbp], 25186        <-- bb
mov     BYTE PTR -7[rbp], 0            <-- 

只需运行简单的程序:

#include <stdio.h>
void function(int a, int b, int c) 
{
char buffer1[]="aaaaa";
char buffer2[]="bbbbbbbbbb";
printf("%p = &an", &a);
printf("%p = &bn", &b);
printf("%p = &cn", &c);
printf("%p = &buffer1 sizeof(buffer1) = %zun", buffer1, sizeof(buffer1));
printf("%p = &buffer2 sizeof(buffer2) = %zun", buffer2, sizeof(buffer2));
printf("%zu = &buffer - &an", (char *)buffer1 - (char *)&a);
}
int main() 
{
function(1,2,3);
return 0;
}

结果完全符合预期。

0x7fff9d9d830c = &a                                                                                                                                                                                                                                           
0x7fff9d9d8308 = &b                                                                                                                                                                                                                                           
0x7fff9d9d8304 = &c                                                                                                                                                                                                                                           
0x7fff9d9d8310 = &buffer1 sizeof(buffer1) = 6                                                                                                                                                                                                                 
0x7fff9d9d8320 = &buffer2 sizeof(buffer2) = 11                                                                                                                                                                                                                
4 = &buffer - &a 

尝试在您的系统上运行它。

编译器通常遵循效率定义的ABI规范,以便优化通过寄存器传递的参数,对齐和代码中可能的深度嵌套表达式的空间。 例如,英特尔 ABI 规范表示,堆栈指针在函数调用时扩展,参数为 16 字节的倍数,因此所有类型的对齐都是可以的。 因此,在进入时,只用一个SP减去局部变量的空间,然后开始执行代码,直到我们需要另一个空间块,这是很正常的。 ABI 规范说明了哪些寄存器用于传递参数,哪些寄存器在调用中必须遵守,哪些可以销毁,哪些用于链接堆栈帧(通常是英特尔中的 EBP(等。 这允许编译器相互依赖(语言之间的接口(,同时实现优化的代码并确保程序效率。 这就是您在进入/退出过程调用中看到一些明显的堆栈内存丢失的原因。

最新更新