c和gcc:64位机器的堆栈增长和对齐

9
我有以下程序。我想知道为什么在64位机器上会输出-4?我的哪些假设出了问题?
[Linux ubuntu 3.2.0-23-generic #36-Ubuntu SMP Tue Apr 10 20:39:51 UTC 2012 x86_64 x86_64 x86_64 GNU/Linux]
1. 在上述机器和gcc编译器中,默认情况下应该先将b推入栈,然后才是a。栈向下增长。因此,b应该具有更高的地址,而a具有较低的地址。所以结果应该是正数。但我得到了-4。有人能解释一下吗?
2. 参数是两个字符,占用堆栈帧中的2个字节。但我看到的差异是4,而我期望的是1。即使有人说这是由于对齐引起的,我也想知道一个只有2个字符的结构体不会被对齐到4个字节。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void CompareAddress(char a, char b)
{
    printf("Differs=%ld\n", (intptr_t )&b - (intptr_t )&a);
}

int main()
{
    CompareAddress('a','b');
    return 0; 
}

/* Differs= -4 */
3个回答

9

这是我的猜测:

在Linux的x64上,调用约定规定前几个参数通过寄存器传递。

因此,在您的情况下,ab都是通过寄存器传递而不是通过堆栈传递。但是,由于您获取了它的地址,编译器将在函数被调用将其存储在堆栈的某个位置。
(不一定按照向下顺序。)

也有可能该函数被直接内联。

无论哪种情况,编译器都会创建临时堆栈空间来存储变量。这些变量可以按任何顺序进行优化。因此,它们可能不会按照您预期的任何特定顺序排列。


1
如果有人感兴趣,为了避免跟随维基百科的引用,官方的AMD64 ABI文档可以在这里找到:http://www.x86-64.org/documentation/abi.pdf - Michael Burr

5

回答这种关于特定平台上特定编译器行为的问题最好的方法是查看汇编代码。您可以通过使用 -S 标志(-fverbose-asm 标志也很不错)使 gcc 转储其汇编代码。

gcc -S -fverbose-asm file.c

提供一个类似于file.s的文件(我已经删除了所有不相关的部分,括号中的部分是我的注释):

CompareAddress:
        # ("allocate" memory on the stack for local variables)
        subq    $16, %rsp       
        # (put a and b onto the stack)
        movl    %edi, %edx      # a, tmp62
        movl    %esi, %eax      # b, tmp63
        movb    %dl, -4(%rbp)   # tmp62, a
        movb    %al, -8(%rbp)   # tmp63, b 
        # (get their addresses)
        leaq    -8(%rbp), %rdx  #, b.0
        leaq    -4(%rbp), %rax  #, a.1
        subq    %rax, %rdx      # a.1, D.4597 (&b - &a)
        # (set up the parameters for the printf call)
        movl    $.LC0, %eax     #, D.4598
        movq    %rdx, %rsi      # D.4597,
        movq    %rax, %rdi      # D.4598,
        movl    $0, %eax        #,
        call    printf  #

main:
        # (put 'a' and 'b' into the registers for the function call)
        movl    $98, %esi       #,
        movl    $97, %edi       #,
        call    CompareAddress

(这个问题很好地解释了[re]bp[re]sp是什么。)

差异为负的原因是堆栈向下增长:即如果您将两个内容推入堆栈,则先推入的那个内容地址更大,而ab之前被推入。

之所以是-4而不是-1,是因为编译器决定将参数对齐到4字节边界更“好”,可能是因为32位/64位CPU处理4字节比处理单个字节更好。

(此外,查看汇编程序可以显示-mpreferred-stack-boundary的效果:它实际上意味着在堆栈上分配内存的大小不同。)


0

我认为程序给出的答案是正确的,GCC 的默认首选堆栈边界是 4,你可以在 GCC 选项中设置 -mpreferred-stack-boundary=num 来改变堆栈边界,然后程序将根据你的设置给出不同的答案。


我尝试了 -mpreferred-stack-boundary 值从 4 到 12。但结果仍然相同。 - Lunar Mushrooms

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接