【问题标题】:Arrays memory allocation on stack堆栈上的数组内存分配
【发布时间】:2018-09-17 08:33:39
【问题描述】:

我在 C 中有 2 个函数:

void func1(unsigned char x)
{
    unsigned char a[10][5];

    a[0][0] = 1;
    a[9][4] = 2;
}

void func2(unsigned char x)
{
    unsigned char a[10][5];

    a[0][0] = 1;
    a[9][4] = 2;

    unsigned char b[10];

    b[0] = 4;
    b[9] = 5;
}

编译:

gcc 7.3 x86-64

-O0 -g

操作系统:

16.04.1-Ubuntu x86-64

生产的intel函数汇编:

func1(unsigned char):
  pushq %rbp
  movq %rsp, %rbp
  movl %edi, %eax
  movb %al, -68(%rbp)
  movb $1, -64(%rbp)
  movb $2, -15(%rbp)
  nop
  popq %rbp
  ret

func2(unsigned char):
  pushq %rbp
  movq %rsp, %rbp
  movl %edi, %eax
  movb %al, -84(%rbp)
  movb $1, -64(%rbp)
  movb $2, -15(%rbp)
  movb $4, -74(%rbp)
  movb $5, -65(%rbp)
  nop
  popq %rbp
  ret

我可以看到,对于 50 字节数组,分配了 64 个字节。似乎它在 16 字节边界上分配堆栈,因为对于 10 字节 - 分配了 16 字节。

我的问题:

1) 数组的 16 字节边界是否有一些堆栈对齐标准? 因为 int、long int 等变量显然没有分配在 16 字节边界上。

2) 为什么数组分配的方式如此奇怪?我们可以看到数组 a14 字节 作为对齐填充添加了就在我们的 50 字节有效负载之后,但对于数组 @ 987654325@,6 个对齐字节被分配我们的 10 个字节的有效负载之前。我错过了什么吗?

3) 为什么函数参数传入EDI (unsigned char x),放置在距离我们数组内存开头4字节的堆栈上(包括填充)。那么字节变量(AL register)也被填充了还是什么?为什么是 4 个字节

【问题讨论】:

  • 不同的 C 编译器可能会输出不同的代码。另一方面,检查未优化的构建充其量是徒劳的。除此之外,从实际的角度来看,您的问题是有价值的。
  • On 2) & 3): Option -O0 表示“尽快生成代码。不要浪费任何时间考虑改进代码的选项。” 而且代码看起来确实可以使用一些改进...
  • C语言中没有栈
  • rsp 本身的对齐保持不变,因此如果一个函数需要对任何东西进行如此多的对齐(例如加载/存储 SSE 向量),它可以免费获得。 Why does System V / AMD64 ABI mandate a 16 byte stack alignment?。这当然并不意味着堆栈上的每个对象都是 16 字节对齐的。 GCC 甚至在叶函数(不进行任何函数调用)中也会这样做,就像实现细节一样。

标签: c arrays assembly memory stack-memory


【解决方案1】:

x86_64 abi 需要 16 字节堆栈对齐(进入函数时堆栈指针必须是 16 字节对齐)。但是你看到的overalignment是-O0造成的;使用-O1 或更高版本,数组对齐更有效。例如

void func2(unsigned char x)
{
    unsigned char a[10][5];

    a[0][0] = 1;
    a[9][4] = 2;

    unsigned char b[10];

    b[0] = 4;
    b[9] = 5;

    __asm__ __volatile__("" :: "r"(a), "r"(b) : "memory");
}

原因:

gcc -O1 -g x.c -c
objdump -d x.o
0000000000000010 <func2>:
  10:   c6 44 24 c0 01          movb   $0x1,-0x40(%rsp)
  15:   c6 44 24 f1 02          movb   $0x2,-0xf(%rsp)
  1a:   c6 44 24 b6 04          movb   $0x4,-0x4a(%rsp)
  1f:   c6 44 24 bf 05          movb   $0x5,-0x41(%rsp)
  24:   48 8d 44 24 c0          lea    -0x40(%rsp),%rax
  29:   48 8d 54 24 b6          lea    -0x4a(%rsp),%rdx
  2e:   c3                      retq   

-Os-O3 创建其他布局。

【讨论】:

  • 请注意,ABI 在 调用 函数时需要 16 字节对齐(即在执行 call 之前的那一刻),这意味着您在实际上进入被调用函数时,由于call 推送了返回地址,所以堆栈错位了 8 个字节。
猜你喜欢
  • 2017-11-08
  • 2018-07-24
  • 2011-05-28
  • 2010-12-11
  • 2021-05-10
  • 2014-10-26
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多