67 votes

Que signifie aligner la pile?

J'ai été un haut niveau de codeur, et les architectures sont assez nouveau pour moi, j'ai donc décidé de lire le tutoriel sur le Montage ici:

http://en.wikibooks.org/wiki/X86_Assembly/Print_Version

Loin dans le tutoriel, des instructions sur la façon de convertir Bonjour le Monde! programme

#include <stdio.h>

int main(void) {
    printf("Hello, world!\n");
    return 0;
}

en l'équivalent de l'assemblée de code a été donné et le suivant est généré:

        .text
LC0:
        .ascii "Hello, world!\12\0"
.globl _main
_main:
        pushl   %ebp
        movl    %esp, %ebp
        subl    $8, %esp
        andl    $-16, %esp
        movl    $0, %eax
        movl    %eax, -4(%ebp)
        movl    -4(%ebp), %eax
        call    __alloca
        call    ___main
        movl    $LC0, (%esp)
        call    _printf
        movl    $0, %eax
        leave
        ret

Pour l'une des lignes,

andl    $-16, %esp

l'explication, c'est:

Ce code "et"s ESP avec 0xFFFFFFF0, l'alignement de la pile avec le côté plus bas de 16 octets à la frontière. Un l'examen de Mingw du code source révèle que c'est peut être pour SIMD instructions figurant dans la "_main" la routine, qui opèrent uniquement sur alignés les adresses. Depuis notre routine n'est pas contiennent des instructions SIMD, cette ligne est inutile.

Je ne comprends pas ce point. Quelqu'un peut-il me donner une explication de ce que cela signifie pour aligner la pile avec le côté 16-frontière d'octet et pourquoi elle est nécessaire? Et comment est l' andl atteindre cet objectif?

75voto

Matthew Slattery Points 21628

Assumer la pile ressemble à l'entrée d' _main (l'adresse du pointeur de pile est juste un exemple):

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230

Pousser %ebp, et de soustraire 8 de %esp de la réserve de l'espace pour les variables locales:

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230
|      %ebp       |
+-----------------+  <--- 0xbfff122c
:    reserved     :
:     space       :
+-----------------+  <--- 0xbfff1224

Maintenant, l' andl instruction des zéros le bas 4 bits de %esp, ce qui peut diminuer; dans cet exemple particulier, il a pour effet de réserver à une augmentation de 4 octets:

|    existing     |
|  stack content  |
+-----------------+  <--- 0xbfff1230
|      %ebp       |
+-----------------+  <--- 0xbfff122c
:    reserved     :
:     space       :
+ - - - - - - - - +  <--- 0xbfff1224
:   extra space   :
+-----------------+  <--- 0xbfff1220

Le but de cela est qu'il y a quelques "SIMD" (Single Instruction, Multiple Data) instructions (également connu en x86-terrain "ESS" pour "Streaming SIMD Extensions") qui peuvent effectuer des opérations en parallèle sur plusieurs mots dans la mémoire, mais besoin de ces plusieurs mots à un bloc de départ à une adresse qui est un multiple de 16 octets.

En général, le compilateur ne peut pas supposer que notamment les décalages d' %esp entraînera une adresse adéquate (parce que l'état d' %esp sur l'entrée à la fonction dépend du code d'appel). Mais, délibérément en alignant le pointeur de pile, de cette façon, le compilateur sait que l'ajout d'un multiple de 16 octets pour le pointeur de pile, le résultat sera une de 16 octets adresse alignée, ce qui est sûr pour une utilisation avec ces instructions SIMD.

21voto

dwelch Points 27195

Cela n'a pas l'air d'être pile spécifiques, mais l'alignement en général. Peut-être penser à l'expression multiple entier.

Si vous avez des articles dans une mémoire d'un octet de la taille, des unités de 1, permet de dire qu'ils sont tous alignés. Les choses qui sont de deux octets la taille, puis les entiers de 2 fois sera aligné, 0, 2, 4, 6, 8, etc. Et non des multiples entiers, 1, 3, 5, 7 ne seront pas alignés. Les éléments qui sont de 4 octets la taille, des multiples entiers de 0, 4, 8, 12, etc sont alignés, 1,2,3,5,6,7, etc ne sont pas. En va de même pour 8, 0,8,16,24 et 16 16,32,48,64, et ainsi de suite.

Ce que cela signifie est que vous pouvez rechercher à la base de l'adresse de l'élément et déterminer s'il est aligné.

taille en octets de l'adresse dans le formulaire de 
1, xxxxxxx
2, xxxxxx0
4, xxxxx00
8, xxxx000
16,xxx0000
32,xx00000
64,x000000
et ainsi de suite

Dans le cas d'un compilateur de mélange dans les données figurant dans la .segment de texte est assez simple pour aligner les données (eh bien, cela dépend de l'architecture). Mais la pile est un runtime chose, le compilateur ne peut pas normalement de déterminer où la pile sera au moment de l'exécution. Donc, au moment de l'exécution si vous avez des variables locales qui doivent être harmonisées vous auriez besoin d'avoir le code d'ajuster la pile de la programmation.

