2009-03-23 8 views
37

Che cos'è l'allineamento dello stack? Perché viene usato? Può essere controllato dalle impostazioni del compilatore?cos'è "stack alignment"?

I dettagli di questa domanda sono presi da un problema affrontato quando si tenta di utilizzare le librerie ffmpeg con msvc, tuttavia quello che mi interessa veramente è una spiegazione di cosa è "allineamento dello stack".

I Dettagli:

  • Quando runnig mia msvc programma, che si collega a avcodec ricevo il seguente errore rispettata: "compilatore non ha allineato variabili di stack libavcodec è stato miscompiled ", seguito da un incidente in avcodec.dll.
  • avcodec.dll non è stato compilato con msvc, quindi non riesco a vedere cosa sta succedendo all'interno.
  • Quando si esegue ffmpeg.exe e si utilizza lo stesso avcodec.dll, tutto funziona correttamente.
  • ffmpeg.exe non è compilato con msvc, fu rispettato gcc/MinGW (stesso avcodec.dll)

Grazie,

Dan

+1

Poiché gli altri hanno spiegato che cos'è l'allineamento dello stack e perché viene utilizzato, voglio solo aggiungere i miei due centesimi relativi a _ "Può essere controllato dalle impostazioni del compilatore?" _. Vedi [questa domanda] (http://stackoverflow.com/questions/5496045/why-is-my-stack-pointer-only-incrementing-in-multiples-of-16?lq=1) – andreee

risposta

91

allineamento delle variabili in memoria (a breve storia).

In passato i computer avevano un databus a 8 bit. Ciò significa che ogni ciclo di clock può elaborare 8 bit di informazioni. Che andava bene allora.

Poi sono arrivati ​​computer a 16 bit. A causa della compatibilità verso il basso e di altri problemi, il byte a 8 bit è stato mantenuto e la parola a 16 bit è stata introdotta. Ogni parola era di 2 byte. E ogni ciclo di clock 16 bit di informazioni potrebbero essere elaborati. Ma questo ha posto un piccolo problema. sguardo

Let ad una mappa di memoria:

+----+ 
|0000| 
|0001| 
+----+ 
|0002| 
|0003| 
+----+ 
|0004| 
|0005| 
+----+ 
| .. | 

Ad ogni indirizzo v'è un byte cui si può accedere individualmente. Ma le parole possono essere recuperate solo agli indirizzi pari. Quindi se leggiamo una parola a 0000, leggiamo i byte a 0000 e 0001. Ma se vogliamo leggere la parola alla posizione 0001, abbiamo bisogno di due accessi di lettura. Prima 0000,0001 e poi 0002,0003 e manteniamo solo 0001,0002.

Naturalmente questo ha richiesto un po 'di tempo extra e questo non è stato apprezzato. Ecco perché hanno inventato l'allineamento. Quindi memorizziamo le variabili di parola ai confini delle parole e alle variabili di byte ai limiti dei byte.

Per esempio, se abbiamo una struttura con un campo di byte (B) e un campo di parola (W) (e un compilatore molto ingenuo), otteniamo la seguente:

+----+ 
|0000| B 
|0001| W 
+----+ 
|0002| W 
|0003| 
+----+ 

Che non è divertente . Ma quando si usa l'allineamento delle parole troviamo:

+----+ 
|0000| B 
|0001| - 
+----+ 
|0002| W 
|0003| W 
+----+ 

Qui la memoria viene sacrificata per la velocità di accesso.

Si può immaginare che quando si utilizza la doppia parola (4 byte) o la parola quadrupla (8 byte) questo è ancora più importante. Ecco perché con la maggior parte dei compilatori moderni è possibile scegliere quale allineamento si sta utilizzando durante la compilazione del programma.

+3

Ottima descrizione dell'allineamento dello stack ! –

+0

Sto cercando di imparare l'assemblea, e ho lottato con la comprensione dell'allineamento. Questo risponde totalmente alle mie domande! – joek1975

+0

Sempre felice di aiutare qualcuno :-). –

