4

我正在查看内存在堆栈上的布局方式,但我不明白为什么看起来有 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.
4

1 回答 1

3

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

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

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

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

示范

C代码:

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

汇编代码:

功能:
    subq $24, %rsp ; 在堆栈上分配 24 个字节
    movl %edi, 12(%rsp) ; 将“值”存储在堆栈上
    泄漏 12(%rsp), %rdi ; 计算'value'的地址
    调用其他函数;调用“其他函数”
    xorl    %eax, %eax      ; Return value 0
    addq    $24, %rsp       ; Deallocate stack
    ret                     ; Return

Remember that %rsp is the stack pointer, %edi / %rdi is the first parameter to a function, and %eax is the return value of a function.

于 2013-11-03T00:10:07.373 回答