Disons, par exemple, vous avez deux 8 octets d'éléments sur la pile, un total de 16 octets, et que vous voulez vraiment alignés (sur 8 les limites d'octets). Sur l'entrée de la fonction de soustraction de 16 à partir du pointeur de pile comme d'habitude pour faire de la place pour ces deux éléments. Mais pour les aligner, il faudrait plus de code. Si nous voulions que ces deux 8 octets éléments alignés sur 8 octets limites et le pointeur de pile, après soustraction de 16 a 0xFF82, eh bien, le plus faible de 3 bits ne sont pas à 0 donc il n'est pas aligné. Le bas de trois bits sont 0b010. Dans un sens générique, nous voulons nous soustraire 2 de la 0xFF82 pour obtenir 0xFF80. Comment nous déterminons c'est un 2 serait par anding avec 0b111 (0x7) et en soustrayant de ce montant. Que signifie alu opérations de l'un et de et de soustraction. Mais nous pouvons prendre un raccourci, si nous et avec ceux qui sont le complément de la valeur de 0x7 (~0 x 7 = 0xFFFF...FFF8) nous obtenons 0xFF80 l'aide d'un alu opération (aussi longtemps que le compilateur et le processeur ont une seule opcode façon de le faire, si pas, il peut vous coûter plus de et la et la soustraction).

Cela semble être ce que votre programme est en train de faire. Anding avec -16 est le même que anding avec 0xFFFF....FFF0, résultant en une adresse qui est aligné sur un 16 frontière d'octet.

Donc pour conclure, si vous avez quelque chose comme un typique pointeur de pile qui travaille son chemin vers le bas de la mémoire de la hausse des adresses à la baisse des adresses, vous voulez

sp = sp & (~(n-1))

où n est le nombre d'octets à aligner (doivent être des puissances, mais c'est bien plus l'alignement implique généralement des puissances de deux). Si vous avez dire faire un malloc (adresses augmentation de bas en haut) et à aligner l'adresse de quelque chose (n'oubliez pas de malloc plus que vous avez besoin au moins de l'alignement de la taille) puis

if(ptr&(~(n-)) { ptr = (ptr+n)&(~(n-1)); }

Ou si vous voulez juste prendre le si là-bas et effectuer les opérations d'ajout et de masque à chaque fois.

beaucoup de/la plupart des non-x86 architectures de l'alignement des règles et des exigences. x86 est trop souple en ce qui concerne le jeu d'instruction va, mais pour autant que l'exécution de passe vous pouvez vous/devrez payer une pénalité pour non alignés accède sur un système x86, donc, même si vous pouvez le faire, vous devez vous efforcer de rester alignés, comme vous le feriez avec toute autre architecture. Peut-être est-ce que ce code était en train de faire.

8voto

tylerl Points 14541

Cela a à voir avec l' alignement d'octets. Certaines architectures nécessitent des adresses utilisées pour un ensemble d'opérations d'aligner les bits limites.

C'est, si vous voulais 64 bits d'alignement pour un pointeur, par exemple, vous pourrait théoriquement diviser l'ensemble de la mémoire adressable en 64 bits morceaux en commençant à zéro. Une adresse serait "aligné" si parfaitement dans l'une de ces morceaux, et ne sont pas alignés si elle a pris une partie de l'une partie et de la partie de l'autre.

Une caractéristique importante de l'alignement d'octets (en supposant que le nombre est une puissance de 2), c'est que les moins significatif X bits de l'adresse sont toujours à zéro. Cela permet au processeur afin de représenter plus d'adresses avec moins de bits par tout simplement pas l'aide de la bas X bits.

5voto

pmg Points 52636

Imaginez ce "dessin"

adresses
 xxx0123456789abcdef01234567 ...
 [------][------][------] ...
les registres

Valeurs à des adresses multiples de 8 "slide" facilement dans (64-bit) registres

adresses
 56789abc ...
 [------][------][------] ...
les registres

Bien sûr, les registres de "marcher" dans les étapes de 8 octets

Maintenant, si vous voulez mettre la valeur à l'adresse xxx5 dans un registre beaucoup plus difficile :-)


Edit andl -16

-16 est 11111111111111111111111111110000 en binaire

lorsque vous "et" n'importe quoi avec -16 vous obtenez une valeur avec les 4 derniers bits mis à 0 ... ou un multitple de 16 ans.

4voto

chrisaycock Points 12900

Lorsque le processeur charge les données à partir de la mémoire dans un registre, il doit accéder par une adresse de base et une taille. Par exemple, il ira chercher les 4 octets de l'adresse 10100100. Notez qu'il existe deux zéros à la fin de cet exemple. C'est parce que les quatre octets sont stockés, de sorte que le 101001 leader bits sont importantes. (Le processeur vraiment accède à ces derniers par le biais d'un "don't care" par l'extraction 101001XX.)

Afin d'aligner quelque chose dans les moyens de mémoire pour réorganiser les données (généralement par le biais de rembourrage), de sorte que l'élément désiré à l'adresse aura assez de zéro octets. En continuant l'exemple ci-dessus, nous ne pouvons pas nous extraire de 4 octets à partir de 10100101 depuis les deux derniers bits ne sont pas à zéro; que serait la cause d'une erreur de bus. Nous devons donc à la bosse de l'adresse jusqu'à 10101000 (et les déchets de trois emplacements d'adresse dans le processus).

Le compilateur fait cela pour vous automatiquement et est représenté dans le code assembleur.

Notez que cela est manifeste que l'optimisation en C/C++:

struct first {
    char letter1;
    int number;
    char letter2;
};

struct second {
    int number;
    char letter1;
    char letter2;
};

int main ()
{
    cout << "Size of first: " << sizeof(first) << endl;
    cout << "Size of second: " << sizeof(second) << endl;
    return 0;
}

La sortie est

Size of first: 12
Size of second: 8

En réarrangeant les deux chars'signifie que l' int sera aligné correctement, et donc le compilateur n'a pas à bosse de l'adresse de base via le rembourrage. C'est pourquoi la taille de la seconde est plus petite.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X