11

IIRC, l'allineamento dello stack si verifica quando le variabili vengono posizionate nello stack "allineate" a un numero specifico di byte. Quindi, se si utilizza un allineamento dello stack a 16 bit, ciascuna variabile nello stack inizierà da un byte che è un multiplo di 2 byte dal puntatore dello stack corrente all'interno di una funzione.

Ciò significa che se si utilizza una variabile che è < 2 byte, ad esempio un char (1 byte), ci saranno 8 bit di "padding" non utilizzati tra esso e la variabile successiva. Ciò consente alcune ottimizzazioni con ipotesi basate su posizioni variabili.

Quando si chiamano funzioni, un metodo per passare argomenti alla funzione successiva consiste nel posizionarle nello stack (anziché collocarle direttamente nei registri). L'utilizzo o meno dell'allineamento qui è importante, in quanto la funzione di chiamata colloca le variabili in pila, per essere lette dalla funzione di chiamata utilizzando gli offset. Se la funzione chiamante allinea le variabili e la funzione chiamata si aspetta che non siano allineate, la funzione chiamata non sarà in grado di trovarle.

Sembra che il codice compilato da msvc sia in disaccordo sull'allineamento delle variabili. Prova a compilare con tutte le ottimizzazioni disattivate.

+1

sizeof (char) è sempre 1 byte, che è sempre almeno 8 bit ... non byte. L'allineamento dipende dalla piattaforma del compilatore e (x86, comunque) è generalmente 4byte per architetture a 32 bit, 8byte per arch 64 bit. – snemarch

+1

Grazie, era davvero un brainfart delle dimensioni di un byte: P. Avevo scelto 16 byte come esempio arbitrario, ma l'utilizzo dell'esempio più piccolo lo rende molto più chiaro. –

10

Alcune architetture CPU richiedono un allineamento specifico di vari tipi di dati e generano eccezioni se non si rispetta questa regola. Nella modalità standard, x86 non richiede questo per i tipi di dati di base, ma può subire delle penalizzazioni prestazionali (consultare www.agner.org per suggerimenti di ottimizzazione di basso livello).

Tuttavia, il set di istruzioni SSE (spesso utilizzato per l'elaborazione audio/video ad alte prestazioni) ha requisiti di allineamento rigorosi e genera eccezioni se si tenta di utilizzarlo su dati non allineati (a meno che non si utilizzi, su alcuni processori, versioni molto più lente e non allineate).

Il tuo problema è probabilmente che uno si aspetta che il compilatore chiamante per mantenere lo stack allineato, mentre l'altro si aspetta callee per allineare la pila se necessario.

EDIT: come per il motivo per cui l'eccezione accade, una routine nella DLL probabilmente vuole utilizzare istruzioni SSE su alcuni dati di stack temporanei, e non riesce perché i due compilatori diversi non sono d'accordo sulle convenzioni di chiamata.

2

Per quanto ne so, i compilatori in genere non allineano le variabili che sono in pila. La libreria potrebbe dipendere da alcune serie di opzioni del compilatore che non sono supportate dal compilatore. La soluzione normale consiste nel dichiarare le variabili che devono essere allineate come statiche, ma se si fa questo nel codice di altre persone, si vuole essere sicuri che le variabili in questione siano inizializzate più avanti nella funzione piuttosto che in la dichiarazione.

// Some compilers won't align this as it's on the stack... 
int __declspec(align(32)) needsToBe32Aligned = 0; 
// Change to 
static int __declspec(align(32)) needsToBe32Aligned; 
needsToBe32Aligned = 0; 

In alternativa, trovare un commutatore che allinea le variabili nello stack. Ovviamente la sintassi di allineamento "__declspec" che ho usato qui potrebbe non essere quella che usa il compilatore.