miércoles, 21 de mayo de 2008

Alineamiento de memoria

Cuando accedemos a un dato en memoria en la arquitectura x86 podemos referenciarlo por bytes, sin embargo, la mínima cantidad de información que puede traer de memoria el procesador es una palabra (4 bytes). ¿Cuál es el problema?

Imaginemos la situación en la que queremos acceder a una palabra que está en la dirección de memoria 100, el procesador traerá correctamente desde la dirección 100 a la 103 (inclusive).
Sin embargo, si la palabra a la que queremos acceder empieza en la dirección 101, tendremos que acceder dos veces, 100-103 y 104-107. Dos accesos de memoria para traer tan solo una palabra, la mitad de la información traída de memoria en este caso no es útil.

¿Qué hacer para solucionarlo? La mayoría de las veces no debemos preocuparnos por este hecho, el compilador se encarga de solucionar el tema. Veamos el siguiente código en C.

#include <stdio.h>

int main(int argc, char **argv){
char x[5];

printf("%d\n", sizeof(x));
}

No hace mucho, simplemente declara un array y llama a la función printf. Si dejamos que el compilador (gcc en este caso) lo traduzca a ensamblador obtendremos esto.

.file   "prog.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $0, %eax
addl $15, %eax
addl $15, %eax
shrl $4, %eax
sall $4, %eax
subl %eax, %esp
subl $8, %esp
pushl $5
pushl $.LC0
call printf
addl $16, %esp
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.0.2 20050901 (prerelease) (SUSE Linux)"
.section .note.GNU-stack,"",@progbits

En el código podemos ver el prólogo y el epílogo habitual de una función, la llamada a printf, algunos ajustes en la pila, y 3 instrucciones que en principio no tienen mucho sentido, andl, shrl, sall.

Estas tres instrucciones se utilizan en esta ocasión para hacer el alineamiento de memoria, en primer lugar andl $-16,%esp es lo mismo que and $0xfffffff0,%esp, es decir, mantiene la dirección de %esp haciendo los últimos 4 bits a cero.

Las instrucciones shrl y sall hacen lo mismo, si primero dseplazamos el valor de un registro 4 bits a la derecha y luego 4 hacia la izquierda, lo que realmente estamos haciendo es poniendo los últimos 4 bits a cero. Evidentemente se pueden cambiar estas dos instrucciones por un andl como el anterior. El siguiente programa es el mismo ahorrándonos unas cuantas instrucciones.

.file   "prog.c"
.section .rodata
.LC0:
.string "%d\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
andl $-16, %esp
movl $30, %eax
andl $-16, %eax
subl %eax, %esp
subl $8, %esp
pushl $5
pushl $.LC0
call printf
addl $16, %esp
leave
ret
.size main, .-main
.ident "GCC: (GNU) 4.0.2 20050901 (prerelease) (SUSE Linux)"
.section .note.GNU-stack,"",@progbits

0 comentarios: