我们阅读了很多关于对齐及其重要性的文章,例如对于放置的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
数组的“通用”表示,如下所示:
[ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]
现在,我假设,在放置(有或没有对齐)之后,我们最终会得到这样的结果new
:int
[x] [x] [x] [x] [ ] [ ] [ ] [ ] [ ] [ ]
其中x
表示 an 的单个字节int
(我们假设sizeof(int) == 4
)。
但我一定是错过了什么。还有更多,我不知道是什么。buffer
通过对齐到int
合适的对齐方式,我们到底能实现什么?如果我们不这样对齐会发生什么?