1

我们阅读了很多关于对齐及其重要性的文章,例如对于放置的new使用,但我想知道 - 它究竟如何改变内存的布局?

显然,如果我们这样做

char buffer[10];
std::cout << sizeof buffer;

alignas(int) char buffer[10];
std::cout << sizeof buffer;

我们得到相同的结果,即10.

但是行为不可能完全相同,不是吗?怎么区分的?我试图寻找答案并跑到godbolt,测试以下代码:

#include <memory>

int main() {
    alignas(int) char buffer[10];
    new (buffer) int;
}

在 GCC 8.2 和没有优化的情况下,会导致以下程序集:

operator new(unsigned long, void*):
    push    rbp
    mov     rbp, rsp
    mov     QWORD PTR [rbp-8], rdi
    mov     QWORD PTR [rbp-16], rsi
    mov     rax, QWORD PTR [rbp-16]
    pop     rbp
    ret
main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    lea     rax, [rbp-12]
    mov     rsi, rax
    mov     edi, 4
    call    operator new(unsigned long, void*)
    mov     eax, 0
    leave
    ret

让我们通过删除部分来稍微更改代码alignas(int)。现在,生成的程序集略有不同:

operator new(unsigned long, void*):
    push    rbp
    mov     rbp, rsp
    mov     QWORD PTR [rbp-8], rdi
    mov     QWORD PTR [rbp-16], rsi
    mov     rax, QWORD PTR [rbp-16]
    pop     rbp
    ret
main:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 16
    lea     rax, [rbp-10]
    mov     rsi, rax
    mov     edi, 4
    call    operator new(unsigned long, void*)
    mov     eax, 0
    leave
    ret

值得注意的是,它仅在lea指令上有所不同,其中第二个参数是[rbp-10]而不是[rbp-12],就像我们在alignas(int)版本中所做的那样。

请注意,我通常不懂汇编。我不能写汇编,但我能读懂它。据我了解,差异只是改变了内存地址的偏移量,它将保存我们的new放置int

但它实现了什么?为什么我们需要那个?假设我们有一个buffer数组的“通用”表示,如下所示:

[ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]

现在,我假设,在放置(有或没有对齐)之后,我们最终会得到这样的结果newint

[x] [x] [x] [x] [ ] [ ] [ ] [ ] [ ] [ ]

其中x表示 an 的单个字节int(我们假设sizeof(int) == 4)。

但我一定是错过了什么。还有更多,我不知道是什么。buffer通过对齐到int合适的对齐方式,我们到底能实现什么?如果我们不这样对齐会发生什么?

4

1 回答 1

1

在某些架构上,必须对齐类型才能使操作正常工作。例如,的地址int可能需要是 4 的倍数。如果它不是如此对齐,那么对内存中的整数进行操作的 CPU 指令将无法工作。

即使数据没有很好地对齐时一切正常,对齐对于性能仍然很重要,因为它确保整数等不会跨越缓存边界。

当您将char缓冲区对齐到整数边界时,它不会影响放置新的工作方式。它只是确保您可以使用placement new 将a 放在int缓冲区的开头,而不会违反任何对齐约束。它通过确保缓冲区的地址是 4 个字节的倍数来做到这一点。

于 2019-01-24T03:19:22.247 回答