Dove viene spinto?
esp - 4
. Più precisamente:
esp
viene sottratto per 4
- il valore viene spinto a
esp
pop
inverte questo.
L'ABI di System V dice a Linux di rsp
puntare a una posizione dello stack ragionevole quando il programma inizia a funzionare: Qual è lo stato del registro predefinito all'avvio del programma (asm, linux)? che è quello che dovresti usare di solito.
Come puoi spingere un registro?
Esempio minimo 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.
*/
Quanto sopra su GitHub con asserzioni eseguibili .
Perché è necessario?
È vero che queste istruzioni potrebbero essere facilmente implementate tramite mov
, add
e sub
.
La ragione per cui esistono, è che quelle combinazioni di istruzioni sono così frequenti che Intel ha deciso di fornirle per noi.
Il motivo per cui queste combinazioni sono così frequenti è che facilitano il salvataggio e il ripristino temporaneo dei valori dei registri in memoria in modo che non vengano sovrascritti.
Per capire il problema, prova a compilare manualmente un po 'di codice C.
Una delle difficoltà maggiori è decidere dove memorizzare ogni variabile.
Idealmente, tutte le variabili si adatterebbero ai registri, che è la memoria più veloce a cui accedere (attualmente circa 100 volte più veloce della RAM).
Ma ovviamente, possiamo facilmente avere più variabili che registri, specialmente per gli argomenti delle funzioni annidate, quindi l'unica soluzione è scrivere in memoria.
Potremmo scrivere in qualsiasi indirizzo di memoria, ma poiché le variabili locali e gli argomenti delle chiamate e dei ritorni di funzione rientrano in un bel pattern di stack, che impedisce la frammentazione della memoria , questo è il modo migliore per gestirlo. Confrontalo con la follia di scrivere un allocatore di heap.
Quindi lasciamo che i compilatori ottimizzino l'allocazione dei registri per noi, poiché questo è NP completo e una delle parti più difficili della scrittura di un compilatore. Questo problema è chiamato allocazione dei registri ed è isomorfo alla colorazione del grafico .
Quando l'allocatore del compilatore è costretto a memorizzare le cose in memoria invece che solo nei registri, ciò è noto come fuoriuscita .
Questo si riduce a una singola istruzione del processore o è più complesso?
Tutto quello che sappiamo per certo è che Intel documenta un'istruzione push
e pop
un'istruzione, quindi sono un'istruzione in questo senso.
Internamente, potrebbe essere espanso a più microcodici, uno da modificare esp
e uno per eseguire l'IO della memoria e richiedere più cicli.
Ma è anche possibile che una singola push
sia più veloce di una combinazione equivalente di altre istruzioni, poiché è più specifica.
Questo è per lo più sotto (der) documentato:
b
,w
,l
, oq
per indicare la dimensione della memoria essere manipolati. Es:pushl %eax
epopl %eax