Gcc 为堆栈变量的扩展对齐生成的程序集

Gcc 为堆栈变量的扩展对齐生成的程序集,gcc,assembly,x86,alignment,memory-alignment,Gcc,Assembly,X86,Alignment,Memory Alignment,我正在深入研究使用扩展对齐的基于堆栈的变量的代码汇编。这是代码的较小版本 struct Something { Something(); }; void foo(Something*); void bar() { alignas(128) Something something; foo(&something); } 当使用clang 8.0编译时,会生成以下代码() gcc的早期版本会产生以下结果()。从gcc 8.1开始,两者产生相同的代码 bar():

我正在深入研究使用扩展对齐的基于堆栈的变量的代码汇编。这是代码的较小版本

struct Something {
    Something();
};

void foo(Something*);

void bar() {
    alignas(128) Something something;
    foo(&something);
}
当使用clang 8.0编译时,会生成以下代码()

gcc的早期版本会产生以下结果()。从gcc 8.1开始,两者产生相同的代码

bar():
        lea     r10, [rsp+8]
        and     rsp, -128
        push    QWORD PTR [r10-8]
        push    rbp
        mov     rbp, rsp
        push    r10
        sub     rsp, 232
        lea     rax, [rbp-240]
        mov     rdi, rax
        call    Something::Something() [complete object constructor]
        lea     rax, [rbp-240]
        mov     rdi, rax
        call    foo(Something*)
        nop
        add     rsp, 232
        pop     r10
        pop     rbp
        lea     rsp, [r10-8]
        ret

我对x86不太熟悉,只是出于好奇——这两段代码中到底发生了什么?编译器是否使用std::align()之类的技巧,将堆栈上变量
something
的当前堆栈位置四舍五入到128的倍数?

这里没有什么神奇之处。逐行:

bar():                                # @bar()
        push    rbp ; preserve base pointer
        mov     rbp, rsp ; set base poiner
        and     rsp, -128 ; Anding with -128 aligns it on 128 boundary
        sub     rsp, 128 ; incrementing stack grows down, incrementing it gives us the space for new object
        mov     rdi, rsp ; address of the new (future) object is passed as an argument to the constructor, in %RDI
        call    Something::Something() [complete object constructor] # call constructor
        mov     rdi, rsp ; callee might have changed %RDI, so need to restore it
        call    foo(Something*) ; calling a function given it address of fully constructed object
        mov     rsp, rbp ; restore stack pointer
        pop     rbp ; restore base pointer
        ret

这里没有什么神奇的。逐行:

bar():                                # @bar()
        push    rbp ; preserve base pointer
        mov     rbp, rsp ; set base poiner
        and     rsp, -128 ; Anding with -128 aligns it on 128 boundary
        sub     rsp, 128 ; incrementing stack grows down, incrementing it gives us the space for new object
        mov     rdi, rsp ; address of the new (future) object is passed as an argument to the constructor, in %RDI
        call    Something::Something() [complete object constructor] # call constructor
        mov     rdi, rsp ; callee might have changed %RDI, so need to restore it
        call    foo(Something*) ; calling a function given it address of fully constructed object
        mov     rsp, rbp ; restore stack pointer
        pop     rbp ; restore base pointer
        ret


是的,
和rsp,-128
将堆栈指针对齐。它利用了堆栈在x86上向下扩展的事实。哦,这是有道理的-128是1…10000000,所以它在第8个0之后计算所有的0,导致数字是128的倍数。但是为什么早期版本的gcc做的不一样呢?您展示的两个版本的代码都有这样的功能。不同之处似乎在于,较旧的代码先对齐堆栈,然后设置堆栈帧,而较新的代码则按相反的顺序进行。@Jester它似乎也在使用一些其他常量,如232和240?在更高版本中,是否有责任在被调用方上设置堆栈框架?为什么?是的,3个寄存器在对齐后被推送,每个寄存器8个字节,总共24个字节。分配另一个232将使其达到256,这也是128字节对齐的。240来自设置
rbp
后发生的
push r10
的232+8字节。显然,此代码比较新的代码复杂,可能是编译器开发人员注意到并修复了它:)是的,
和rsp,-128
对齐堆栈指针。它利用了堆栈在x86上向下扩展的事实。哦,这是有道理的-128是1…10000000,所以它在第8个0之后计算所有的0,导致数字是128的倍数。但是为什么早期版本的gcc做的不一样呢?您展示的两个版本的代码都有这样的功能。不同之处似乎在于,较旧的代码先对齐堆栈,然后设置堆栈帧,而较新的代码则按相反的顺序进行。@Jester它似乎也在使用一些其他常量,如232和240?在更高版本中,是否有责任在被调用方上设置堆栈框架?为什么?是的,3个寄存器在对齐后被推送,每个寄存器8个字节,总共24个字节。分配另一个232将使其达到256,这也是128字节对齐的。240来自设置
rbp
后发生的
push r10
的232+8字节。显然,这段代码比较新的代码更复杂,可能是编译器开发人员注意到并修复了它:)明白了,读了@Jester的注释后,这部分现在就有意义了。为什么gcc的早期版本不这样做呢?@好奇的是,我看不出有什么不同。不同的编译器可以使用不同的codegen来获得相同的可观察结果。看起来在某种程度上,gcc的人认为他的codegen更好。@好奇:早期的gcc复制返回地址来制作一个完整的假堆栈框架,我认为这对一些奇怪的情况很有用。即使在没有用的时候也总是这样做,这是一个错过的优化。@PeterCordes我不认为我遵循了抱歉:(参见链接副本,明白了,在阅读了@Jester的评论后,这一部分现在有了意义。为什么gcc的早期版本不做同样的事情呢?@好奇的是,我看不出有太大的区别。不同的编译器可以使用不同的codegen来获得相同的可观察结果。看起来在某种程度上,gcc的人认为他的codegen更好@好奇:早期的gcc复制返回地址来制作一个完整的假堆栈框架,我认为这对一些奇怪的角落案例很有用。即使没有用,也总是这样做是一个遗漏的优化。@PeterCordes我不认为我遵循抱歉:(见链接副本