Czy zmienne stosu są wyrównane przez GCC __attribute __ ((aligned (x)))?


88

mam następujący kod:

#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]);
}

Mam następujący wynik:

0x7fffbfcd2da0 0x7fffbfcd2da4 0x7fffbfcd2da8 0x7fffbfcd2dac

Dlaczego adres a[0]nie jest wielokrotnością 0x1000?

Co dokładnie __attribute__((aligned(x)))robi? Źle zrozumiałem to wyjaśnienie?

Używam gcc 4.1.2.

Odpowiedzi:


98

Uważam, że problem polega na tym, że twoja tablica znajduje się na stosie, a twój kompilator jest zbyt stary, aby obsługiwać nadmiernie wyrównane zmienne stosu. GCC 4.6 i nowsze poprawiły ten błąd .

C11 / C ++ 11 alignas(64) float a[4];Po prostu działa dla każdej potęgi wyrównania 2.
Tak robi GNU C.__attribute__((aligned(x))) jak go używałeś.

(W C11 #include <stdalign.h>dla #define alignas _Alignas: cppref ).


Ale w przypadku bardzo dużego wyrównania, do granicy 4k stron, możesz nie chcieć, aby znajdował się na stosie.

Ponieważ wskaźnik stosu może być dowolny podczas uruchamiania funkcji, nie ma sposobu na wyrównanie tablicy bez przydzielenia o wiele więcej niż potrzebujesz i dostosowania jej. (Kompilatorzy to zrobiąand rsp, -4096 lub równoważne i nie używają żadnego z przydzielonych bajtów od 0 do 4088; rozgałęzianie na to, czy ta przestrzeń jest wystarczająco duża, czy nie, byłoby możliwe, ale nie jest wykonywane, ponieważ ogromne wyrównania są znacznie większe niż rozmiar tablicy lub innych lokalnych nie są normalnym przypadkiem).

Jeśli przeniesiesz tablicę z funkcji do zmiennej globalnej, to powinno działać. Inną rzeczą, którą możesz zrobić, to zachować ją jako zmienną lokalną (co jest bardzo dobrą rzeczą), ale ją zrób static. Zapobiegnie to przechowywaniu go na stosie. Pamiętaj, że oba te sposoby nie są bezpieczne dla wątków ani rekursji, ponieważ będzie tylko jedna kopia tablicy.

Za pomocą tego kodu:

#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]);
}

Rozumiem:

0x804c000 0x804c004 0x804c008 0x804c00c

co jest oczekiwane. Z twoim oryginalnym kodem po prostu otrzymuję losowe wartości, tak jak ty.


11
+1 poprawna odpowiedź. Alternatywnym rozwiązaniem jest uczynienie tablicy lokalnej statyczną. Wyrównanie na stosie jest zawsze problemem i najlepiej jest nabrać nawyku jego unikania.
Dan Olson

O tak, nie pomyślałem o tym, żeby to zmienić. To dobry pomysł, ponieważ zapobiega kolizji nazw. Zmienię odpowiedź.
Zifre

3
Zwróć uwagę, że uczynienie go statycznym sprawia, że ​​nie jest on ponownie wprowadzany i nie jest bezpieczny dla wątków.
ArchaeaSoftware

3
Również gcc 4.6+ obsługuje to poprawnie nawet na stosie.
tekst z

1
Kiedyś ta odpowiedź była poprawna, ale teraz nie. gcc w wieku 4.6, może starszym, wie, jak wyrównać wskaźnik stosu, aby poprawnie zaimplementować C11 / C ++ 11 alignas(64)lub cokolwiek innego na obiektach z automatycznym przechowywaniem. I oczywiście GNU C__attribute((aligned((64)))
Peter Cordes

41

Wystąpił błąd w gcc, który powodował, że atrybut wyrównany nie działał ze zmiennymi stosu. Wydaje się, że problem został rozwiązany za pomocą poprawki, do której link znajduje się poniżej. Poniższy link zawiera również sporo dyskusji na temat problemu.

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

Wypróbowałem twój kod powyżej z dwiema różnymi wersjami gcc: 4.1.2 z pudełka RedHat 5.7 i zawiódł podobnie jak twój problem (lokalne tablice nie były w żaden sposób wyrównane do granic 0x1000 bajtów). Następnie wypróbowałem twój kod z gcc 4.4.6 na RedHacie 6.3 i działał bezbłędnie (lokalne tablice zostały wyrównane). Ludzie z Myth TV mieli podobny problem (który wydaje się naprawiać powyższa łatka gcc):

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

W każdym razie wygląda na to, że znalazłeś błąd w gcc, który wydaje się być naprawiony w późniejszych wersjach.


3
Zgodnie z połączonym błędem, gcc 4.6 było pierwszym wydaniem, w którym ten problem został w pełni naprawiony dla wszystkich architektur.
tekst z

Poza tym kod asemblera generowany przez gcc w celu utworzenia wyrównanej zmiennej na stosie jest okropny i niezoptymalizowany. Czy więc ma sens umieszczanie wyrównanych zmiennych na stosie zamiast wywoływania memalign()?
Jérôme Pouiller

13

Najnowsze GCC (testowane z 4.5.2-8ubuntu4) wydaje się działać zgodnie z oczekiwaniami z prawidłowo wyrównaną tablicą.

#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]);
}

Dostaję:

0x7ffffffefff0 0x7ffffffefff4 0x7ffffffefff8 0x7ffffffefffc
0x7ffffffef000 0x7ffffffef004 0x7ffffffef008 0x7ffffffef00c
0x7ffffffe0000 0x7ffffffe0004 0x7ffffffe0008 0x7ffffffe000c

Jest to trochę zaskakujące, biorąc pod uwagę, że tablice są alokowane w stosie - czy to oznacza, że ​​stos jest teraz pełen dziur?
ysap

Lub jego stos jest wyrównany do 16 bajtów.
user7116

9

Wyrównanie nie jest skuteczne dla wszystkich typów. Powinieneś rozważyć użycie struktury, aby zobaczyć atrybuty w akcji:

#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]);
}

A potem przeczytasz:

0x603000 0x604000 0x605000 0x606000

Tego się spodziewałeś.

Edycja: Przesłane przez @yzap i po komentarzu @Caleb Case, początkowy problem dotyczy tylko wersji GCC . Sprawdziłem GCC 3.4.6 vs GCC 4.4.1 z kodem źródłowym żądającego:

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

Jest teraz oczywiste, że starsze wersje GCC (gdzieś przed 4.4.1) wykazują patologie dopasowania.

Uwaga 1: Proponowany przeze mnie kod nie odpowiada na pytanie, które rozumiałem jako „wyrównanie każdego pola tablicy”.

Uwaga 2: Wprowadzenie niestatycznego a [] do main () i kompilacja z GCC 3.4.6 łamie dyrektywę wyrównania tablicy struct, ale utrzymuje odległość 0x1000 między strukturami ... nadal źle! (zobacz odpowiedź @zifre dla obejść)


2
Jak odpowiedział zifre, nie jest to typ, ale fakt, że ustawiłeś go jako statyczny w swojej wersji.
ysap

@ysap, to zarówno wersja GCC, jak i definicja globalna sprawiły, że działa. Dzięki za komentarz! Zredagowałem odpowiedź, aby to naprawić. :)
levif
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.