2010-11-13 21 views
38

me han sido un codificador de alto nivel, y arquitecturas son bastante nuevo para mí, así que decidí leer el tutorial sobre la Asamblea aquí:¿Qué significa alinear la pila?

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

lejos en el tutorial, instrucciones sobre cómo convertir el Hola Mundo! se le dio programa

#include <stdio.h> 

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

en código ensamblador equivalente y se generó el siguiente:

 .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 

Para una de las líneas,

andl $-16, %esp 

la explicación fue:

Este código "y" s ESP con 0xFFFFFFF0, alineando la pila con el siguiente límite más bajo de 16 bytes. Un examen del código fuente de Mingw revela que esto puede ser para instrucciones SIMD que aparecen en la rutina "_main" , que operan solo en direcciones alineadas . Dado que nuestra rutina no contiene instrucciones SIMD, esta línea es innecesaria.

No entiendo este punto. ¿Puede alguien darme una explicación de lo que significa alinear la pila con el próximo límite de 16 bytes y por qué es necesario? ¿Y cómo es el andl logrando esto?

+3

http://en.wikipedia.org/wiki/Data_structure_alignment – chrisaycock

+1

No tiene mucho sentido mirar código de máquina sin habilitar el optimizador. –

Respuesta

51

asumir la pila se ve así en la entrada a _main (la dirección del puntero de pila es sólo un ejemplo):

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

empuje %ebp, y restar 8 de %esp a reservar un poco de espacio para las variables locales:

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

Ahora, la instrucción andl ceros los bajos 4 bits de %esp, que puede diciembre arréglalo; en este ejemplo particular, tiene el efecto de reservar un adicional de 4 bytes:

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

El punto de esto es que hay algunos (Single Instruction, Multiple Data) instrucciones "SIMD" (también conocidos en x86-tierra como "SSE" para "Streaming SIMD Extensions") que puede realizar operaciones paralelas en varias palabras en la memoria, pero requiere que esas palabras múltiples sean un bloque que comienza en una dirección que es un múltiplo de 16 bytes.

En general, el compilador no puede suponer que determinados desplazamientos de %esp darán como resultado una dirección adecuada (porque el estado de %esp al ingresar a la función depende del código de llamada). Pero, al alinear deliberadamente el puntero de pila de esta manera, el compilador sabe que agregar cualquier múltiplo de 16 bytes al puntero de pila dará como resultado una dirección alineada de 16 bytes, que es segura para usar con estas instrucciones SIMD.

+0

Ahora, la instrucción andl pone a cero los 4 bits bajos de% esp, lo que puede disminuirlo. Entonces, ¿cómo sabe el compilador cuántos bytes disminuyeron más tarde en la pila de balances? – secmask

+3

@secmask: el valor de '% esp' justo después de pulsar el original '% ebp' se ha almacenado en'% ebp', por lo que no es necesario que lo sepa, ya que '% ebp' apunta a la parte superior de la reserva espacio. '% esp' se restaura con la instrucción' leave' en el código que se muestra - 'leave' es equivalente a' movl% ebp,% esp; popl% ebp'. –

3

Debe estar solo en direcciones pares, no en las impares, porque hay un déficit de rendimiento al acceder a ellas.

+0

Esto no tiene nada que ver con el rendimiento. La CPU simplemente no puede obtener datos de una dirección no alineada, ya que eso sería un error de bus. – chrisaycock

+0

Error de bus o no, no falla. –

+0

@chrisaycock Los procesadores modernos pueden, con una pequeña penalización de rendimiento. – YoYoYonnY

7

Esto tiene que ver con byte alignment. Ciertas arquitecturas requieren que las direcciones utilizadas para un conjunto específico de operaciones se alineen con límites de bits específicos.

Es decir, si desea una alineación de 64 bits para un puntero, por ejemplo, puede dividir conceptualmente toda la memoria direccionable en trozos de 64 bits empezando en cero. Una dirección se "alinearía" si encaja exactamente en uno de estos fragmentos, y no se alinearía si formara parte de un fragmento y parte de otro.

Una característica importante de la alineación de bytes (suponiendo que el número sea una potencia de 2) es que los bits menos significativos X de la dirección son siempre cero. Esto permite que el procesador represente más direcciones con menos bits simplemente al no usar los X bits inferiores.

+1

+1 de mi lado también! Gracias por la explicación. – Legend

5

imaginar esto "dibujo"

 
addresses 
xxxabcdef... 
    [------][------][------] ... 
registers 

valores en las direcciones múltiples del "slide" 8 fácilmente en (64 bits) registra

 
addresses 
     56789abc ... 
    [------][------][------] ... 
registers 

Por supuesto registra "paseo" en pasos de 8 bytes

Ahora, si desea poner el valor en la dirección xxx5 en un registro es mucho más difícil :-)


Editar andl -16

-16 es 11111111111111111111111111110000 en binario

cuando "y" cualquier cosa con -16 se obtiene un valor con los últimos 4 bits puestos a 0 o ... un múltiplo de 16.

3

