c - 为什么在 Ubuntu 上的 C 中,堆栈上的参数和局部变量有 12 个字节?

标签 c

我正在查看内存在堆栈上的布局方式,但我不明白为什么看起来有 12 个字节的空间用于存储每个变量。这是一个简单的 C 程序,它打印出各种变量的位置:

#include <stdio.h>
#include <stdlib.h>

int test (long p1, long p2){
    int l1 = 9999;
    int l2 = 99993333;

    printf("%p p1\n", &p1);
    printf("%p p2\n", &p2);
    printf("%p l1\n", &l1);
    printf("%p l1\n", &l2);
}

int main(int argc, const char** argv)
{
    register void* stack asm("esp");
    int x = 22;
    int y = 1000;
    printf("%p stack\n", stack);
    printf("%p argv\n", &argv);
    printf("%p argc\n", &argc);
    printf("%p l1\n", &x);
    printf("%p l2\n", &y);
    test(1, 888);
    return 0;
}

运行时,输出如下:

~/gc$ ./a.out
0x7fff5496b200 stack
0x7fff5496b200 argv
0x7fff5496b20c argc
0x7fff5496b218 l1
0x7fff5496b21c l2
0x7fff5496b1d8 p1
0x7fff5496b1d0 p2
0x7fff5496b1e8 l1
0x7fff5496b1ec l1

为什么argv和argc的地址之间有12个空格,l1和l2之间有12个空格?我希望 long 和指针为 8,对于 main 的 int 参数,我会理解 4 或 8 个字符,但我看不出有任何理由应该是 12。

有人提到汇编代码会有用,所以我也得到了:

Dump of assembler code for function main:
   0x0000000000400614 <+0>: push   %rbp
   0x0000000000400615 <+1>: mov    %rsp,%rbp
   0x0000000000400618 <+4>: sub    $0x20,%rsp
   0x000000000040061c <+8>: mov    %edi,-0x14(%rbp)
   0x000000000040061f <+11>:    mov    %rsi,-0x20(%rbp)
   0x0000000000400623 <+15>:    movl   $0x16,-0x8(%rbp)
   0x000000000040062a <+22>:    movl   $0x3e8,-0x4(%rbp)
   0x0000000000400631 <+29>:    mov    %rsp,%rax
   0x0000000000400634 <+32>:    mov    %rax,%rsi
   0x0000000000400637 <+35>:    mov    $0x40079c,%edi
   0x000000000040063c <+40>:    mov    $0x0,%eax
   0x0000000000400641 <+45>:    callq  0x400410 <printf@plt>
   0x0000000000400646 <+50>:    lea    -0x20(%rbp),%rax
   0x000000000040064a <+54>:    mov    %rax,%rsi
   0x000000000040064d <+57>:    mov    $0x4007a6,%edi
   0x0000000000400652 <+62>:    mov    $0x0,%eax
   0x0000000000400657 <+67>:    callq  0x400410 <printf@plt>
   0x000000000040065c <+72>:    lea    -0x14(%rbp),%rax
   0x0000000000400660 <+76>:    mov    %rax,%rsi
   0x0000000000400663 <+79>:    mov    $0x4007af,%edi
   0x0000000000400668 <+84>:    mov    $0x0,%eax
   0x000000000040066d <+89>:    callq  0x400410 <printf@plt>
   0x0000000000400672 <+94>:    lea    -0x8(%rbp),%rax
   0x0000000000400676 <+98>:    mov    %rax,%rsi
   0x0000000000400679 <+101>:   mov    $0x400780,%edi
   0x000000000040067e <+106>:   mov    $0x0,%eax
   0x0000000000400683 <+111>:   callq  0x400410 <printf@plt>
   0x0000000000400688 <+116>:   lea    -0x4(%rbp),%rax
   0x000000000040068c <+120>:   mov    %rax,%rsi
   0x000000000040068f <+123>:   mov    $0x400787,%edi
   0x0000000000400694 <+128>:   mov    $0x0,%eax
   0x0000000000400699 <+133>:   callq  0x400410 <printf@plt>
   0x000000000040069e <+138>:   mov    $0x14d,%ecx
   0x00000000004006a3 <+143>:   mov    $0x1589e,%edx
   0x00000000004006a8 <+148>:   mov    $0x378,%esi
   0x00000000004006ad <+153>:   mov    $0x1,%edi
   0x00000000004006b2 <+158>:   callq  0x40052c <test>
   0x00000000004006b7 <+163>:   mov    $0x0,%eax
   0x00000000004006bc <+168>:   leaveq 
   0x00000000004006bd <+169>:   retq   
