Выровнены ли переменные стека по __attribute __ ((выровнены (x))) GCC?

88

у меня есть следующий код:

#include <stdio.h>

int
main(void)
{
        float a[4] __attribute__((aligned(0x1000))) = {1.0, 2.0, 3.0, 4.0};
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

И у меня есть следующий вывод:

0x7fffbfcd2da0 0x7fffbfcd2da4 0x7fffbfcd2da8 0x7fffbfcd2dac

Почему адрес a[0]не кратен 0x1000?

Что именно __attribute__((aligned(x)))делает? Я неправильно понял это объяснение?

Я использую gcc 4.1.2.

Cojocar
источник

Ответы:

98

Я считаю, что проблема в том, что ваш массив находится в стеке, и что ваш компилятор слишком стар, чтобы поддерживать чрезмерно выровненные переменные стека. GCC 4.6 и более поздние версии исправили эту ошибку .

C11 / C ++ 11 alignas(64) float a[4];Работает при любом выравнивании степени двойки.
То же самое и с GNU C в том __attribute__((aligned(x)))виде, в котором вы его использовали.

(В C11 #include <stdalign.h>для #define alignas _Alignas: cppref ).


Но в вашем случае очень большого выравнивания по границе страницы 4k вам может не понадобиться его в стеке.

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

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

С этим кодом:

#include <stdio.h>

float a[4] __attribute__((aligned(0x1000))) = {1.0, 2.0, 3.0, 4.0};

int
main(void)
{
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

Я получаю это:

0x804c000 0x804c004 0x804c008 0x804c00c

что и ожидается. С вашим исходным кодом я просто получаю случайные значения, как и вы.

Зифре
источник
11
+1 правильный ответ. Альтернативное решение - сделать локальный массив статическим. Выравнивание в стеке всегда является проблемой, и лучше выработать привычку избегать этого.
Дэн Олсон,
О да, я не думал делать это статичным. Это хорошая идея, поскольку она предотвращает конфликты имен. Отредактирую свой ответ.
Zifre
3
Обратите внимание, что делая его статическим, также делает его нереентерабельным и небезопасным для потоков.
ArchaeaSoftware,
3
Также gcc 4.6+ правильно обрабатывает это даже в стеке.
TextShell
1
Раньше этот ответ был правильным, но теперь это не так. gcc, такой же старый, как 4.6, может быть, старше, знает, как выровнять указатель стека, чтобы правильно реализовать C11 / C ++ 11 alignas(64)или что-то еще на объектах с автоматическим хранением. И, конечно же, GNU C__attribute((aligned((64)))
Питер Кордес
41

В gcc была ошибка, из-за которой атрибут, выровненный по стеку, не работал. Кажется, это исправлено патчем, указанным ниже. Ссылка ниже также содержит довольно много обсуждения проблемы.

http://gcc.gnu.org/bugzilla/show_bug.cgi?id=16660

Я пробовал ваш код выше с двумя разными версиями gcc: 4.1.2 из окна RedHat 5.7, и он не удался, как и ваша проблема (локальные массивы никоим образом не выровнены по границам байтов 0x1000). Затем я попробовал ваш код с gcc 4.4.6 на RedHat 6.3, и он работал безупречно (локальные массивы были выровнены). У ребят из Myth TV была похожая проблема (которую, похоже, исправил вышеуказанный патч gcc):

http://code.mythtv.org/trac/ticket/6535

В любом случае, похоже, что вы нашли ошибку в gcc, которая, похоже, будет исправлена ​​в более поздних версиях.

rts1
источник
3
Согласно связанной ошибке, gcc 4.6 был первым выпуском, в котором эта проблема была полностью исправлена ​​для всех архитектур.
TextShell
Кроме того, ассемблерный код, сгенерированный gcc для создания выровненной переменной в стеке, ужасен и неоптимизирован. Итак, имеет ли смысл размещать выровненные переменные в стеке вместо вызова memalign()?
Jérôme Pouiller
13

Недавний GCC (протестированный с 4.5.2-8ubuntu4), похоже, работает должным образом с правильным выравниванием массива.

#include <stdio.h>

int main(void)
{
    float a[4] = { 1.0, 2.0, 3.0, 4.0 };
    float b[4] __attribute__((aligned(0x1000))) = { 1.0, 2.0, 3.0, 4.0 };
    float c[4] __attribute__((aligned(0x10000))) = { 1.0, 2.0, 3.0, 4.0 };

    printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
    printf("%p %p %p %p\n", &b[0], &b[1], &b[2], &b[3]);
    printf("%p %p %p %p\n", &c[0], &c[1], &c[2], &c[3]);
}

Я получил:

0x7ffffffefff0 0x7ffffffefff4 0x7ffffffefff8 0x7ffffffefffc
0x7ffffffef000 0x7ffffffef004 0x7ffffffef008 0x7ffffffef00c
0x7ffffffe0000 0x7ffffffe0004 0x7ffffffe0008 0x7ffffffe000c
Калеб Кейс
источник
Это немного удивительно, учитывая, что массивы размещены в стеке - означает ли это, что стек теперь полон дыр?
ysap
Или его стек выровнен по 16 байт.
user7116
9

Выравнивание действует не для всех типов. Вам следует подумать об использовании структуры, чтобы увидеть атрибуты в действии:

#include <stdio.h>

struct my_float {
        float number;
}  __attribute__((aligned(0x1000)));

struct my_float a[4] = { {1.0}, {2.0}, {3.0}, {4.0} };

int
main(void)
{
        printf("%p %p %p %p\n", &a[0], &a[1], &a[2], &a[3]);
}

А потом вы прочтете:

0x603000 0x604000 0x605000 0x606000

Это то, чего вы ожидали.

Edit: Выдвинутый на @yzap и после @Caleb случая комментарий, исходная проблема связана с GCC версии только . Я проверил GCC 3.4.6 против GCC 4.4.1 с исходным кодом запрашивающего:

$ ./test_orig-3.4.6
0x7fffe217d200 0x7fffe217d204 0x7fffe217d208 0x7fffe217d20c
$ ./test_orig-4.4.1
0x7fff81db9000 0x7fff81db9004 0x7fff81db9008 0x7fff81db900c

Теперь очевидно, что более старые версии GCC (где-то до 4.4.1) показывают патологии выравнивания.

Примечание 1. Предлагаемый мной код не отвечает на вопрос, который я понял как «выравнивание каждого поля массива».

Примечание 2: Внесение нестатического a [] внутрь main () и компиляция с GCC 3.4.6 нарушает директиву выравнивания массива структур, но сохраняет расстояние 0x1000 между структурами ... все еще плохо! (см. ответ @zifre для обходных путей)

левиф
источник
2
Как ответил zifre, дело не в типе, а в том, что вы сделали его статическим в своей версии.
ysap 08
@ysap, и версия GCC, и глобальное определение заставили его работать. Спасибо за комментарий! Я отредактировал ответ, чтобы исправить. :)
levif