2013-04-04 20 views
11

Perché i libri dicono "il compilatore alloca lo spazio per le variabili in memoria". Non è l'eseguibile che lo fa? Voglio dire, per esempio, se scrivo il seguente programma,Perché i libri dicono "il compilatore alloca lo spazio per le variabili in memoria"?

#include <iostream> 
using namespace std; 

int main() 
{ 
    int foo = 0; 
    cout<<foo; 
    return 0; 
} 

e compilarlo, e ottenere un file eseguibile (lascia che sia program.exe), ora, se corro program.exe, questo file eseguibile stesso comando di allocare un po 'di spazio per la variabile foo. Non è vero? Per favore, spiega perché i libri continuano a dire "il compilatore lo farà ... fallo", mentre in realtà l'eseguibile compilato lo fa.

Aggiungendo un'altra domanda correlata a questa domanda, perché sizeof ha chiamato un operatore di compilazione? Non è in realtà un operatore di runtime?

+0

è un cattivo esempio. 'foo' non è usato e verrà eliminato durante la compilazione. – Abyx

+0

@Abyx Man, prova a capire la mia domanda. L'attenzione non è se la var è usata. Ad ogni modo, ho modificato il programma per te. –

+0

uomo, in realtà nulla è cambiato con quella modifica. =) – Abyx

risposta

19

quando assumiamo un architetto per progettare una casa , lui o lei definisce la dimensione delle stanze, ecc. e informa gli operai (operai) a riguardo. I lavoratori fanno il lavoro di conseguenza. Ma ancora diremmo "L'architetto ha fatto la casa in questo modo" e non "L'operaio ha fatto la casa in questo modo".

L'operaio sta semplicemente eseguendo i passaggi definiti dall'architetto. Il compilatore fa tutto il lavoro per controllare e definire quanta memoria deve essere allocata, ecc. In fase di esecuzione e poi quelle istruzioni sono appena seguite.

+7

buona analogia - +1 –

2

Si dice che il compilatore alloca spazio per le variabili in memoria, perché altrimenti è necessario allocare (e gratuito!) Memoria te stesso con new/malloc ecc

+2

e non sarà compilatore allora? vuoi scriverlo su un pezzo di carta? ; p – 4pie0

7

E 'solo un uso sciolto della terminologia. Ovviamente il compilatore non assegna memoria per il programma. Una descrizione più accurata è che indica al runtime la quantità di memoria da allocare quando il programma è in esecuzione.