End of assembler dump.
(gdb) disassemble test
Dump of assembler code for function test:
   0x000000000040052c <+0>: push   %rbp
   0x000000000040052d <+1>: mov    %rsp,%rbp
   0x0000000000400530 <+4>: sub    $0x40,%rsp
   0x0000000000400534 <+8>: mov    %rdi,-0x28(%rbp)
   0x0000000000400538 <+12>:    mov    %rsi,-0x30(%rbp)
   0x000000000040053c <+16>:    mov    %rdx,-0x38(%rbp)
   0x0000000000400540 <+20>:    mov    %rcx,-0x40(%rbp)
   0x0000000000400544 <+24>:    movl   $0x270f,-0x18(%rbp)
   0x000000000040054b <+31>:    movq   $0x5f5c6f5,-0x10(%rbp)
   0x0000000000400553 <+39>:    movl   $0x63,-0x14(%rbp)
   0x000000000040055a <+46>:    movq   $0x371,-0x8(%rbp)
   0x0000000000400562 <+54>:    lea    -0x28(%rbp),%rax
   0x0000000000400566 <+58>:    mov    %rax,%rsi
   0x0000000000400569 <+61>:    mov    $0x400764,%edi
   0x000000000040056e <+66>:    mov    $0x0,%eax
   0x0000000000400573 <+71>:    callq  0x400410 <printf@plt>
   0x0000000000400578 <+76>:    lea    -0x30(%rbp),%rax
   0x000000000040057c <+80>:    mov    %rax,%rsi
   0x000000000040057f <+83>:    mov    $0x40076b,%edi
   0x0000000000400584 <+88>:    mov    $0x0,%eax
   0x0000000000400589 <+93>:    callq  0x400410 <printf@plt>
   0x000000000040058e <+98>:    lea    -0x38(%rbp),%rax
   0x0000000000400592 <+102>:   mov    %rax,%rsi
   0x0000000000400595 <+105>:   mov    $0x400772,%edi
   0x000000000040059a <+110>:   mov    $0x0,%eax
   0x000000000040059f <+115>:   callq  0x400410 <printf@plt>
   0x00000000004005a4 <+120>:   lea    -0x40(%rbp),%rax
   0x00000000004005a8 <+124>:   mov    %rax,%rsi
   0x00000000004005ab <+127>:   mov    $0x400779,%edi
   0x00000000004005b0 <+132>:   mov    $0x0,%eax
   0x00000000004005b5 <+137>:   callq  0x400410 <printf@plt>
   0x00000000004005ba <+142>:   lea    -0x18(%rbp),%rax
   0x00000000004005be <+146>:   mov    %rax,%rsi
   0x00000000004005c1 <+149>:   mov    $0x400780,%edi
   0x00000000004005c6 <+154>:   mov    $0x0,%eax
   0x00000000004005cb <+159>:   callq  0x400410 <printf@plt>
   0x00000000004005d0 <+164>:   lea    -0x10(%rbp),%rax
   0x00000000004005d4 <+168>:   mov    %rax,%rsi
   0x00000000004005d7 <+171>:   mov    $0x400787,%edi
   0x00000000004005dc <+176>:   mov    $0x0,%eax
   0x00000000004005e1 <+181>:   callq  0x400410 <printf@plt>
   0x00000000004005e6 <+186>:   lea    -0x14(%rbp),%rax
   0x00000000004005ea <+190>:   mov    %rax,%rsi
   0x00000000004005ed <+193>:   mov    $0x40078e,%edi
   0x00000000004005f2 <+198>:   mov    $0x0,%eax
   0x00000000004005f7 <+203>:   callq  0x400410 <printf@plt>
   0x00000000004005fc <+208>:   lea    -0x8(%rbp),%rax
   0x0000000000400600 <+212>:   mov    %rax,%rsi
   0x0000000000400603 <+215>:   mov    $0x400795,%edi
   0x0000000000400608 <+220>:   mov    $0x0,%eax
   0x000000000040060d <+225>:   callq  0x400410 <printf@plt>
   0x0000000000400612 <+230>:   leaveq 
   0x0000000000400613 <+231>:   retq   
End of assembler dump.

最佳答案

您使用的是 64 位系统(根据打印的指针大小),这意味着您可能使用的是 x86-64。

x86-64 ABI 中函数的某些参数没有地址,因为它们是在寄存器中传递的。但是,根据 C 标准,您可以使用它们的地址。因此,当您编写 &argc 时,编译器会在堆栈上为其保留空间并返回该地址。

所以它只是另一个局部变量。编译器可以自由地将 argc 放在堆栈的任何位置。此行为不是强制性的,它只是您的编译器的工作方式。

至于为什么 12 恰好在这种特殊情况下成为间距,请记住堆栈在 x86-64 上向下增长。因此,如果将 argc 压入堆栈,堆栈指针将下降 4 个字节,如果将 argv 压入堆栈,它将首先下降另外 4 个字节以进行对齐正确,然后在 argv 被推送后它会下降 8 个字节。当然,编译器可以自由地做其他事情,比如将 argvargc 放在堆栈上的任意其他位置。

演示

C 代码:

void otherfunc(int *ptr);
int func(int value)
{
    otherfunc(&value);
    return 0;
}

汇编代码:

func:
    subq    $24, %rsp       ; Allocate 24 bytes on the stack
    movl    %edi, 12(%rsp)  ; Store 'value' on the stack
    leaq    12(%rsp), %rdi  ; Calculate the address of 'value'
    call    otherfunc       ; Call 'otherfunc'
    xorl    %eax, %eax      ; Return value 0
    addq    $24, %rsp       ; Deallocate stack
    ret                     ; Return

记住 %rsp 是堆栈指针,%edi/%rdi 是函数的第一个参数, %eax 是函数的返回值。

关于c - 为什么在 Ubuntu 上的 C 中,堆栈上的参数和局部变量有 12 个字节?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19748119/

相关文章:

c - 一个父进程有 2 个子进程

c - 如何使用 getrusage 读取 C 中的页面错误?

c - gmtime_r 的奇怪行为

c - 如何在 Eclipse 中运行 cunit 测试?

c - 如何将数据从缓冲区放入 C 中的数组?

C 指针数组未按预期打印,它用最后一个输入替换所有内容

c - 如何将十六进制转换为二进制

c++ - 如何获得 GLuint 纹理的宽度?

c - 如何知道 HTTP header 部分何时结束?

c++ - 需要在 linux 中编写一个守护进程,不确定使用 C++ 还是 C