2014-12-05 14 views
6

La mia domanda è semplice, perché l'architettura x86 utilizza due registri stack (esp ; ebp)?Perché l'architettura x86 utilizza due registri stack (esp; ebp)?

La lunghezza del frame dello stack è stata determinata fase di compilazione, allora che possiamo solo usare un registro (ad esempio esp) per accedere alla pila, e mantenere l'indirizzo di base che ha usato per essere in registro ebp sulla stack (o in altre aree di memoria, ma ciò comporterebbe una maggiore penalizzazione delle prestazioni)

È possibile?

+3

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'. –

+0

@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

+1

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. –

risposta

10

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.

+0

Ciao paxdiablo, grazie per la tua risposta! Conosco il concetto di puntatore "stack" e puntatore "base", ma sono curioso, perché utilizzare due registri quando si progetta x86? x86 è limitato da registri che si confrontano con ARM, quindi perché sprecare due registri sull'accesso allo stack? – computereasy

+0

Questo codice assembly può essere riscritto utilizzando solo un registro stack. – computereasy

+3

@computereasy: sì, ma se, durante la chiamata alla funzione, si sta _changing_ quel puntatore stack, la generazione del codice sarà molto più complessa: l'accesso locale e dei parametri dovrà essere consapevole di come 'esp' ha cambiato durante la funzione. – paxdiablo

2

Perché l'architettura x86 utilizza due registri stack (esp; ebp)?

Secondo @ GSG di risposta alla domanda relativa "Do any languages/compilers utilize the x86 ENTER instruction with a nonzero nesting level?" l'architettura x86 è stata progettata 30 anni fa come" Pascal machine".

Vedere The 8086/8088 Primer, by Stephen P. Morse, Chapter 8: High-Level-Language Programming (Pascal) per logica da uno dei progettisti di chip.

Come tale incorpora il supporto hardware per subroutine nidificate e ricorsive (procedure, funzioni) come nel Pascal programming language questo era un aspetto importante del structured programming paradigm supposto per produrre codice che fosse più facile da leggere/scrivere/mantenere

Il supporto hardware sotto forma di istruzioni CPU speciali abilitato per generare codice utilizzando meno istruzioni. Meno istruzioni di solito significavano anche codice più veloce.

È possibile implementare l'accesso variabile del frame dello stack su un'altra macchina Turing complete senza l'utilizzo del registro ebp?

Sì, ma con diversi tempi di esecuzione

Problemi correlati