2015-05-13 5 views
13

В известной работе «Smashing стека для развлечения и прибыли», автор принимает функцию Cвыравнивание памяти сегодня и 20 лет назад

void function(int a, int b, int c) { 
    char buffer1[5]; 
    char buffer2[10]; 
} 

и генерирует соответствующий выходной ассемблерный код

pushl %ebp 
movl %esp,%ebp 
subl $20,%esp 

Автор объясняет, что, поскольку компьютеры адресуют память в кратном размере слова, компилятор зарезервировал 20 байт в стеке (8 байтов для buffer1, 12 байтов для buffer2).

я попытался воссоздать этот пример и получил следующий

pushl %ebp 
movl %esp, %ebp 
subl $16, %esp 

другой результат! Я пробовал различные комбинации размеров для buffer1 и buffer2, и кажется, что современные gcc не имеют размер буфера для буферов, чтобы увеличить размер слова. Вместо этого он выполняет опцию -mpreferred-stack-boundary.

В качестве иллюстрации - с использованием арифметических правил бумаги для буфера 1 [5] и buffer2 [13] Я бы получил 8 + 16 = 24 байта, зарезервированных в стеке. Но на самом деле я получил 32 байта.

Документ довольно старый, и с тех пор произошло много чего. Я хотел бы знать, что именно мотивировало это изменение поведения? Это движение к 64-битным машинам? Или что-то другое?

Редактировать

Код компилируется на x86_64 машину с помощью GCC версии 4.8.2 (Ubuntu 4.8.2-19ubuntu1) так:

$ gcc -S -o example1.s example1.c -fno-stack-protector -m32

+4

8 + 16 - 24, а не 20. Кстати, кажется, что компилятор только немного поумнел, его локальный анализ анализа переменных рассмотрен как массивы символов, а так как массивы символов не нуждаются в выравнивании, он просто скрепил их и выровнял полученный «компактный» массив. –

+4

Есть ли смысл выравнивать значения 'char' в любом случае? –

+2

это на x86 или x86_64? – rmmh

ответ

7

Что изменилось SSE, который требует 16 выравнивания байт, это рассматривается в этой старой Gcc документа для -mpreferred-stack-boundary=num, который говорит (курсив мой):

На Pentium и PentiumPro, двойной и длинной двойной значения должны быть выровнены с 8-байтовой границей (см. -malign-double) или имеют значительные штрафы за производительность во время выполнения. На Pentium III тип данных Streaming SIMD Extension (SSE) __m128 имеет аналогичные штрафы, если он не выровнен по 16 байт.

Это подтверждается также и бумаги Smashing The Modern Stack For Fun And Profit, которая охватывает ли это другие современные изменения, которые нарушают Smashing стека для развлечения и прибыли.

0

Я не пробовал конкретной версии компилятора или версии распространения, о которой вы сообщаете. Я предполагаю, что это 16 из требований выравнивания байтов в стеке (т. Е. Все корректировки стека будут выровнены по оси x, а x может быть 16 для вашего вызова).

Обратите внимание, что выравнивание параметров, с которым, по-видимому, начиналось, немного отличается от приведенного выше и управляется выровками по переменной в gcc. Попробуйте использовать их, и вы должны увидеть разницу.

2

Выравнивание по размеру, из которого выравнивание стека является только одним аспектом, зависит от архитектуры. Он частично определен в бинарном интерфейсе Applicion для языка и стандартном стандарте обработки вызовов (иногда он является как единым спецификацией) для архитектуры (процессор, он может даже варьироваться в зависимости от платформы), а также зависит от компилятора/инструментальной цепочки, где первые документы оставляют место для вариантов.

Первые два документа (имена могут отличаться) в основном предназначены для внешнего интерфейса между функциями; они могут оставить внутреннюю структуру инструментальной цепочки. Однако это должно соответствовать архитектуре. Обычно аппаратное обеспечение требует минимального выравнивания, но допускает большее выравнивание по соображениям производительности (например, минимальное выравнивание по байтам, но для чтения 32-битного слова потребуется несколько шинных циклов, поэтому компилятор использует 32-битное выравнивание).

Обычно компилятор (после PCS) использует оптимальное для архитектуры выравнивание и контролирует параметры оптимизации (оптимизируется для скорости или размера). Он учитывает не только размер объекта (согласованный с его естественной границей), но и размеры внутренних шин (например, 32-разрядный x86 имеет внутренние 64 или 128-битные шины, процессоры ARM имеют внутренние 32 до 128 (возможно, даже более широкие) бит-шины), кеши и т. д.Для локальных переменных он также может принимать во внимание шаблоны доступа, поэтому две смежные переменные могут быть загружены параллельно в пару регистров вместо двух отдельных нагрузок или даже переупорядочить такие переменные.

Например, для стекового указателя может потребоваться более высокое выравнивание, поэтому ЦП может одновременно вводить в кадр прерывания два регистра, нажимать векторные регистры, которые требуют более высокого выравнивания и т. Д. Вы можете написать довольно толстую книгу об этом предмете (и Уверен, у кого-то уже есть).

Таким образом, в общем случае единого правила выравнивания не существует. Однако для структурированной и массивной упаковки стандарт C делает определением некоторых правил для упаковки/выравнивания, в основном для обеспечения последовательности, например. sizeof (тип) и адрес в массиве (требуется для правильного malloc()).

Даже массивы символов могут быть выровнены для оптимальной компоновки кеша. Обратите внимание, что не только процессор, у которого могут быть кэши, но также мосты PCIe, не говоря уже о том, что PCIe передает себя на страницы DRAM.

Смежные вопросы