¿Dónde se empuja?
esp - 4
. Más precisamente:
esp
se resta de 4
- el valor se empuja a
esp
pop
invierte esto.
El System V ABI le dice a Linux que rsp
señale una ubicación de pila sensible cuando el programa comienza a ejecutarse: ¿Cuál es el estado de registro predeterminado cuando se inicia el programa (asm, linux)? que es lo que debería utilizar habitualmente.
¿Cómo se puede presionar un registro?
Ejemplo mínimo de GNU GAS:
.data
/* .long takes 4 bytes each. */
val1:
/* Store bytes 0x 01 00 00 00 here. */
.long 1
val2:
/* 0x 02 00 00 00 */
.long 2
.text
/* Make esp point to the address of val2.
* Unusual, but totally possible. */
mov $val2, %esp
/* eax = 3 */
mov $3, %ea
push %eax
/*
Outcome:
- esp == val1
- val1 == 3
esp was changed to point to val1,
and then val1 was modified.
*/
pop %ebx
/*
Outcome:
- esp == &val2
- ebx == 3
Inverses push: ebx gets the value of val1 (first)
and then esp is increased back to point to val2.
*/
Lo anterior en GitHub con aserciones ejecutables .
¿Por qué es necesario?
Es cierto que esas instrucciones podrían implementarse fácilmente a través de mov
, add
y sub
.
La razón por la que existen, es que esas combinaciones de instrucciones son tan frecuentes, que Intel decidió proporcionárnoslas.
La razón por la que esas combinaciones son tan frecuentes es que facilitan guardar y restaurar los valores de los registros en la memoria temporalmente para que no se sobrescriban.
Para comprender el problema, intente compilar algo de código C a mano.
Una dificultad importante es decidir dónde se almacenará cada variable.
Idealmente, todas las variables encajarían en los registros, que es la memoria más rápida para acceder (actualmente alrededor de 100 veces más rápido que la RAM).
Pero, por supuesto, podemos tener fácilmente más variables que registros, especialmente para los argumentos de funciones anidadas, por lo que la única solución es escribir en la memoria.
Podríamos escribir en cualquier dirección de memoria, pero dado que las variables locales y los argumentos de las llamadas a funciones y los retornos encajan en un patrón de pila agradable, que evita la fragmentación de la memoria , esa es la mejor manera de lidiar con eso. Compare eso con la locura de escribir un asignador de montones.
Luego dejamos que los compiladores optimicen la asignación de registros para nosotros, ya que es NP completo y una de las partes más difíciles de escribir un compilador. Este problema se denomina asignación de registros y es isomorfo para colorear los gráficos .
Cuando el asignador del compilador se ve obligado a almacenar cosas en la memoria en lugar de solo registros, eso se conoce como derrame .
¿Esto se reduce a una sola instrucción de procesador o es más complejo?
Todo lo que sabemos con certeza es que Intel documenta una push
y una pop
instrucción, por lo que son una instrucción en ese sentido.
Internamente, podría expandirse a múltiples microcódigos, uno para modificar esp
y otro para hacer la E / S de memoria, y tomar múltiples ciclos.
Pero también es posible que una sola push
sea más rápida que una combinación equivalente de otras instrucciones, ya que es más específica.
Esto está en su mayoría sub (der) documentado:
b
,w
,l
, oq
para indicar el tamaño de la memoria que se está manipulado. Ej .:pushl %eax
ypopl %eax