【问题标题】:Why does my buffer have more memory allocated on the stack than I asked for?为什么我的缓冲区在堆栈上分配的内存比我要求的多?
【发布时间】:2021-12-20 05:14:55
【问题描述】:

这是我的源代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX 500

int main(int argc, char** argv)
{
        if (argc != 2)
                exit(1);
        char str[MAX];
        strcpy(str, argv[1]);
        return 0;
}

disassembled main 使用 gdb 并得到以下结果:

Dump of assembler code for function main:
   0x0000000000001145 <+0>:     push   %rbp
   0x0000000000001146 <+1>:     mov    %rsp,%rbp
   0x0000000000001149 <+4>:     sub    $0x210,%rsp
   .
   .
   .
End of assembler dump.

这里值得注意的是:

0x0000000000001149 &lt;+4&gt;: sub $0x210,%rsp

我的问题是-
为什么有$0x210(528 字节),而应该是$0x1f4(500 字节)?

【问题讨论】:

  • 什么编译器,什么优化选项?
  • @NateEldredge 这是gcc (Debian 10.2.1-6) 10.2.1 20210110,默认设置。
  • 尝试使用#define MAX 100 并注意sub $0xXXX,%rsp 是否低于400。这提供了有关填充以及需要多少 base 内存的见解,即使 MAX 理论上为零。
  • @chux-ReinstateMonica 是的,最近我在考虑填充(松弛字节)。顺便说一句,输出:0x0000000000001149 &lt;+4&gt;: add $0xffffffffffffff80,%rsp
  • @NateEldredge 定义 MAX 0 给了我这个汇编代码:0x0000000000001149 &lt;+4&gt;: sub $0x10,%rsp,即 16 字节的缓冲区。正如@chux-ReinstateMonica 所说。

标签: c assembly x86-64 stack-memory


【解决方案1】:

我猜你正在使用 gcc 并在没有优化的情况下进行编译,例如 this (godbolt)

这里发生了几件事:

首先,在没有优化的情况下编译时,编译器会尝试确保每个局部变量在内存中都有一个地址,以便调试器可以轻松地检查或修改它。这包括函数参数,这些参数在 x86-64 上以其他方式在寄存器中传递。所以编译器需要分配额外的堆栈空间,argcargv 参数可以被“溢出”。您可以在程序集的第 5 行和第 6 行看到溢出:

        movl    %edi, -516(%rbp)
        movq    %rsi, -528(%rbp)

如果你仔细看,你可能会注意到编译器在-520(%rbp) 可用时将argc(来自%edi)放在地址-516(%rbp) 上浪费了4 个字节。不完全清楚为什么,但毕竟,它没有优化!这样我们就达到了 516 个字节。

另一个问题是 x86-64 ABI 需要 16 字节堆栈对齐;见Why does the x86-64 / AMD64 System V ABI mandate a 16 byte stack alignment?。在这种情况下,长话短说,这意味着我们的堆栈调整需要是 16 字节的倍数。 (返回地址和推入的rbp 又增加了 16 个字节,这不会干扰这种对齐。)所以我们的 516 必须四舍五入到 16 的下一个倍数,即 528。

如果编译器更加小心并且不浪费argcargv 之间的4 个字节,我们可以只用512 个字节。不过,使用 528 的一个好处是缓冲区 str 最终以 16 字节对齐。这对于 char 的数组来说不是必需的,它的最小对齐只有 1,但它可以使像 strcpy 这样的字符串函数更有效地使用快速 SIMD 算法。我不确定编译器是故意这样做还是只是巧合。

【讨论】:

  • 好的,告诉我是否正确:有 16 字节的堆栈对齐,所以当要求 500 chars 时,我得到了 512 字节的内存,然后 argc 得到了推送和内存增加了(512+16 = 528)正确吗?
  • @Shubham:Argc 和 argv 并不是真正意义上的pushed,它们与mov 一起存储在sub 保留的空间中。所以它更像是 500+8+4 = 512,然后 GCC 浪费了额外的 16 个字节。 (请参阅Why does GCC allocate more space than necessary on the stack, beyond what's needed for alignment?)数组本来有空间加上溢出堆栈参数,RSP 对齐 16,并且数组从底部开始,因此也对齐。当然,只有调试版本才会真正将 main 的传入寄存器参数存储到内存中。
  • @PeterCordes 谢谢你。我认为我需要更深入地研究组装以完全了解正在发生的事情。但至少这个答案和所有这些 cmets 对我来说是有意义的。
猜你喜欢
  • 2019-12-07
  • 1970-01-01
  • 2021-12-29
  • 2016-05-08
  • 2011-05-06
  • 2015-10-15
  • 2014-12-05
  • 2023-03-12
  • 2011-12-24
相关资源
最近更新 更多