Questo dipende ovviamente dalla convenzione di chiamata, ma generalmente è come questo.
Il stack è in grado di essere spostato arbitrariamente in base a qualsiasi cosa sia necessario premere o estrarre dallo stack in qualsiasi momento. Ciò può accadere in qualsiasi momento all'interno di una funzione, in quanto è necessario salvare temporaneamente alcuni dati nello stack.
Il di base puntatore è generalmente impostato lo stesso valore per una determinata profondità dello stack, e viene utilizzato per accedere passa parametri (su un lato) e variabili locali (sull'altro lato). Viene anche utilizzato per spostare rapidamente il puntatore dello stack all'uscita da una funzione.
Il motivo è fatto in questo modo è quello di semplificare il codice in modo che non sei dover riferire i contenuti dello stack basata su uno stack pointer possibilmente evoluzione. L'utilizzo del puntatore di base semplifica considerevolmente l'attività di generazione del codice (non è necessario sapere quale sia il puntatore dello stack in un dato momento, basta utilizzare il puntatore di base, che rimane lo stesso per la durata della funzione).
Senza questo, il codice che ha voluto spingere due copie di una variabile locale per chiamare la funzione successiva , sarebbe simile a questa:
mov eax, [esp+16] ; get var1
push eax ; push it
mov eax, [esp+20] ; get var1 again
push eax
call _somethingElse
Metti da parte il fatto che si no ricarica eax
in questo caso, il punto che sto cercando di fare è che la posizione relativa degli oggetti da un puntatore dello stack mobile possa complicare inutilmente le cose.
Ad esempio, ecco una funzione codificato in assembly che segue una convenzione di chiamata comune:
_doSomething:
push ebp ; stack current base pointer
mov ebp, esp ; save previous stack pointer
sub esp, 48 ; incl 48 bytes local storage
; do whatever you want here, including changing
; esp, as long as it ends where it started.
mov esp, ebp ; restore previous stack pointer
pop ebp ; and previous base pointer
ret ; then return
_callIt:
mov eax, 7
push eax ; push parameter for function
call _doSomething
add esp, 4 ; get rid of pushed value
:
Se si segue il codice, si può vedere che ebp
all'interno del corpo della funzione è un punto di riferimento fisso con [ebp]
(contenuto di ebp
) come indirizzo di ritorno, [ebp+4]
come valore spinto di 7
e [ebp-N]
come spazio di archiviazione locale per _doSomething
, dove N varia da 1
a 48
.
Questo è il caso indipendentemente dal numero di elementi inseriti o inseriti nel corpo della funzione.
compilatori regolarmente fare quello che proponete _Quando la StackFrame è fixed_, come fai notare. Tuttavia, sia VLA che 'alloca()' possono alterare lo stackframe in modo dinamico; Quindi diventa utile avere sia 'esp' che' ebp'. –
@IwillnotexistIdonotexist, ciao, grazie per la risposta. IMHO, 'VLA' e' alloca() 'sono situazioni abbastanza rare e dubito anche che i compilatori possano produrre codice assembly che li contenga. Perché i progettisti di x86 dovrebbero perdere 1/8 del registro generale per gestire questo raro caso? – computereasy
Non lo fanno; Le funzioni che non usano 'alloca()' & co. (che è la stragrande maggioranza) di solito non "sprecano" il registro, mentre le funzioni che lo fanno, lo fanno. E sì, i compilatori producono il codice corrispondente se si usano VLA come, per esempio, 'int foo (int n) {int bar [n];/* ... */return bar [0];} 'o chiama' alloca() '. È difficile tracciare dove è la parte superiore dello stackframe altrimenti. –