Как именно выравнивание влияет на макет памяти и поведение размещения новых?

Мы много читаем о выравнивании и как это важно, например, для размещения 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 байтам.

Другие вопросы по тегам