考虑以下通过alloca()函数在堆栈上分配内存的玩具示例:
#include <alloca.h>
void foo() {
volatile int *p = alloca(4);
*p = 7;
}使用gcc 8.2用-O3编译上述函数将得到以下汇编代码:
foo:
pushq %rbp
movq %rsp, %rbp
subq $16, %rsp
leaq 15(%rsp), %rax
andq $-16, %rax
movl $7, (%rax)
leave
ret老实说,我本以为会有一个更紧凑的汇编代码。
分配内存的16字节对齐
上述代码中的指令andq $-16, %rax导致rax包含(仅)16字节对齐地址( rsp和rsp + 15 (均含))。
这种对齐执行是我首先不明白的:为什么alloca()要将分配的内存与16字节的边界对齐?
可能错过了优化?
无论如何,让我们考虑一下,我们希望alloca()分配的内存是16字节对齐的。尽管如此,在上面的汇编代码中,请记住GCC假设堆栈在执行函数调用(即call foo)时将对齐到一个16字节的边界,如果我们在推动rbp寄存器之后注意rbp内部堆栈的状态:
Size Stack RSP mod 16 Description
-----------------------------------------------------------------------------------
------------------
| . |
| . |
| . |
------------------........0 at "call foo" (stack 16-byte aligned)
8 bytes | return address |
------------------........8 at foo entry
8 bytes | saved RBP |
------------------........0 <----- RSP is 16-byte aligned!!!我认为,通过利用https://en.wikipedia.org/wiki/Red_zone_(computing) (即无需修改rsp)和rsp已经包含16字节对齐地址的事实,可以使用以下代码:
foo:
pushq %rbp
movq %rsp, %rbp
movl $7, -16(%rbp)
leave
ret寄存器rbp中包含的地址是16字节对齐的,因此rbp - 16也将对齐到16字节的边界。
更好的是,可以优化新堆栈框架的创建,因为rsp没有被修改:
foo:
movl $7, -8(%rsp)
ret这只是一个漏掉的优化,还是我在这里遗漏了其他的东西?
发布于 2018-09-26 20:56:45
x86-64系统VLAs要求C99可变长度数组(VLAs)对齐16字节,对于>= 16字节的自动/静态数组也是如此。
看起来gcc正在把alloca当作一个VLA,并且没有对每个函数调用只运行一次的alloca进行常量传播。(或者它在内部使用alloca作为VLAs。)
如果运行时值大于128个字节,一般的alloca / VLA不能使用红色区域。GCC还使用RBP创建了一个堆栈框架,而不是保存分配大小并在稍后执行add rsp, rdx。
,所以asm看起来就像函数、arg或其他运行时变量,而不是常量,这就是我得出这个结论的原因。
也是alignof(maxalign_t) == 16,但是alloca和malloc可以满足这样的要求,即返回对小于16字节的对象没有16字节对齐的任何对象可用的内存。在x86-64 SysV中,没有一个标准类型的对齐要求比它们的大小更宽。
你说得对,它应该能把它优化成这样:
void foo() {
alignas(16) int dummy[1];
volatile int *p = dummy; // alloca(4)
*p = 7;
}并将其编译到您建议的movl $7, -8(%rsp);ret中。
对于alloca来说,alignas(16)可能是可选的。
如果您真的需要gcc发出更好的代码,当常量传播使arg到alloca成为编译时常量时,您可以考虑首先使用VLA。GNU C++在C++模式下支持C99风格的VLAs,但ISO C++ (和MSVC)不支持。
或者可能使用if(__builtin_constant_p(size)) { VLA version } else { alloca version },但是VLA的作用域意味着您不能从if的作用域返回VLA,该范围检测到我们被编译时常量size内联。所以你必须复制需要指针的代码。
发布于 2018-09-26 22:12:30
这是gcc (部分)错过了优化。Clang按预期做了。
我之所以这样说,部分是因为如果你知道你要使用gcc,你可以使用内置的函数(用条件编译,让gcc和其他编译器有可移植的代码)。
__builtin_alloca_with_align是你的朋友;)
下面是一个示例(修改后的编译器不会将函数调用减少到单个ret):
#include <alloca.h>
volatile int* p;
void foo()
{
p = alloca(4) ;
*p = 7;
}
void zoo()
{
// aligment is 16 bits, not bytes
p = __builtin_alloca_with_align(4,16) ;
*p = 7;
}
int main()
{
foo();
zoo();
}反汇编代码(使用objdump -d -w --insn-width=12 -M intel)
Clang将生成以下代码(clang -O3 test.c) --这两个函数看起来都很相似
0000000000400480 <foo>:
400480: 48 8d 44 24 f8 lea rax,[rsp-0x8]
400485: 48 89 05 a4 0b 20 00 mov QWORD PTR [rip+0x200ba4],rax # 601030 <p>
40048c: c7 44 24 f8 07 00 00 00 mov DWORD PTR [rsp-0x8],0x7
400494: c3 ret
00000000004004a0 <zoo>:
4004a0: 48 8d 44 24 fc lea rax,[rsp-0x4]
4004a5: 48 89 05 84 0b 20 00 mov QWORD PTR [rip+0x200b84],rax # 601030 <p>
4004ac: c7 44 24 fc 07 00 00 00 mov DWORD PTR [rsp-0x4],0x7
4004b4: c3 ret GCC这个(gcc -g -O3 -fno-stack-protector)
0000000000000620 <foo>:
620: 55 push rbp
621: 48 89 e5 mov rbp,rsp
624: 48 83 ec 20 sub rsp,0x20
628: 48 8d 44 24 0f lea rax,[rsp+0xf]
62d: 48 83 e0 f0 and rax,0xfffffffffffffff0
631: 48 89 05 e0 09 20 00 mov QWORD PTR [rip+0x2009e0],rax # 201018 <p>
638: c7 00 07 00 00 00 mov DWORD PTR [rax],0x7
63e: c9 leave
63f: c3 ret
0000000000000640 <zoo>:
640: 48 8d 44 24 fc lea rax,[rsp-0x4]
645: c7 44 24 fc 07 00 00 00 mov DWORD PTR [rsp-0x4],0x7
64d: 48 89 05 c4 09 20 00 mov QWORD PTR [rip+0x2009c4],rax # 201018 <p>
654: c3 ret 正如您所看到的,动物园现在看起来像预期的,并且类似于clang代码。
https://stackoverflow.com/questions/52525744
复制相似问题