2010-03-01 13 views
9

Supponiamo di avere il seguente:In che momento esatto è allocata una memoria locale?

void print() 
{ 
    int a; // declaration 
    a = 9; 
    cout << a << endl; 
} 

int main() 
{ 
    print(); 
} 

è il deposito di una variabile distribuito alla stampa funzione momento è denominata nel principale o è quando l'esecuzione raggiunge la dichiarazione all'interno della funzione?

+0

Suppongo che tu stia parlando di archiviazione in pila, non in un registro, giusto? – Ponkadoodle

+0

Sì in pila. – Brandon

+5

riiiiight ....... now – Russell

risposta

9

Questo dipende molto dal compilatore sotto le copertine, ma logicamente la memoria viene assegnata non appena viene dichiarata la variabile.

Considerate questo semplicistico C++ esempio:

// junk.c++ 
int addtwo(int a) 
{ 
    int x = 2; 

    return a + x; 
} 

Quando GCC compila questo, il seguente codice viene generato (; commenti miniera):

.file "junk.c++" 
    .text 
.globl _Z6addtwoi 
    .type _Z6addtwoi, @function 
_Z6addtwoi: 
.LFB2: 
    pushl %ebp   ;store the old stack frame (caller's parameters and locals) 
.LCFI0: 
    movl %esp, %ebp  ;set up the base pointer for our parameters and locals 
.LCFI1: 
    subl $16, %esp  ;leave room for local variables on the stack 
.LCFI2: 
    movl $2, -4(%ebp) ;store the 2 in "x" (-4 offset from the base pointer) 
    movl -4(%ebp), %edx ;put "x" into the DX register 
    movl 8(%ebp), %eax ;put "a" (+8 offset from base pointer) into AX register 
    addl %edx, %eax  ;add the two together, storing the results in AX 
    leave     ;tear down the stack frame, no more locals or parameters 
    ret     ;exit the function, result is returned in AX by convention 
.LFE2: 
    .size _Z6addtwoi, .-_Z6addtwoi 
    .ident "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3" 
    .section .note.GNU-stack,"",@progbits 

Tutto tra il _Z6addtwoi e .LCFI2 è boilerplate codice utilizzato per impostare il frame dello stack (memorizzare le variabili della funzione precedente, ecc. in modo sicuro fuori mano). Quest'ultimo "subl $ 16,% esp" è l'allocazione della variabile locale x.

.LCFI2 è il primo bit del codice di esecuzione effettivo che è stato digitato. "movl $ 2, -4 (% ebp)" sta mettendo il valore 2 nella variabile. (Inizializzazione, in altre parole.) Ora il tuo spazio è allocato e inizializzato. Dopo di ciò carica il valore nel registro EDX e lo segue spostando il parametro, trovato in "8 (% ebp)", in un altro registro EAX. Quindi aggiunge i due insieme, lasciando il risultato in EAX.Questa è ora la fine di qualsiasi codice che hai effettivamente digitato. Il resto è di nuovo solo la caldaia. Poiché GCC richiede che gli interi siano restituiti in EAX, non è necessario eseguire alcun lavoro per il valore restituito. L'istruzione "leave" abbassa il frame dello stack e l'istruzione "ret" restituisce il controllo al chiamante.

TL; Riepilogo DR: puoi pensare che il tuo spazio sia stato allocato con la prima riga del codice eseguibile nel tuo blocco (accoppiato {}).


Ho pensato di pulirlo un po 'con commenti esplicativi visto che questa è la risposta selezionata.

+0

In realtà, dovrebbe essere "non appena la variabile è _defined_". Le dichiarazioni non riservano alcun archivio: http://stackoverflow.com/questions/1410563/ – sbi

+0

Puoi persino dichiarare una variabile locale senza definirla? E per i globals, lo storage è allocato all'avvio del programma. – MSalters

+0

Globali inizializzati vengono assegnati * prima dell'avvio * del programma con GCC. Mi è capitato di averlo testato di recente compilando un file con il seguente ambito globale: static char c [100000000] = ""; Il file di output di GCC diventa molto grande quando si fanno cose del genere. I compilatori più intelligenti come CL eseguiranno allocazione e inizializzazione all'avvio. –

3

quanto riguarda la costruzione di oggetti:

costruzione accadere nel punto di dichiarazione, e il distruttore viene chiamato quando l'oggetto passa nell'ambito.

Ma la costruzione di oggetti e l'allocazione della memoria non devono necessariamente coincidere.

Proprio come la distruzione di oggetti e quando la memoria è dislocata non è necessario che coincidano.

Per quanto riguarda quando la memoria sullo stack è effettivamente allocato:

non lo so, ma è possibile controllare tramite il seguente codice:

void f() 
{ 
    int y; 
    y = 0;//breakpoint here 

    int x[1000000]; 
} 

Con l'esecuzione di questo codice è possibile vedere dove accade l'eccezione, per me su Visual Studio 2008 succede all'entrata della funzione. Non raggiunge mai il punto di interruzione.

+0

Il codice non mostra quando alloca lo spazio di stack, tuttavia, solo quando esegue il costruttore. Non c'è ragione per cui debbano essere uguali. Inoltre, non c'è motivo per cui sia necessario deallocare lo spazio per c alla fine di tale ambito. Ha solo bisogno di eseguire il distruttore. –

+0

@Brooks Moses: Penso che tu abbia fatto questo commento proprio mentre lo modificavo per distinguere quella cosa esatta. –

+0

Sì, sembra così, quindi ti ho svalutato. :) Ho scritto una risposta supplementare per mostrare un modo diverso di dimostrarlo nel codice. (Naturalmente, il modo migliore è solo guardare l'assembly compilato, e vedere dove incrementa il puntatore dello stack - che ora vedo qualcun altro ha fatto proprio come stavo facendo * questo * commento, hah!) –

2

Questo dipenderà dal compilatore, ma in genere la variabile int a verrà allocata nello stack al momento della chiamata della funzione.

+0

-1 , tutto il compilatore ha la stessa semantica di allocazione di memoria quando si tratta di variabili locali. saranno assegnati su dichiarazione, e rilasciati quando va fuori portata come quello che ha detto Brian. in caso di più variabili locali, vengono deallocate utilizzando il paradigma LIFO, in cui l'ultima memoria allocata viene liberata per prima. – YeenFei

+3

Wow, questa è una dichiarazione abbastanza folle per fare YeenFei."tutto il compilatore ha la stessa allocazione di memoria semantica quando si tratta di variabili locali". È probabile che sia vero per * alcuni * compilatori di livello relativamente basso, ma non tutti. Molti compilatori allocheranno la memoria per una variabile la prima volta che viene letta o scritta. Si consideri 'int a; int b = 2; a = 3; ', il compilatore potrebbe allocare memoria per' a' dopo 'b'. – Ponkadoodle

+2

Inoltre, si noti che questa è una forma molto semplice di allocazione; in generale tutte le variabili locali all'interno di un oscilloscopio sono date offset di compilazione da un puntatore salvato allo stack, e quindi quando viene immesso l'ambito, il programma "assegna" quelle variabili salvando il puntatore dello stack corrente da qualche parte e poi facendo dimensione totale delle variabili locali. È molto più efficiente farlo tutto in una volta piuttosto che farlo in modo incrementale, e significa che puoi trovare tutte le variabili locali con lo stesso puntatore variabile e alcune costanti, piuttosto che dover memorizzare diversi puntatori diversi per ciascuna. –

2

Almeno come le cose sono in genere implementate, è tra i due. Quando si chiama una funzione, il compilatore genererà il codice per la chiamata di funzione che valuta i parametri (se presenti) e li inserisce in registri o in pila. Quindi, quando l'esecuzione raggiunge la voce relativa alla funzione, lo spazio per le variabili locali verrà allocato nello stack e i locali che necessitano di inizializzazione verranno inizializzati. A quel punto, potrebbe esserci del codice per salvare i registri che sono usati dalla funzione, mischiare i valori in giro per inserirli nei registri desiderati e così via. Successivamente, il codice per il corpo della funzione inizia a essere eseguito.

3

Come supplemento alla risposta di Brian R. Bondy: È abbastanza facile eseguire alcuni esperimenti per mostrare come funziona, in un modo un po 'più dettagliato rispetto a buttare fuori dallo spazio errori dello stack. Considerate questo codice:

#include<iostream> 

void foo() 
{ 
    int e; std::cout << "foo:e " << &e << std::endl; 
} 

int main() 
{ 
    int a; std::cout << "a: " << &a << std::endl; 
    foo(); 
    int b; std::cout << "b: " << &b << std::endl; 
    { 
    int c; std::cout << "c: " << &c << std::endl; 
    foo(); 
    } 
    int d; std::cout << "d: " << &d << std::endl; 
} 

Questo produce questo output sulla mia macchina:

$ ./stack.exe 
a: 0x28cd30 
foo:e 0x28cd04 
b: 0x28cd2c 
c: 0x28cd24 
foo:e 0x28cd04 
d: 0x28cd28 

Poiché lo stack cresce verso il basso, si può vedere l'ordine in cui le cose vengono messe in pila: a, b, d, e c in questo ordine, e quindi le due chiamate a foo() mettono la sua e nello stesso posto entrambe le volte. Ciò significa che la stessa quantità di memoria è stata allocata nello stack entrambe le volte che viene chiamato foo(), anche se intervengono diverse dichiarazioni variabili (inclusa una all'interno di un ambito interno). Quindi, in questo caso, possiamo concludere che tutta la memoria dello stack per le variabili locali in main() è stata allocata all'inizio di main() piuttosto che incrementata in modo incrementale.

Puoi inoltre vedere che il compilatore dispone le cose in modo che i costruttori sono chiamati in ordine decrescente stack e distruttori sono chiamati in ordine crescente - tutto ciò è la parte inferiore costruito cosa sullo stack quando è costruito e quando è distrutta , ma questo è non significa che è la cosa in basso per cui è stato allocato lo spazio, o che non c'è spazio attualmente non utilizzato sopra lo stack per cose che non sono ancora state costruite (come lo spazio per d quando c o le due incarnazioni di foo: e sono costruite).

+0

@Brooks Moses: Mi piace il tuo setup sperimentale. L'output su uno dei miei setup (Cygwin su i386, usando g ++ 3.4.4). è leggermente diverso Ho ottenuto lo stesso indirizzo sia per 'c' che per 'd'. – Arun

+0

Grazie. Questo è un risultato interessante - è una differenza GCC da 3,4 a 4.x, direi; Stavo anche usando Cygwin su i386, ma stavo usando il compilatore g ++ 4.3.2. (Un aggiornamento molto consigliato, btw, ma devi selezionare esplicitamente gcc4 nel programma di installazione Cygwin per scaricarlo.) –

Problemi correlati