Fino a quando il programma non viene effettivamente eseguito, non è in memoria (a meno che non venga caricato dinamicamente, ma anche ciò accade durante l'esecuzione, quindi fuori dal campo di applicazione del compilatore), quindi non c'è memoria di cui parlare.

Ciò di cui quei libri stanno parlando è l'allocazione di variabili la cui dimensione è nota in fase di compilazione, a differenza dell'allocazione dinamica cin >> x; int * y = new[x];, in cui la dimensione non è nota.

11

Tecnicamente l'atto di creare lo spazio in sé è fatto in fase di esecuzione, ma il compilatore è quello di capire quanto spazio di riservare sulla pila nel vostro caso, per la variabile foo.

Il compilatore conosce le dimensioni del tipo int e pertanto può generare le giuste istruzioni dell'assemblatore che riserveranno spazio sufficiente allo stack per consentire a foo di vivere lì.

Se si guarda l'assemblatore di sotto generato (usando MSVC2012) per il programma che ha mostrato, ho commentato alcuni di essi per mostrare ciò che accade:

#include "stdafx.h" 
#include <iostream> 
using namespace std; 

int main() 
{ 
//Setup stack frame for main by storing the stack pointer from the calling function and 
//reserving space for local variables and storing commonly used registers on the stack 
002E4390 push  ebp 
002E4391 mov   ebp,esp 
// reserve space for local variables, which is 204 bytes here, no idea why so much. 
// this is where the compiler calculated the size of your foo and added that to whatever else needs to be stored on the stack. Subtract from stack pointer (esp) because stack grows downward. 
002E4393 sub   esp,0CCh 
002E4399 push  ebx 
002E439A push  esi 
002E439B push  edi 
002E439C lea   edi,[ebp-0CCh] // load effective address of [ebp-0CCh], which I suspect would be your foo variable into edi register 
002E43A2 mov   ecx,33h 
002E43A7 mov   eax,0CCCCCCCCh 
002E43AC rep stos dword ptr es:[edi] //fill block of memory at es:[edi] with stuff 
    int foo; 
    return 0; 
002E43AE xor   eax,eax //set eax to zero for return value 
} 
// restore everything back to how it was before main was called 
    002E43B0 pop   edi 
    002E43B1 pop   esi 
    002E43B2 pop   ebx 
    002E43B3 mov   esp,ebp 
    002E43B5 pop   ebp 
    002E43B6 ret 
+1

Una domanda interessante è perché il compilatore alloca 204 byte per le variabili locali, quando tutto ciò di cui ha bisogno è 4. (Hai usato VC++ qui, ma ho notato diversi compilatori che sembrano allocare molto più del necessario.) –

+0

@ JamesKanze, me lo sono chiesto. Non sono sicuro del motivo per cui sembra assegnare così tanto. –

+0

L'aggiunta di un altro int lo rende allocare 216 byte ... –

1

Naturalmente il compilatore non "alloca lo spazio per le variabili". Il compilatore genera un codice che alloca lo spazio per le variabili in memoria.

I.e.se avete

int foo; 
foo = 1; 

nel codice sorgente, il compilatore può generare un codice come

int* fooPtr = allocate sizeof(int) 
*fooPtr = 1; 

Nel architettura x86, che di solito allocate cosa sarà un singolo istruzioni di montaggio:

sub esp, 4 ; allocate 4 == sizeof(int) bytes on stack 
       ; now the value of "esp" is equal to the address of "foo", 
       ; i.e. it's "fooPtr" 
mov [esp], 1 ; *fooPtr = 1 

Se hai più di una variabile locale, il compilatore le impacchetterà in una struttura e le assegnerà insieme:

int foo; 
int bar; 
bar = 1; 

sarà compilato come

struct Variables { int foo; int bar; }; 
Variables* v = allocate sizeof(Variables); 
v->bar = 1; 

o

sub esp, 4+4  ; allocate sizeof(Variables) on stack 
mov [esp + 4], 1 ; where 4 is offsetof(Variables, bar) 
-1

suggerisco di leggere la costruzione del compilatore. Concentrati sulla fase di archiviazione, la query verrà risolta.

Dopo che il programma viene compilato, viene convertito in codice oggetto, che è un codice linguaggio assembly. ogni riga di un programma linguistico di alto livello viene tradotta in molti passaggi del linguaggio assembly. Il programma tradotto viene inserito nell'assemblatore che viene eseguito. C'è una fase di assegnazione STORAGE nella costruzione di un commutatore che si traduce in una tabella operativa della macchina (istruzioni MOT a livello di assemblaggio). È qui che viene eseguita l'allocazione dello spazio per variabili/registri. C'è anche una parola chiave modificatore di registro in C++.

+1

Questo non fornisce una risposta alla domanda. Per criticare o richiedere chiarimenti da un autore, lascia un commento sotto il loro post - puoi sempre commentare i tuoi post, e una volta che hai [reputazione] sufficiente (http://stackoverflow.com/help/whats-reputation) essere in grado di [commentare qualsiasi post] (http://stackoverflow.com/help/privileges/comment). –

+0

Dopo che il programma viene compilato, viene convertito in codice oggetto, che è un codice linguaggio assembly. ogni riga di un programma linguistico di alto livello viene tradotta in molti passaggi del linguaggio assembly. Il programma tradotto viene inserito nell'assemblatore che viene eseguito. C'è una fase di assegnazione STORAGE nella costruzione di un commutatore che si traduce in una tabella operativa della macchina (istruzioni MOT a livello di assemblaggio). È qui che viene eseguita l'allocazione dello spazio per variabili/registri. C'è anche una parola chiave modificatore di registro in C++. – DevK

+0

Conosco Nico O .. Chiedo chiarimenti sui commenti degli utenti se non capisci la risposta/prospettiva degli altri. Una volta che l'autore è a conoscenza del processo di costruzione del compilatore, capirà che è il suo codice oggetto che funziona. Il compilatore è un semplice generatore di codice per un livello inferiore di codice di programmazione. – DevK

0

Il compilatore genera le istruzioni della macchina e elabora quale indirizzo di memoria occuperà le variabili locali. Ad ogni variabile locale viene assegnato un indirizzo relativo alla parte superiore della pila, ad esempio foo si presume che si trovi all'indirizzo di memoria stack_pointer. Se si dispone di una variabile foo2, questa verrà inserita all'indirizzo stack_pointer + 4, dove 4 corrisponde a una dimensione di int.

Quando si accede alla variabile locale foo, il compilatore sostituirà l'indirizzo contenuto in stack_pointer. L'hardware ha uno speciale registro stack_pointer che punta sempre in cima allo stack corrente.

Il compilatore sa qual è la dimensione di ciascuna variabile perché è responsabile dell'osservazione delle dichiarazioni struct o class e del calcolo della disposizione in memoria. Quindi sizeof è noto al momento della compilazione e viene trattato come un'espressione costante. I tipi primitivi come int sono di una certa dimensione, ad esempio sizeof(int) è 4.