c - 为什么c程序为未使用的局部变量保留空间?

标签 c assembly x86-64

<分区>

我正在阅读从头开始编程。 pdf地址:http://mirror.ossplanet.net/nongnu/pgubook/ProgrammingGroundUp-0-8.pdf

我很好奇 Page37 为局部变量预留的空间。 他说,我们需要 2 个字的内存,所以将堆栈指针向下移动 2 个字。 执行这条指令:subl $8, %esp 所以,在这里,我想我明白了。

但是,我写了c代码来验证这个预留空间。

#include <stdio.h>

int test(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9, int a10, int a11, int a12) {
    printf("a1=%#x, a2=%#x, a3=%#x, a4=%#x, a5=%#x, a6=%#x, a7=%#x, a8=%#x, a9=%#x, a10=%#x, a11=%#x, a12=%#x", a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12);

    return 0;
}

int main(void){
    test(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12);
    printf("Wick is me!");

    return 0;
}

然后,我使用 gcc 转换为可执行文件,gcc -Og -g,并使用 gdb 调试器。

我使用 disass 到 main 函数,并复制了下面的一些 asm 代码。

   0x000055555555519d <+0>: endbr64 
   0x00005555555551a1 <+4>: sub    $0x8,%rsp  # reserve space?
   0x00005555555551a5 <+8>: pushq  $0x12
   0x00005555555551a7 <+10>:    pushq  $0x11
   0x00005555555551a9 <+12>:    pushq  $0x10
   0x00005555555551ab <+14>:    pushq  $0x9
   0x00005555555551ad <+16>:    pushq  $0x8
   0x00005555555551af <+18>:    pushq  $0x7
   0x00000000000011b1 <+20>:    mov    $0x6,%r9d
   0x00000000000011b7 <+26>:    mov    $0x5,%r8d
   0x00000000000011bd <+32>:    mov    $0x4,%ecx
   0x00000000000011c2 <+37>:    mov    $0x3,%edx
   0x00000000000011c7 <+42>:    mov    $0x2,%esi
   0x00000000000011cc <+47>:    mov    $0x1,%edi
   0x00000000000011d1 <+52>:    callq  0x1149 <test>
   0x00000000000011d6 <+57>:    add    $0x30,%rsp
   0x00000000000011da <+61>:    lea    0xe89(%rip),%rsi        # 0x206a
   0x00000000000011e1 <+68>:    mov    $0x1,%edi
   0x00000000000011e6 <+73>:    mov    $0x0,%eax
   0x00000000000011eb <+78>:    callq  0x1050 <__printf_chk@plt>
   0x00000000000011f0 <+83>:    mov    $0x0,%eax
   0x00000000000011f5 <+88>:    add    $0x8,%rsp
   0x00005555555551f9 <+92>:    retq

我怀疑这是预留空间指令。然后,我逐行执行汇编代码并检查堆栈中的内容。

为什么这条指令只有sub 8字节,而且0x7fffffffe390好像是main函数的返回地址。这不应该是预留空间吗?

下面是 rsp 地址附近的内容。 i r $rsp, x/40xb rsp 地址

0x7fffffffe390: 0x00    0x52    0x55    0x55    0x55    0x55    0x00    0x00   => after sub
0x7fffffffe398: 0xb3    0x20    0xdf    0xf7    0xff    0x7f    0x00    0x00   => before sub

然后,我执行所有 pushq 指令,并使用 x/64xb 0x7fffffffe360

0x7fffffffe360: 0x07    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffe368: 0x08    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffe370: 0x09    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffe378: 0x10    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffe380: 0x11    0x00    0x00    0x00    0x00    0x00    0x00    0x00
0x7fffffffe388: 0x12    0x00    0x00    0x00    0x00    0x00    0x00    0x00

above is local variables
==========================

0x7fffffffe390: 0x00    0x52    0x55    0x55    0x55    0x55    0x00    0x00
0x7fffffffe398: 0xb3    0x20    0xdf    0xf7    0xff    0x7f    0x00    0x00

我认为0x7fffffffe390~0x7fffffffe398是局部变量的预留空间,但它没有变化!是不是我的测试方式不对?

执行环境:

  • GDB 版本:9.2
  • 海湾合作委员会版本:9.4.0
  • 操作系统:x86_64 GNU/Linux

最佳答案

x86-64 SysV ABI 要求堆栈在调用时是 16 位对齐的。 由于调用指令将 8 字节的返回地址压入堆栈,因此在函数开始时堆栈总是错位 8,如果要进行嵌套调用,则调用者需要压入奇数个8 个字节到堆栈,使其再次 16 位对齐。

由于您的函数采用 12 个整数参数,其中 6 个以每个 8 字节的形式进入堆栈,因此需要在堆栈参数之前将额外的 8 字节压入堆栈,因此堆栈在调用之前是 16 位对齐的.

如果您的函数有 11 个参数(或任何其他 6 个(寄存器参数)+奇数堆栈参数),则不需要额外的堆栈压入。

Gcc 和 clang 仍然奇怪地生成 sub rsp, 16 (gcc) 和 push rax; sub rsp, 8; (clang) 对于这种情况 ( https://gcc.godbolt.org/z/jGj5WPq8c )。我不明白为什么。

关于c - 为什么c程序为未使用的局部变量保留空间?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72616943/

相关文章:

c - 如何在 ubuntu 中使用 bochs?

assembly - “PTR” 和 “NEAR PTR” 和有什么区别?

c - 通过函数调用了解简单 C 程序的 x86-64 汇编

c - 是否可以将 stdout 重定向到 C 中的两个位置?

C 代码因 memmove 崩溃

windows - 为什么PE需要Original First Thunk(OFT)?

c - 已编译二进制文件中的预初始化函数指针?

c++ - 具有非原子大小项目的无锁双端队列

c++ - x86-64 调用约定中的返回值

c - 运行最小 OpenMP 程序时的非法指令