Как именно выравнивание влияет на макет памяти и поведение размещения новых?
Мы много читаем о выравнивании и как это важно, например, для размещения new
использование, но мне было интересно - как это точно изменить расположение памяти?
Очевидно, что если мы сделаем
char buffer[10];
std::cout << sizeof buffer;
а также
alignas(int) char buffer[10];
std::cout << sizeof buffer;
мы получаем тот же результат, который 10
,
Но поведение не может быть точно таким же, не так ли? Почему это различимо? Я попытался найти ответ и побежал к Годболту, проверяя следующий код:
#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
представляет собой один байт int
(мы предполагаем, что sizeof(int) == 4
).
Но я должен что-то упустить. Это еще не все, и я не знаю, что. Что именно мы достигаем, выравнивая buffer
в int
подходит расклад? Что произойдет, если мы не выровняем это так?
1 ответ
На некоторых архитектурах типы должны быть выровнены, чтобы операции работали правильно. Адрес int
Например, может потребоваться кратное 4. Если оно не выровнено, то инструкции процессора, которые работают с целыми числами в памяти, работать не будут.
Даже если все работает, когда данные плохо выровнены, выравнивание все равно важно для производительности, поскольку гарантирует, что целые числа и т. Д. Не пересекают границы кэша.
Когда вы выравниваете char
буфера к целочисленной границе, это не влияет на способ размещения новых работ. Это просто гарантирует, что вы можете использовать новое размещение, чтобы положить int
в начале вашего буфера, не нарушая каких-либо ограничений выравнивания. Это достигается за счет того, что адрес буфера кратен 4 байтам.