Мы много читали о выравнивании и его важности, например, для размещения 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
ed int
.
Но чего он добивается? Зачем нам это нужно? Предположим, у нас есть «общее» представление массива buffer
следующим образом:
[ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ] [ ]
Теперь, я бы предположил, что после размещения new
с int
( с или без выравнивания) мы бы получили что-то вроде этого:
[x] [x] [x] [x] [ ] [ ] [ ] [ ] [ ] [ ]
где x
представляет один байт int
(мы предполагаем, что sizeof(int) == 4
).
Но я должен что-то упустить. Это еще не все, и я не знаю, что. Чего именно мы достигаем, выровняв подходящее выравнивание от buffer
до int
? Что произойдет, если мы не настроим это так?