¿Dónde se empuja?
esp - 4
. Más precisamente:
esp
consigue resta por 4
- el valor es empujado a
esp
pop
invierte este.
El Sistema V ABI dice a Linux para hacer rsp
punto a una ubicación pila sensata cuando el programa se pone en marcha: https://stackoverflow.com/a/32967009/895245 que es lo que debe utilizar normalmente.
¿Cómo se puede presionar un registro?
Minimal GNU ejemplo 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 with assertions.
¿Por qué es necesario?
Es cierto que esas instrucciones podrían implementarse fácilmente a través de mov
, add
y sub
.
Ellos razonan que existen, es que esas combinaciones de instrucciones son tan frecuentes, que Intel decidió proporcionarlas para nosotros.
La razón por la que esas combinaciones son tan frecuentes, es que hacen que sea fácil de guardar y restaurar los valores de los registros de la memoria temporal para que no se sobreescriben.
Para comprender el problema, intente compilar código C a mano.
Una dificultad importante es decidir dónde se almacenará cada variable.
Lo ideal es que todas las variables quepan en los registros, que es la memoria más rápida para acceder (actualmente es 100x faster que la RAM).
Pero, por supuesto, podemos tener fácilmente más variables que los 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 de función y devoluciones encajan en un buen patrón de pila, que previene memory fragmentation, esa es la mejor manera de manejarlo. Compare eso con la locura de escribir un asignador de montón.
Luego dejamos que los compiladores optimicen la asignación de registros para nosotros, ya que es NP completa, y una de las partes más difíciles de escribir un compilador. Este problema se llama register allocation, y es isomorfo a graph coloring.
Cuando el asignador del compilador se ve obligado a almacenar cosas en la memoria en lugar de solo registros, 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 instrucción pop
, por lo que son una instrucción en ese sentido.
Internamente, podría expandirse a múltiples microcódigos, uno para modificar esp
y uno para hacer la memoria IO, y tomar varios ciclos.
Pero también es posible que un solo push
sea más rápido que una combinación equivalente de otras instrucciones, ya que es más específico.
Esto es sobre todo de las Naciones Unidas (der) documentado:
Advertencia: todas las respuestas actuales se proporcionan en la sintaxis de ensamblaje de Intel; push-pop en la sintaxis de AT & T, por ejemplo, usa una corrección posterior como 'b',' w', 'l' o' q' para indicar el tamaño de la memoria que se está manipulando. Ej: 'pushl% eax' y' popl% eax' – Hawken
@hawken En la mayoría de los ensambladores capaces de tragar la sintaxis de AT & T (notablemente gas) el tamaño del sufijo puede ser omitido si el tamaño del operando puede deducirse del tamaño del operando. Este es el caso de los ejemplos que ha dado, ya que '% eax' siempre tiene un tamaño de 32 bits. – hirschhornsalz