【发布时间】:2019-06-17 15:45:00
【问题描述】:
我们阅读了很多关于对齐及其重要性的文章,例如对于放置 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) 版本中那样。
请注意,我通常不懂汇编。我不能写汇编,但我能读懂它。据我了解,差异只是改变了内存地址的偏移量,它将保存我们的位置-newed int。
但它实现了什么?为什么我们需要那个?假设我们有一个 buffer 数组的“通用”表示,如下所示:
[ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]
现在,我假设,在放置-newing int(有或没有对齐)之后,我们最终会得到这样的结果:
[x] [x] [x] [x] [ ] [ ] [ ] [ ] [ ] [ ]
其中x 表示int 的单个字节(我们假设sizeof(int) == 4)。
但我一定错过了什么。还有更多,我不知道是什么。通过将buffer 与int 匹配对齐,我们究竟能实现什么?如果我们不如此对齐会发生什么?
【问题讨论】:
-
当您在同一缓冲区中放置新的
char并立即在其“之后”放置新的int时,就会出现问题。 -
@MooingDuck 所以假设我永远不会将我放置的类型 -
new混合到我的缓冲区中(假设我以非常简化的方式模仿std::vector的缓冲区方式),我可以在没有任何对齐的情况下做到这一点? -
不,对齐仍然是一个问题,在混合类型之前很难注意到问题。
-
相关,但不完全回答这个问题:stackoverflow.com/questions/119123/…
标签: c++ memory-alignment placement-new memory-layout alignas