首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么GCC分配的堆栈内存比需要的要多?

为什么GCC分配的堆栈内存比需要的要多?
EN

Stack Overflow用户
提问于 2022-02-02 09:33:33
回答 1查看 433关注 0票数 8

我正在阅读“计算机系统:程序员的观点,3/E”(CS:APP3e),下面的代码就是这本书中的一个例子:

代码语言:javascript
复制
long call_proc() {
    long  x1 = 1;
    int   x2 = 2;
    short x3 = 3;
    char  x4 = 4;
    proc(x1, &x1, x2, &x2, x3, &x3, x4, &x4);
    return (x1+x2)*(x3-x4);
}

这本书给出了GCC生成的装配代码:

代码语言:javascript
复制
long call_proc()
call_proc:
    ; Set up arguments to proc
    subq    $32, %rsp           ; Allocate 32-byte stack frame
    movq    $1, 24(%rsp)        ; Store 1 in &x1
    movl    $2, 20(%rsp)        ; Store 2 in &x2
    movw    $3, 18(%rsp)        ; Store 3 in &x3
    movb    $4, 17(%rsp)        ; Store 4 in &x4
    leaq    17(%rsp), %rax      ; Create &x4
    movq    %rax, 8(%rsp)       ; Store &x4 as argument 8
    movl    $4, (%rsp)          ; Store 4 as argument 7
    leaq    18(%rsp), %r9       ; Pass &x3 as argument 6
    movl    $3, %r8d            ; Pass 3 as argument 5
    leaq    20(%rsp), %rcx      ; Pass &x2 as argument 4
    movl    $2, %edx            ; Pass 2 as argument 3
    leaq    24(%rsp), %rsi      ; Pass &x1 as argument 2
    movl    $1, %edi            ; Pass 1 as argument 1
    ; Call proc
    call    proc
    ; Retrieve changes to memory
    movslq  20(%rsp), %rdx      ; Get x2 and convert to long
    addq    24(%rsp), %rdx      ; Compute x1+x2
    movswl  18(%rsp), %eax      ; Get x3 and convert to int
    movsbl  17(%rsp), %ecx      ; Get x4 and convert to int
    subl    %ecx, %eax          ; Compute x3-x4
    cltq                        ; Convert to long
    imulq   %rdx, %rax          ; Compute (x1+x2) * (x3-x4)
    addq    $32, %rsp           ; Deallocate stack frame
    ret                         ; Return

我可以理解这段代码:编译器在堆栈上分配32字节的空间,其中前16个字节保存传递给proc的参数,最后16个字节包含4个局部变量。

然后,我使用优化标志-Og在GCC 11.2上测试了这段代码,并得到了以下汇编代码:

代码语言:javascript
复制
call_proc():
        subq    $24, %rsp
        movq    $1, 8(%rsp)
        movl    $2, 4(%rsp)
        movw    $3, 2(%rsp)
        movb    $4, 1(%rsp)
        leaq    1(%rsp), %rax
        pushq   %rax
        pushq   $4
        leaq    18(%rsp), %r9
        movl    $3, %r8d
        leaq    20(%rsp), %rcx
        movl    $2, %edx
        leaq    24(%rsp), %rsi
        movl    $1, %edi
        call    proc(long, long*, int, int*, short, short*, char, char*)
        movslq  20(%rsp), %rax
        addq    24(%rsp), %rax
        movswl  18(%rsp), %edx
        movsbl  17(%rsp), %ecx
        subl    %ecx, %edx
        movslq  %edx, %rdx
        imulq   %rdx, %rax
        addq    $40, %rsp
        ret

我注意到gcc首先为4个局部变量分配了24个字节。然后使用pushq向堆栈添加2个参数,因此最后的代码使用addq $40, %rsp释放堆栈空间。

与书中的代码相比,GCC在这里分配了8个字节的空间,而且它似乎没有使用额外的空间。为什么它需要额外的空间?

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-02-03 03:18:27

(本答复是Antti Haapala、klutt和Peter Cordes上述评论的摘要。)

GCC分配比“必需”更多的空间,以确保堆栈对齐,以便调用proc:堆栈指针必须调整为16倍加8(即奇数倍数为8)。为什么x86-64 / AMD64系统V要求16字节堆栈对齐?

奇怪的是,书中的代码没有这样做;如图所示,代码将违反ABI,如果proc实际上依赖于正确的堆栈对齐(例如使用对齐的SSE2指令),它可能会崩溃。

因此,要么书中的代码被错误地从编译器输出中复制,要么书的作者使用了一些不寻常的编译器标志来改变ABI。

现代GCC 11.2发出的asm几乎完全相同(哥德波特使用-Og -mpreferred-stack-boundary=3 -maccumulate-outgoing-args,前者改变-Og -mpreferred-stack-boundary=3 -maccumulate-outgoing-args,使其只保持2^3字节的堆栈对齐,低于默认的2^4。)(以这种方式编译的代码不能安全地调用任何正常编译的函数,甚至是标准库函数。) -maccumulate-outgoing-args在以前的GCC中是默认的,但现代CPU有一个“堆栈引擎”,使得push/pop单独运行,因此这个选项不再是默认的;推到堆栈isn可以节省一点代码大小。

与书中的asm不同的是,在调用之前有一个movl $0, %eax,因为没有原型,所以调用方必须假设它可能是可变的,并且传递给AL = XMM寄存器中的FP args的数量。(与所通过的args匹配的原型可以防止这种情况发生。)其他指令都是一样的,与这本书使用的GCC版本相同,除了在call proc返回后选择寄存器:它最终使用的是movslq %edx, %rdx而不是cltq (用RAX进行符号扩展)。

CS:APP 3e全球版是由出版商(而不是作者)推出的因实践中的错误而臭名昭著,但显然这段代码也出现在北美版中。因此,这可能是作者使用带有奇怪选项的实际编译器输出的错误/选择。与一些糟糕的全球版本实践问题不同,这段代码可以不受GCC版本的修改,但只能使用非标准选项。

相关:为什么GCC在堆栈上分配的空间超出了对齐所需的范围? - GCC有一个漏掉的优化错误,它有时会保留额外的16个字节,这是它确实不需要的。不过,这里不是这样的。

票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70953275

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档