2014-11-12 21 views
5

In C come molti di voi sanno, lo stack è dove risiedono tutte le variabili locali. La pila è la prima nella struttura dei dati dell'ultima uscita, quindi è possibile accedere solo a ciò che è stato inserito più recentemente su di esso. Quindi, dato il seguente codice:Qual è l'idea alla base dell'utilizzo di uno stack per le variabili locali?

int k = 5; 
int j = 3; 
short int i; 

if (k > j) i = 1; 

Ovviamente si tratta di codice inutile che non ha alcun significato reale, ma sto cercando di avvolgere la mia testa intorno a qualcosa.

Per la dichiarazione breve int i suppongo che 2 byte vengano allocati nello stack. Per int k e int j per entrambi i 4 byte ottenere assegnato con i valori 5 e 3. Così lo stack apparirebbe come segue

---------- <- stack pointer 
int i 
---------- 
int k = 5 
---------- 
int j = 3 
---------- 

così per l'istruzione if si dovrà pop l'INT per arrivare al condizioni k e j, e se sì, dove devo andare? Tutto ciò sembra molto dispendioso in termini di tempo e noioso se questo è il modo in cui C fa variabili locali.

Quindi, in questo modo, C fa o sto facendo il pieno?

+0

in realtà il layout degli elementi nello stack è errato, in quanto io sono un corto, non un int. e uno stack cresce verso il basso nella memoria, non verso l'alto e le variabili locali sono messe in pila, nell'ordine inverso. Irregardless, qualsiasi valore sullo stack locale NON viene scoppiato piuttosto che viene usato un offset dal puntatore dello stack, quindi facendo riferimento a 'k' si ottiene "read word (alcuni registri) da sp [(offset a k)" allo stesso modo per 'j 'variabile. e 'i' è impostato come "scrivi halfword sp [offset a i] da (metà inferiore del registro che contiene il risultato di k + j) in generale puoi pensare allo stack come un lungo array con partizioni – user3629249

risposta

4

Sei sempre così pacchiando.

Sì, le variabili locali (auto) vengono in genere memorizzate su una pila. Tuttavia, non vengono estratti dalla pila quando letti; sono referenziati da un offset dal puntatore dello stack.

prendere il codice seguente:

x = y + z; 

dove ognuno di x, y e z sono assegnati in pila.Quando il compilatore genera il codice macchina equivalente, si farà riferimento a ciascuna variabile da un offset da un determinato registro, specie come:

mov -8(%ebp), %eax 
add -12(%ebp), %eax 
mov %eax, -4(%ebp) 

In architetture x86, %ebp è il puntatore telaio; lo stack è suddiviso in frame, in cui ogni frame contiene parametri di funzione (se presenti), l'indirizzo di ritorno (ovvero l'indirizzo dell'istruzione che segue la chiamata di funzione) e le variabili locali (se presenti). Nei sistemi con cui ho familiarità, lo stack cresce "verso il basso" verso 0 e le variabili locali sono memorizzate "sotto" il puntatore del frame (indirizzi inferiori), quindi l'offset negativo. Il codice sopra presuppone che , e z sia -12(%ebp).

Tutto verrà rimosso dallo stack quando la funzione ritorna, ma non prima.

EDIT

prega di notare che nessuno di questa agisce su mandato del definizione del linguaggio C. La lingua non richiede l'uso di uno stack di runtime su tutti (anche se un compilatore sarebbe una cagna da implementare senza uno). Definisce semplicemente la durata delle variabili auto dalla fine della loro dichiarazione fino alla fine del loro ambito di chiusura. Una pila lo rende facile, ma non è necessario.


1. Bene, i puntatori stack e frame verranno impostati su nuovi valori; i dati rimarranno dove erano, ma quella memoria è ora disponibile per qualcos'altro da usare.

7

La pila non è una pila. È ancora accesso casuale memoria, il che significa che è possibile accedere a qualsiasi posizione in tempo costante. L'unico scopo della disciplina dello stack è di assegnare ad ogni funzione una propria area di memoria privata che la funzione può essere sicura non è utilizzata da nessun altro.

+0

In effetti, se guardi allo smontaggio di una funzione C, non troverete push e pop per le variabili locali, troverete un offset costante dal puntatore dello stack di base - le variabili si trovano in un luogo molto prevedibile oltre ad essere nella memoria ad accesso casuale. L'istruzione per caricare 'k' nell'esempio dell'OP, ad esempio, assomiglierebbe a" mov eax, [ebp-4] '- il puntatore dello stack stesso, essendo variabile all'interno di una funzione, non è nemmeno effettivamente usato per i locali. –

+0

Sembra che più fonti non siano d'accordo con te, eccone uno: http://gribblelab.org/CBootcamp/7_Memory_Stack_vs_Heap.html note "lo stack è una struttura di dati" FILO "(first in, last out), ovvero gestito e ottimizzato dalla CPU abbastanza da vicino. " Vorrei precisare che non sto dicendo che usa il puntatore stack hardware ma è infatti uno stack FILO – Anthony

+1

Potrebbe aiutare a guardare il codice generato eseguire 'gcc -c foo.c' quindi' objdump -d foo.o '(opzionalmente aggiungendo' -M intel' per objdump se sei come me e odi la sintassi AT & T) e puoi vedere che cosa è effettivamente generato. Anche se immagino che non sia di aiuto se non riesci a leggere il linguaggio assembly ... ma in breve è che l'istruzione call spinge un indirizzo di ritorno nello stack, quindi la funzione usa la memoria accanto a quella per i locali, quindi restituire tutto ciò che viene fuori di nuovo. Quindi è piuttosto una pila di matrici piuttosto che una pila pura. –

0

La parte superiore della pila, su un processore Intel, e molti altri, è referenziata da un indirizzo memorizzato in un registro cpu, chiamandolo SP che viene copiato sul puntatore di base, chiamandolo BP; molte istruzioni macchina consentono un'espressione di indirizzo composta dalla corrente BP combinata con un offset di byte. quindi, nel tuo esempio sarei sfalsato 0, j sarebbe offset -2 e k sarebbe offset -6.

l'if si limiterebbe a confrontare i contenuti degli indirizzi -6 (BP) e -4 (BP). i valori di offset effettivi possono differire da implementazione a implementazione; ma, questa è l'idea generale ...

4

Lo stack di chiamate è uno stack, ma non è utilizzato come stai immaginando. Ogni volta che viene effettuata una chiamata da una funzione, l'indirizzo di ritorno (il contatore del programma) viene inserito nello stack, insieme alle variabili locali. Quando ciascuna funzione ritorna, la pila viene spuntata da quello che viene chiamato uno "stack frame", che include le variabili. All'interno di ogni funzione, la memoria viene considerata come accesso casuale. Il compilatore, avendo generato il codice che ordinava le variabili locali nello stack, sa esattamente quale distanza sono dal puntatore del frame stack, e quindi non deve premere e inserire le singole variabili locali.