Cuando el procesador carga datos de la memoria en un registro, necesita acceder por una dirección base y un tamaño. Por ejemplo, obtendrá 4 bytes de la dirección 10100100. Observe que hay dos ceros al final de ese ejemplo. Esto se debe a que los cuatro bytes están almacenados de manera que los 101001 bits principales son significativos. (El procesador realmente accede a estos a través de un "no me importa" por ir a buscar 101001XX.)

Así alinear algo en medios de memoria para reorganizar los datos (generalmente a través de relleno) de manera que la dirección del elemento deseado tendrá suficientes cero bytes. Continuando con el ejemplo anterior, no podemos obtener 4 bytes de 10100101 ya que los últimos dos bits no son cero; eso causaría un error de bus. Por lo tanto, debemos ubicar la dirección hasta 10101000 (y perder tres ubicaciones de direcciones en el proceso).

El compilador hace esto automáticamente y se representa en el código de ensamblaje.

Tenga en cuenta que esto se manifiesta como una optimización 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 salida es

Size of first: 12 
Size of second: 8 

Reorganización de los dos char 's significa que el int estarán alineados correctamente, y por lo que el compilador no tiene que golpear la dirección base a través del relleno. Es por eso que el tamaño del segundo es más pequeño.

13

Esto no suena a apilar específico, pero la alineación en general. Quizás piense en el término entero múltiple.

Si tiene elementos en la memoria que son un byte de tamaño, unidades de 1, entonces digamos que todos ellos están alineados. Las cosas que tienen dos bytes de tamaño, luego los enteros multiplicados por 2 se alinearán, 0, 2, 4, 6, 8, etc. Y los múltiplos no enteros, 1, 3, 5, 7 no se alinearán. Los elementos que tienen 4 bytes de tamaño, los múltiplos enteros 0, 4, 8, 12, etc. están alineados, 1,2,3,5,6,7, etc. no lo están. Lo mismo vale para 8, 0,8,16,24 y 16 16,32,48,64, y así sucesivamente.

Lo que esto significa es que usted puede mirar en la dirección base para el artículo y determinar si está alineado.

 
size in bytes, address in the form of 
1, xxxxxxx 
2, xxxxxx0 
4, xxxxx00 
8, xxxx000 
16,xxx0000 
32,xx00000 
64,x000000 
and so on 

En el caso de una mezcla en datos con instrucciones en el segmento .text es bastante sencillo para alinear datos según sea necesario compilador (bueno, depende de la arquitectura). Pero la pila es una cosa de tiempo de ejecución, el compilador normalmente no puede determinar dónde estará la pila en tiempo de ejecución. Entonces, en el tiempo de ejecución, si tiene variables locales que necesitan alinearse, necesitará que el código ajuste la pila de forma programática.

Digamos por ejemplo que tiene dos elementos 8 bytes en la pila 16, el total de bytes, y que realmente quiere que alineados (en 8 límites de bytes). Al ingresar, la función restaría 16 del puntero de la pila como de costumbre para dejar espacio para estos dos elementos. Pero para alinearlos, debería haber más código. Si queríamos estos dos elementos de 8 bytes alineados en los límites de 8 bytes y el puntero de la pila después de restar 16 era 0xFF82, los 3 bits inferiores no son 0, por lo que no están alineados. Los tres bits más bajos son 0b010. En un sentido genérico, queremos restar 2 del 0xFF82 para obtener 0xFF80. La forma en que determinamos que es un 2 sería haciendo anding con 0b111 (0x7) y restando esa cantidad. Eso significa operaciones alu y un restar. Pero podemos tomar un atajo si nosotros y con el valor de complemento de 0x7 (~ 0x7 = 0xFFFF ... FFF8) obtenemos 0xFF80 usando una operación alu (siempre que el compilador y el procesador tengan una sola forma de código de operación para hacer eso, si no puede costarle más que el yy restar).

Esto parece ser lo que su programa estaba haciendo. Anding con -16 es lo mismo que anding con 0xFFFF .... FFF0, lo que resulta en una dirección alineada en un límite de 16 bytes.

Así que para terminar con esto, si usted tiene algo así como un puntero de pila típica, que se cuela por la memoria de las direcciones más altas para disminuir las direcciones, a continuación, desea

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

donde n es el número de bytes alinear (debe haber poderes, pero está bien que la alineación por lo general involucre poderes de dos). Si usted tiene opinión realizado un malloc (direcciones aumentan de bajo a alto) y quieren alinear la dirección de algo (recuerde malloc más de lo necesario por lo menos el tamaño de alineación) y luego

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

O si lo desea simplemente tome el si está allí y realice el agregado y la máscara cada vez.

muchas/la mayoría de arquitecturas que no son x86 tienen reglas y requisitos de alineación. x86 es demasiado flexible en lo que respecta al conjunto de instrucciones, pero en lo que respecta a la ejecución, puede/pagará una penalización por accesos no alineados en un x86, por lo que aunque pueda hacerlo, debe esforzarse por mantenerse alineado como lo haría con cualquier otra arquitectura. Tal vez eso es lo que estaba haciendo este código.

+1

Excelente respuesta, ¿por qué está en la parte inferior de la página? – jwbensley