Les variables de pile sont-elles alignées par le __attribute __ ((aligné (x)))?

88

J'ai le code suivant:

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

Et j'ai la sortie suivante:

0x7fffbfcd2da0 0x7fffbfcd2da4 0x7fffbfcd2da8 0x7fffbfcd2dac

Pourquoi l'adresse de a[0]n'est pas un multiple de 0x1000?

Que fait exactement __attribute__((aligned(x)))? J'ai mal compris cette explication?

J'utilise gcc 4.1.2.

cojocar
la source

Réponses:

98

Je pense que le problème est que votre tableau est sur la pile et que votre compilateur est trop ancien pour prendre en charge des variables de pile suralignées. GCC 4.6 et versions ultérieures ont corrigé ce bogue .

C11 / C ++ 11 alignas(64) float a[4];Fonctionne juste pour toute puissance d'alignement 2.
Il en va de même pour le GNU C __attribute__((aligned(x)))tel que vous l'utilisiez.

(En C11, #include <stdalign.h>pour le #define alignas _Alignas: cppref ).


Mais dans votre cas d'un très grand alignement, à une limite de 4k pages, vous ne voudrez peut-être pas qu'il soit sur la pile.

Étant donné que le pointeur de pile peut être n'importe quoi au démarrage de la fonction, il n'y a aucun moyen d'aligner le tableau sans allouer beaucoup plus que ce dont vous avez besoin et l'ajuster. (Les compilateurs and rsp, -4096ou l'équivalent et n'utiliseront aucun des 0 à 4088 octets alloués; il serait possible de déterminer si cet espace est suffisamment grand ou non, mais n'est pas fait car d'énormes alignements beaucoup plus grands que la taille du tableau ou d'autres locaux ne sont pas le cas normal.)

Si vous déplacez le tableau hors de la fonction et dans une variable globale, cela devrait fonctionner. L'autre chose que vous pouvez faire est de la conserver en tant que variable locale (ce qui est une très bonne chose), mais faites-la static. Cela l'empêchera d'être stocké sur la pile. Sachez que ces deux méthodes ne sont pas sécurisées pour les threads ou la récursivité, car il n'y aura qu'une seule copie du tableau.

Avec ce code:

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

J'ai compris:

0x804c000 0x804c004 0x804c008 0x804c00c

ce qui est attendu. Avec votre code d'origine, j'obtiens juste des valeurs aléatoires comme vous l'avez fait.

Zifre
la source
11
+1 bonne réponse. Une autre solution consiste à rendre la baie locale statique. L'alignement sur la pile est toujours un problème et il est préférable de prendre l'habitude de l'éviter.
Dan Olson
Oh oui, je n'ai pas pensé à le rendre statique. C'est une bonne idée car cela évite les collisions de noms. Je modifierai ma réponse.
Zifre
3
Notez que le rendre statique le rend également non réentrant et non thread-safe.
ArchaeaSoftware
3
De plus, gcc 4.6+ gère cela correctement même sur la pile.
SMShell
1
Cette réponse était correcte, mais maintenant ce n'est pas le cas. gcc aussi vieux que 4.6, peut-être plus ancien, sait comment aligner le pointeur de pile pour implémenter correctement C11 / C ++ 11 alignas(64)ou autre sur des objets avec stockage automatique. Et bien sûr GNU C__attribute((aligned((64)))
Peter Cordes
41

Il y avait un bogue dans gcc qui empêchait l' attribut aligné de fonctionner avec les variables de pile. Il semble être corrigé avec le correctif lié ci-dessous. Le lien ci-dessous contient également un peu de discussion sur le problème.

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

J'ai essayé votre code ci-dessus avec deux versions différentes de gcc: 4.1.2 à partir d'une boîte RedHat 5.7, et cela a échoué de la même manière que votre problème (les tableaux locaux ne sont en aucun cas alignés sur les limites d'octets 0x1000). J'ai ensuite essayé votre code avec gcc 4.4.6 sur RedHat 6.3, et cela a fonctionné parfaitement (les tableaux locaux étaient alignés). Les gens de Myth TV avaient un problème similaire (que le correctif gcc ci-dessus semblait résoudre):

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

Quoi qu'il en soit, il semble que vous ayez trouvé un bogue dans gcc, qui semble être corrigé dans les versions ultérieures.

rts1
la source
3
Selon le bogue lié, gcc 4.6 était la première version avec ce problème entièrement corrigé pour toutes les architectures.
SMShell
À côté de cela, le code d'assemblage généré par gcc pour créer une variable alignée sur la pile est si horrible et si non optimisé. Alors, est-il judicieux d'allouer des variables alignées sur la pile au lieu d'appeler memalign()?
Jérôme Pouiller
13

Les GCC récents (testés avec 4.5.2-8ubuntu4) semblent fonctionner comme prévu avec le tableau correctement aligné.

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

Je reçois:

0x7ffffffefff0 0x7ffffffefff4 0x7ffffffefff8 0x7ffffffefffc
0x7ffffffef000 0x7ffffffef004 0x7ffffffef008 0x7ffffffef00c
0x7ffffffe0000 0x7ffffffe0004 0x7ffffffe0008 0x7ffffffe000c
Affaire Caleb
la source
C'est un peu surprenant, étant donné que les tableaux sont alloués dans la pile - cela signifie-t-il que la pile est maintenant pleine de trous?
ysap
Ou sa pile est alignée sur 16 octets.
user7116
9

L'alignement n'est pas efficace pour tous les types. Vous devriez envisager d'utiliser une structure pour voir les attributs en action:

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

Et puis, vous lirez:

0x603000 0x604000 0x605000 0x606000

C'est ce à quoi vous vous attendiez.

Edit: Poussé par @yzap et suivant le commentaire @Caleb Case, le problème initial est uniquement dû à la version GCC . J'ai vérifié GCC 3.4.6 vs GCC 4.4.1 avec le code source du demandeur:

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

Il est maintenant évident que les anciennes versions de GCC (quelque part avant 4.4.1) présentent des pathologies d'alignement.

Note 1: Mon code proposé ne répond pas à la question que j'ai comprise comme «aligner chaque champ du tableau».

Note 2: Mettre un [] non statique dans main () et compiler avec GCC 3.4.6 casse la directive d'alignement du tableau de struct mais garde la distance 0x1000 entre les structs ... toujours mauvais! (voir la réponse @zifre pour les solutions de contournement)

levif
la source
2
Comme l'a répondu zifre, ce n'est pas le type, mais le fait que vous l'avez rendu statique dans votre version.
ysap
@ysap, c'est à la fois la version GCC et la définition globale qui l'ont fait fonctionner. Merci pour votre commentaire! J'ai modifié la réponse pour y remédier. :)
levif