2009-02-23 12 views
78

Sono un principiante in linguaggio assembly e ho notato che il codice x86 emesso dai compilatori di solito mantiene il puntatore del frame anche in modalità rilascio/ottimizzato, quando potrebbe usare il registro EBP per qualcos'altro. Capisco perché il puntatore del frame potrebbe rendere il codice più facile da eseguire il debug e potrebbe essere necessario se alloca() viene chiamato all'interno di una funzione. Tuttavia, x86 ha pochissimi registri e usarne due per mantenere la posizione dello stack frame quando uno sarebbe sufficiente non ha senso per me. Perché omettere il puntatore del frame è considerato una cattiva idea anche nelle build ottimizzate/di rilascio?Qual è lo scopo del registro del puntatore del frame EBP?

+13

Se pensi che x86 abbia pochissimi registri dovresti controllare 6502 :) –

+1

Correlati: [Perché usare EBP nel prologo di funzione ed epilogo?] (Http://stackoverflow.com/questions/15655553/why-to-use- ebp-in-function-prologue-epilogue) – legends2k

+1

VLA V99 può anche trarne vantaggio. –

risposta

87

Il puntatore del frame è un puntatore di riferimento che consente a un debugger di sapere dove si trova una variabile locale o un argomento con un singolo offset costante. Sebbene il valore di ESP cambi nel corso dell'esecuzione, l'EBP rimane lo stesso, consentendo di raggiungere la stessa variabile allo stesso offset (come il primo parametro sarà sempre a EBP + 8 mentre gli offset ESP possono cambiare in modo significativo dal momento in cui si spinge/popping things)

Perché i compilatori non buttano via il puntatore del frame? Perché con il puntatore del frame, il debugger può capire dove le variabili e gli argomenti locali stanno usando la tabella dei simboli poiché sono garantiti per essere a un offset costante rispetto a EBP. Altrimenti non esiste un modo semplice per capire dove una variabile locale si trova in un punto qualsiasi del codice.

Come già detto da Greg, aiuta anche lo sbobinamento dello sbobinamento per un debugger poiché EBP fornisce un elenco di frame di stack in ordine inverso, consentendo al debugger di calcolare la dimensione dello stack frame (variabili locali + argomenti) della funzione.

La maggior parte dei compilatori fornisce un'opzione per omettere i puntatori di fotogramma anche se rende il debug molto difficile. Questa opzione non dovrebbe mai essere utilizzata globalmente, nemmeno nel codice di rilascio. Non sai quando sarà necessario eseguire il debug di un arresto anomalo di un utente.

+0

Inoltre, aiuta a generare una traccia di stack se il programma si blocca. – flodin

+7

Il compilatore probabilmente sa cosa fa ESP. Gli altri punti sono validi, tuttavia, +1 – erikkallen

+3

I debugger moderni possono fare stack backtrace anche nel codice compilato con '-fomit-frame-pointer'. Quella impostazione è l'impostazione predefinita nella recente gcc. –

6

Dipende dal compilatore, certamente. Ho visto il codice ottimizzato emesso dai compilatori x86 che utilizza liberamente il registro EBP come registro generale. (Non ricordo con quale compilatore l'ho notato, però.)

I compilatori possono anche scegliere di mantenere il registro EBP per assistere con lo srotolamento dello stack durante la gestione delle eccezioni, ma ancora una volta dipende dall'attuazione precisa del compilatore.

1

L'utilizzo di frame di stack è diventato incredibilmente economico in qualsiasi hardware anche remotamente moderno. Se si dispone di frame stack economici, salvare un paio di registri non è così importante. Sono sicuro che i fotogrammi dello stack veloce rispetto a più registri sono stati un compromesso di ingegneria e sono stati ottenuti frame di stack veloci.

Quanto stai risparmiando andando registro puro? Ne vale la pena?

+0

Altri registri sono limitati dalla codifica delle istruzioni. x86-64 utilizza bit nel byte di prefisso REX per estendere la parte di istruzioni che specifica il registro da 3 a 4 bit per i registri src e dest. Se ci fosse spazio, x86-64 sarebbe probabilmente passato a 32 registri di architettura, anche se il salvataggio/ripristino di molti switch di contesto inizia a sommarsi. 15 è un enorme passo avanti da 7, ma 31 è un miglioramento molto più piccolo nella maggior parte dei casi. (senza contare il puntatore dello stack come scopo generale.) Fare push/pop velocemente è ottimo per molto di più dei semplici stack frame. Tuttavia, non è un compromesso con # di regs. –

4

Tuttavia, x86 ha pochissimi registri

Ciò è vero solo nel senso che codici operativi possono indirizzare solo 8 registri. Il processore stesso avrà in realtà molti più registri oltre a questo e userà la ridenominazione dei registri, il pipelining, l'esecuzione speculativa e altre parole d'ordine del processore per aggirare tale limite. Wikipedia ha un buon paragrafo introduttivo su cosa può fare un processore x86 per superare il limite del registro: http://en.wikipedia.org/wiki/X86#Current_implementations.

+1

La domanda originale riguarda il codice generato, che è strettamente limitato ai registri referenziabili da opcode. – Darron

+1

Sì, ma questo è il motivo per cui l'omissione del puntatore del frame nelle build ottimizzate non è così importante al giorno d'oggi. – Michael

+0

La rinomina del registro non è esattamente la stessa cosa di avere effettivamente un numero maggiore di registri disponibili. Ci sono ancora molte situazioni in cui la registrazione del registro non aiuta, ma più registri "regolari" lo farebbero. – jalf

26

Solo aggiungendo i miei due centesimi a già buone risposte.

Fa parte di una buona architettura del linguaggio avere una catena di frame stack. Il BP punta al frame corrente, dove sono memorizzate le variabili locali di subroutine. (I locali hanno offset negativi e gli argomenti sono in offset positivi.)

L'idea che si stia impedendo l'utilizzo di un registro perfettamente valido nell'ottimizzazione solleva la domanda: quando e dove l'ottimizzazione è effettivamente utile?

L'ottimizzazione vale solo in cicli stretti che 1) non chiamano funzioni, 2) dove il contatore del programma trascorre una frazione significativa del suo tempo e 3) nel codice che il compilatore effettivamente vedrà mai (cioè funzioni non di libreria). Questa è di solito una frazione molto piccola del codice generale, specialmente nei sistemi di grandi dimensioni.

Altro codice può essere attorcigliato e schiacciato per eliminare i cicli, e semplicemente non avrà importanza, perché il contatore del programma non è praticamente mai lì.

So che non l'hai chiesto, ma secondo la mia esperienza, il 99% dei problemi di prestazioni non ha nulla a che fare con l'ottimizzazione del compilatore. Hanno tutto a che fare con l'over-design.

+0

Grazie a @Mike, ho trovato la tua risposta molto utile. – sixtyfootersdude

+2

Eliminare il puntatore del frame consente anche di salvare un paio di istruzioni per ogni chiamata di funzione, che è una piccola ottimizzazione a sé stante. A proposito, il tuo uso di "elemosina la domanda" non è corretto; intendi "solleva la domanda". – augurar

+0

@augurar: risolto. Grazie. Io sono un po 'di groppa grammatica io :) –

Problemi correlati