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

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

#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.


person cojocar    schedule 08.05.2009    source источник


Ответы (4)


Я считаю, что проблема в том, что ваш массив находится в стеке, а ваш компилятор слишком стар, чтобы поддерживать переменные стека с чрезмерным выравниванием. 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

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

person Zifre    schedule 08.05.2009
comment
+1 правильный ответ. Альтернативное решение — сделать локальный массив статическим. Выравнивание в стеке — это всегда проблема, и лучше привыкнуть избегать ее. - person Dan Olson; 08.05.2009
comment
Ах да, я не думал сделать его статичным. Это хорошая идея, поскольку она предотвращает конфликты имен. Я отредактирую свой ответ. - person Zifre; 08.05.2009
comment
Обратите внимание, что если сделать его статическим, он также сделает его нереентерабельным и небезопасным для потоков. - person ArchaeaSoftware; 30.09.2013
comment
Также gcc 4.6+ правильно обрабатывает это даже в стеке. - person textshell; 26.12.2015
comment
В чем разница между __attribute__((aligned(0x1000))) и __align(0x1000)? - person m4l490n; 29.01.2016
comment
В macOS компилятор выравнивает любой массив до 16 байт. Делает ли GCC это также в 64-битной системе? - person Royi; 05.08.2017
comment
Этот ответ раньше был правильным, но теперь это не так. gcc начиная с 4.6, а может и старше, знает, как выровнять указатель стека, чтобы правильно реализовать C11/C++11 alignas(64) или что-то еще на объектах с автоматическим хранением. И, конечно же, GNU C __attribute((aligned((64))) - person Peter Cordes; 15.01.2019

В 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, которая, кажется, исправлена ​​в более поздних версиях.

person rts1    schedule 15.10.2012
comment
Согласно связанной ошибке, gcc 4.6 был первым выпуском, в котором эта проблема была полностью исправлена ​​для всех архитектур. - person textshell; 26.12.2015
comment
Кроме того, ассемблерный код, сгенерированный gcc для создания выровненной переменной в стеке, настолько ужасен и настолько неоптимизирован. Итак, имеет ли смысл размещать выровненные переменные в стеке вместо вызова memalign()? - person Jérôme Pouiller; 17.09.2018

Недавний 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
person Caleb Case    schedule 04.05.2011
comment
Это немного удивительно, учитывая, что массивы распределены в стеке — значит ли это, что стек теперь дырявый? - person ysap; 23.03.2012
comment
Или его стек выровнен по 16 байтам. - person user7116; 17.01.2013

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

#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

Что вы и ожидали.

Редактировать: Подтолкнул @yzap и после комментария @Caleb Case первоначальная проблема связана с версией 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 для обходных путей)

person levif    schedule 12.11.2010
comment
Как ответил zifre, дело не в типе, а в том, что вы сделали его статичным в своей версии. - person ysap; 08.03.2012
comment
@ysap, именно версия GCC и глобальное определение заставили его работать. Спасибо за ваш комментарий! Я отредактировал ответ, чтобы исправить это. :) - person levif; 23.03.2012