Чи вирівнюються змінні стеку за допомогою __attribute __ ((вирівняні (x)))?


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.

Відповіді:


98

Я вважаю, що проблема полягає в тому, що ваш масив знаходиться в стеку, і що ваш компілятор занадто старий, щоб підтримувати перевирівняні змінні стека. GCC 4.6 та пізніше виправили цю помилку .

C11 / C ++ 11 alignas(64) float a[4];Просто працює для будь-якої потужності вирівнювання 2.
Так само як і 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+ обробляє це правильно навіть у стеку.
текстова оболонка

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, яка, як видається, була виправлена ​​в пізніших версіях.


3
Відповідно до пов'язаної помилки, gcc 4.6 був першим випуском із цією проблемою, повністю виправленою для всіх архітектур.
текстова оболонка

Окрім цього, код збірки, згенерований 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

На що ви очікували.

Редагувати: натиснув @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 порушує директиву вирівнювання масиву struct, але тримає 0x1000 відстань між структурами ... все ще погано! (див. відповідь @zifre щодо обхідних шляхів)


2
Як відповів zifre, це не тип, а той факт, що ви зробили його статичним у своїй версії.
ysap

@ysap, це зробило як версію GCC, так і глобальне визначення. Дякуємо за коментар! Я відредагував відповідь, щоб це виправити. :)
levif
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.