为什么64位架构上的对齐方式是16字节?

9
(gdb) disas foo
Dump of assembler code for function foo:
0x00000000004004a8 <foo+0>: push   %rbp
0x00000000004004a9 <foo+1>: mov    %rsp,%rbp
0x00000000004004ac <foo+4>: mov    0x13c(%rip),%eax        # 0x4005ee <__dso_handle+30>
0x00000000004004b2 <foo+10>:    mov    %eax,-0x10(%rbp)
0x00000000004004b5 <foo+13>:    lea    -0x10(%rbp),%rax
0x00000000004004b9 <foo+17>:    add    $0x18,%rax
0x00000000004004bd <foo+21>:    mov    %rax,%rdx
0x00000000004004c0 <foo+24>:    mov    $0x400498,%eax
0x00000000004004c5 <foo+29>:    mov    %eax,(%rdx)
0x00000000004004c7 <foo+31>:    leaveq 
0x00000000004004c8 <foo+32>:    retq   
(gdb) l foo
8   void foo() {
9       char overme[4] = "WOW";
10      *(int*)(overme+24) = (int)bad;
11  }

为什么不只用8个字节呢?

1
似乎是为什么x86-64 / AMD64 System V ABI要求16字节的堆栈对齐?的重复,尽管代码示例与对齐大多无关,只是GCC在禁用优化时选择将东西放在红区的某个版本。在涉及2种不同类型UB(严格别名违规和越界数组访问)的测试用例中。因此告诉我们很少。 - Peter Cordes
2个回答

16

gcc没有将这个空间“分配”给变量,相反,x86_64 abi要求函数调用时堆栈指针始终保持16字节对齐,以防被调用者使用矢量化SSE数学。这是一个非常愚蠢和浪费资源的要求(如果被调用者需要对齐,则应该确保对齐),但这是标准,gcc遵循标准。您可以使用-mpreferred-stack-boundary = 3 修复它(8字节对齐,64位的最小值)。


@R,寄存器大小为8字节,为什么要进行16字节对齐? - compile-fan
你能详细说明一下“向量化SSE数学”吗? - compile-fan
显然,如果给出不对齐的地址,许多SSE指令会崩溃、做错事情或者执行非常缓慢(不确定是哪一种)。它们一次处理128位的数据,可以是单个128位浮点数,也可以是许多更小的整数或浮点数的向量,因此正确的对齐方式是16。 - R.. GitHub STOP HELPING ICE

1

它是8个字节,而不是16个。LEA指令没有显示任何对齐相关的内容,-0x10只是应用于RBP寄存器值的偏移量。可能是为了生成一个小的本地数组的地址。如果代码生成器使用任何SIMD指令,则16可能是相关的。这些都无法在两行问题中看到。


@Hans Passant,我已经更新了代码,“mov %eax,-0x10(%rbp)”为char overme [4]分配了16个字节。 - compile-fan
不,EAX寄存器存储4个字节。它正在复制“WOW”字符串,这恰好也是4个字节长。我正确猜测了“小型本地数组”。当您使字符串更长时,将获得非常不同的代码。否则,该代码是未定义的行为。 - Hans Passant
@Hans Passant,您能详细说明为什么如果对齐是8字节,那个“小的本地数组”需要16字节而不是8字节吗? - compile-fan
1
不知道为什么,gcc经常吞噬堆栈字节。它也没有通过调整rsp来为数组腾出空间。尝试关闭代码优化器以获得一些不那么RSA加密的东西。这有什么意义?你是故意想要践踏堆栈吗? - Hans Passant
@Hans Passant,它已经使用优化器关闭编译了,gcc -O0 xxx。是的,我正在尝试压缩栈,但不明白为什么overme被分配了16个字节... - compile-fan
我怀疑是-fstack-protector,但代码看起来不像这样。 - blaze

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