Cosa significa veramente "Memoria allocata in fase di compilazione"?


159

In linguaggi di programmazione come C e C ++, le persone spesso fanno riferimento all'allocazione di memoria statica e dinamica. Capisco il concetto ma la frase "Tutta la memoria è stata allocata (riservata) durante il tempo di compilazione" mi confonde sempre.

La compilazione, a quanto ho capito, converte il codice C / C ++ di alto livello in linguaggio macchina e genera un file eseguibile. Come viene "allocata" la memoria in un file compilato? La memoria non è sempre allocata nella RAM con tutte le cose di gestione della memoria virtuale?

L'allocazione di memoria per definizione non è un concetto di runtime?

Se creo una variabile allocata staticamente 1 KB nel mio codice C / C ++, aumenterà la dimensione dell'eseguibile dello stesso importo?

Questa è una delle pagine in cui la frase viene utilizzata sotto l'intestazione "Allocazione statica".

Back To Basics: allocazione della memoria, una passeggiata nella storia


il codice e i dati sono totalmente separati nella maggior parte delle architetture moderne. mentre i file di origine contengono entrambi i dati di codice nello stesso posto, il cestino ha solo riferimenti ai dati. Ciò significa che i dati statici nell'origine vengono risolti solo come riferimenti.
Cholthi Paul Ttiopic,

Risposte:


184

Memoria allocata in fase di compilazione significa che il compilatore si risolve in fase di compilazione in cui determinate cose verranno allocate all'interno della mappa della memoria di processo.

Ad esempio, considera un array globale:

int array[100];

Il compilatore conosce in fase di compilazione la dimensione dell'array e la dimensione di un int, quindi conosce l'intera dimensione dell'array in fase di compilazione. Anche una variabile globale ha una durata di archiviazione statica per impostazione predefinita: è allocata nell'area di memoria statica dello spazio di memoria del processo (sezione .data / .bss). Date queste informazioni, il compilatore decide durante la compilazione in quale indirizzo dell'area di memoria statica sarà l'array .

Naturalmente gli indirizzi di memoria sono indirizzi virtuali. Il programma presuppone che disponga di un proprio spazio di memoria (ad esempio, da 0x00000000 a 0xFFFFFFFF). Ecco perché il compilatore potrebbe fare ipotesi come "Okay, l'array sarà all'indirizzo 0x00A33211". In fase di esecuzione, gli indirizzi vengono tradotti in indirizzi reali / hardware da MMU e sistema operativo.

Le cose di archiviazione statica inizializzata dal valore sono leggermente diverse. Per esempio:

int array[] = { 1 , 2 , 3 , 4 };

Nel nostro primo esempio, il compilatore ha deciso solo dove verrà allocato l'array, memorizzando tali informazioni nell'eseguibile.
Nel caso di cose inizializzate dal valore, il compilatore inserisce anche il valore iniziale dell'array nell'eseguibile e aggiunge il codice che dice al caricatore del programma che dopo l'allocazione dell'array all'avvio del programma, l'array deve essere riempito con questi valori.

Ecco due esempi dell'assembly generato dal compilatore (GCC4.8.1 con target x86):

Codice C ++:

int a[4];
int b[] = { 1 , 2 , 3 , 4 };

int main()
{}

Gruppo di uscita:

a:
    .zero   16
b:
    .long   1
    .long   2
    .long   3
    .long   4
main:
    pushq   %rbp
    movq    %rsp, %rbp
    movl    $0, %eax
    popq    %rbp
    ret

Come puoi vedere, i valori vengono iniettati direttamente nell'assieme. Nell'array a, il compilatore genera un'inizializzazione zero di 16 byte, poiché lo standard afferma che le cose memorizzate statiche dovrebbero essere inizializzate a zero per impostazione predefinita:

8.5.9 (Inizializzatori) [Nota]:
ogni oggetto con durata di memorizzazione statica viene inizializzato a zero all'avvio del programma prima che avvenga qualsiasi altra inizializzazione. In alcuni casi, l'inizializzazione aggiuntiva viene eseguita in seguito.

Suggerisco sempre alle persone di smontare il loro codice per vedere cosa fa veramente il compilatore con il codice C ++. Ciò si applica dalle classi di archiviazione / durata (come questa domanda) alle ottimizzazioni avanzate del compilatore. Potresti incaricare il tuo compilatore di generare l'assembly, ma ci sono strumenti meravigliosi per farlo su Internet in modo amichevole. Il mio preferito è GCC Explorer .


2
Grazie. Questo chiarisce molto. Quindi il compilatore genera qualcosa di equivalente a "riserva di memoria da 0xABC a 0xXYZ per array variabile [] ecc." e quindi il caricatore lo usa per allocarlo davvero prima che esegua il programma?
Talha ha detto il

1
@TalhaSayed esattamente. Guarda la modifica per vedere l'esempio
Manu343726,

2
@Secko Ho semplificato le cose. È solo una menzione del programma che funziona attraverso la memoria virtuale, ma poiché la domanda non riguarda la memoria virtuale non ho esteso l'argomento. Stavo solo indicando che il compilatore può fare ipotesi sugli indirizzi di memoria in fase di compilazione, grazie alla memoria virtuale.
Manu343726,

2
@Secko si. mmm "generato" è un termine migliore penso.
Manu343726,

2
"È allocato nell'area di memoria statica dello spazio di memoria di processo" Lettura che ha allocato alcune aree mammarie statiche nello spazio di memoria di processo.
Radiodef,

27

La memoria allocata in fase di compilazione significa semplicemente che non ci sarà ulteriore allocazione in fase di esecuzione - nessuna chiamata a metodi di allocazione dinamica, nuovi o malloc. Avrai una quantità fissa di utilizzo della memoria anche se non hai bisogno di tutta quella memoria per tutto il tempo.

L'allocazione di memoria per definizione non è un concetto di runtime?

La memoria non è in uso prima del runtime, ma immediatamente prima dell'esecuzione, l'avvio della sua allocazione viene gestito dal sistema.

Se creo una variabile allocata staticamente 1 KB nel mio codice C / C ++, aumenterà la dimensione dell'eseguibile dello stesso importo?

La semplice dichiarazione dell'elettricità statica non aumenterà la dimensione dell'eseguibile di più di qualche byte. Dichiarandolo con un valore iniziale diverso da zero sarà (al fine di mantenere quel valore iniziale). Piuttosto, il linker aggiunge semplicemente questa quantità di 1 KB al requisito di memoria che il caricatore del sistema crea per te immediatamente prima dell'esecuzione.


1
se scrivo static int i[4] = {2 , 3 , 5 ,5 }aumenterà di dimensioni eseguibili di 16 byte. Hai detto "Dichiarare semplicemente lo statico non aumenterà la dimensione del tuo eseguibile più di qualche byte. Dichiararlo con un valore iniziale diverso da zero lo farà" Dichiararlo con un valore iniziale significa cosa significa.
Suraj Jain,

L'eseguibile ha due aree per i dati statici: una per la statica non inizializzata e una per la statistica inizializzata. L'area non inizializzata è in realtà solo un'indicazione della dimensione; quando viene eseguito il programma, tale dimensione viene utilizzata per espandere l'area di archiviazione statica ma il programma stesso non ha dovuto contenere altro oltre alla quantità di dati non inizializzati utilizzati. Per le statiche inizializzate, il programma deve contenere non solo le dimensioni di (ciascuna) statica, ma anche ciò a cui viene inizializzato. Quindi, nel tuo esempio, il tuo programma avrà 2, 3, 5 e 5.
Mah,

L'implementazione è definita su dove viene posizionata / come viene allocata, ma non sono sicuro di aver capito la necessità di sapere.
Mah,

23

La memoria allocata in fase di compilazione significa che quando si carica il programma, una parte della memoria verrà immediatamente allocata e la dimensione e la posizione (relativa) di questa allocazione vengono determinate al momento della compilazione.

char a[32];
char b;
char c;

Queste 3 variabili sono "allocate al momento della compilazione", significa che il compilatore calcola la loro dimensione (che è fissa) al momento della compilazione. La variabile asarà un offset in memoria, diciamo, indicando l'indirizzo 0, bpunterà all'indirizzo 33 e c34 (supponendo che non vi sia ottimizzazione dell'allineamento). Pertanto, l' allocazione di 1 KB di dati statici non aumenterà la dimensione del codice , poiché cambierà solo un offset al suo interno. Lo spazio effettivo verrà assegnato al momento del caricamento .

L'allocazione di memoria reale avviene sempre in fase di esecuzione, poiché il kernel deve tenerne traccia e aggiornare le sue strutture di dati interne (quanta memoria viene allocata per ciascun processo, pagine e così via). La differenza è che il compilatore conosce già la dimensione di ogni dato che intendi utilizzare e questo viene allocato non appena il tuo programma viene eseguito.

Ricorda anche che stiamo parlando di indirizzi relativi . L'indirizzo reale in cui verrà posizionata la variabile sarà diverso. Al momento del caricamento il kernel riserverà un po 'di memoria per il processo, diciamo all'indirizzo x, e tutti gli indirizzi codificati contenuti nel file eseguibile saranno incrementati di xbyte, in modo che la variabile anell'esempio sia all'indirizzo x, b all'indirizzo x+33e presto.


17

L'aggiunta di variabili nello stack che occupano N byte non (necessariamente) aumenta la dimensione del cestino di N byte. In realtà aggiungerà solo pochi byte per la maggior parte del tempo.
Cominciamo con un esempio di come l'aggiunta di 1000 caratteri per il vostro codice sarà aumentare le dimensioni del cestino in modo lineare.

Se 1k è una stringa, di mille caratteri, che viene dichiarata così

const char *c_string = "Here goes a thousand chars...999";//implicit \0 at end

e poi vim your_compiled_binlo faresti, potresti effettivamente vedere quella stringa nel cestino da qualche parte. In tal caso, sì: l'eseguibile sarà 1 k più grande, perché contiene la stringa per intero.
Se, tuttavia, allocare una matrice di ints, chars o longs in pila e assegnarla in un ciclo, qualcosa lungo queste linee

int big_arr[1000];
for (int i=0;i<1000;++i) big_arr[i] = some_computation_func(i);

quindi no: non aumenterà il cestino ...1000*sizeof(int)
allocazione in fase di compilazione significa ciò che ora hai capito significa (basato sui tuoi commenti): il cestino compilato contiene informazioni che il sistema richiede per sapere quanta memoria quale funzione / blocco sarà necessario quando viene eseguita, insieme alle informazioni sulla dimensione dello stack richiesta dall'applicazione. Questo è ciò che il sistema assegnerà quando eseguirà il tuo cestino, e il tuo programma diventerà un processo (bene, l'esecuzione del tuo cestino è il processo che ... bene, ottieni quello che sto dicendo).
Naturalmente, non sto dipingendo il quadro completo qui: il cestino contiene informazioni su quanto grande sarà effettivamente il cestino. Sulla base di queste informazioni (tra le altre cose), il sistema riserva un pezzo di memoria, chiamato stack, su cui il programma ottiene una sorta di regno libero. La memoria dello stack viene comunque allocata dal sistema, quando viene avviato il processo (il risultato dell'esecuzione del cestino). Il processo quindi gestisce la memoria dello stack per te. Quando viene invocata / eseguita una funzione o un ciclo (qualsiasi tipo di blocco), le variabili locali a quel blocco vengono inviate allo stack e rimosse (la memoria dello stack viene "liberata" per così dire) per essere utilizzata da altri funzioni / blocchi. Quindi dichiarandoint some_array[100]aggiungerà solo pochi byte di informazioni aggiuntive al cestino, che indica al sistema che la funzione X richiederà 100*sizeof(int)+ un po 'di spazio per la contabilità.


Molte grazie. Ancora una domanda, anche le variabili locali per le funzioni vengono allocate allo stesso modo durante la compilazione?
Talha disse il

@TalhaSayed: Sì, questo è ciò che intendevo quando ho detto: "informazioni che il sistema richiede per sapere quanta memoria sarà richiesta dalla funzione / blocco". Nel momento in cui si chiama una funzione, il sistema alloca la memoria richiesta per quella funzione. Nel momento in cui la funzione ritorna, quella memoria verrà nuovamente liberata.
Elias Van Ootegem,

Per quanto riguarda i commenti nel tuo codice C: non è effettivamente / necessariamente ciò che accade. Ad esempio, molto probabilmente la stringa verrà allocata una sola volta, al momento della compilazione. Quindi non viene mai "liberato" (anche io penso che la terminologia sia di solito usata solo quando si alloca qualcosa in modo dinamico), inon è "liberata" o neanche. Se idovesse risiedere nella memoria, verrebbe semplicemente messo nello stack, qualcosa che non è stato liberato in quel senso della parola, trascurandolo io csarà tenuto nei registri per tutto il tempo. Ovviamente, tutto dipende dal compilatore, il che significa che non è così in bianco e nero.
phant0m

@ phant0m: non ho mai detto che la stringa sia allocata nello stack, solo il puntatore lo sarebbe, la stringa stessa risiederebbe nella memoria di sola lettura. So che la memoria associata alle variabili locali non viene liberata nel senso di free()chiamate, ma la memoria dello stack che hanno usato è libera per l'uso da parte di altre funzioni una volta che la funzione che ho elencato ritorna. Ho rimosso il codice, poiché potrebbe essere
fonte di

Ah capisco In tal caso, prendi il mio commento per dire "Ero confuso dalle tue parole".
phant0m

16

Su molte piattaforme, tutte le allocazioni globali o statiche all'interno di ciascun modulo saranno consolidate dal compilatore in tre o meno allocazioni consolidate (una per dati non inizializzati (spesso chiamati "bss"), una per dati scrivibili inizializzati (spesso chiamati "dati" ) e uno per i dati costanti ("const")) e tutte le allocazioni globali o statiche di ciascun tipo all'interno di un programma verranno consolidate dal linker in un globale per ciascun tipo. Ad esempio, supponendo che intsia composto da quattro byte, un modulo ha le seguenti sole allocazioni statiche:

int a;
const int b[6] = {1,2,3,4,5,6};
char c[200];
const int d = 23;
int e[4] = {1,2,3,4};
int f;

direbbe al linker che aveva bisogno di 208 byte per bss, 16 byte per "dati" e 28 byte per "const". Inoltre, qualsiasi riferimento a una variabile verrebbe sostituito con un selettore di area e un offset, quindi a, b, c, d ed e, verrebbero sostituiti da bss + 0, const + 0, bss + 4, const + 24, data +0 o bss + 204, rispettivamente.

Quando un programma è collegato, tutte le aree bss di tutti i moduli vengono concatenate insieme; allo stesso modo i dati e le aree const. Per ogni modulo, l'indirizzo di tutte le variabili relative a bss verrà aumentato della dimensione di tutte le aree bss di tutti i moduli precedenti (di nuovo, anche con dati e const). Pertanto, al termine del linker, qualsiasi programma avrà un'allocazione bss, un'allocazione dei dati e un'assegnazione const.

Quando viene caricato un programma, generalmente si verifica una delle quattro cose in base alla piattaforma:

  1. L'eseguibile indicherà quanti byte sono necessari per ciascun tipo di dati e - per l'area di dati inizializzata, in cui è possibile trovare il contenuto iniziale. Includerà anche un elenco di tutte le istruzioni che utilizzano un indirizzo bss, data o const. Il sistema operativo o il caricatore assegnerà la quantità appropriata di spazio per ciascuna area e quindi aggiungerà l'indirizzo iniziale di quell'area a ciascuna istruzione che ne ha bisogno.

  2. Il sistema operativo assegnerà un blocco di memoria per contenere tutti e tre i tipi di dati e fornirà all'applicazione un puntatore a quel blocco di memoria. Qualsiasi codice che utilizza dati statici o globali lo farà riferimento rispetto a quel puntatore (in molti casi, il puntatore verrà archiviato in un registro per la durata di un'applicazione).

  3. Il sistema operativo inizialmente non allocherà memoria all'applicazione, ad eccezione di ciò che contiene il suo codice binario, ma la prima cosa che farà l'applicazione sarà richiedere un'allocazione adeguata dal sistema operativo, che manterrà per sempre in un registro.

  4. Il sistema operativo inizialmente non alloca spazio per l'applicazione, ma l'applicazione richiederà un'allocazione adeguata all'avvio (come sopra). L'applicazione includerà un elenco di istruzioni con indirizzi che devono essere aggiornati per riflettere dove è stata allocata la memoria (come con il primo stile), ma anziché avere l'applicazione patchata dal caricatore del sistema operativo, l'applicazione includerà abbastanza codice per patcharsi .

Tutti e quattro gli approcci presentano vantaggi e svantaggi. In ogni caso, tuttavia, il compilatore consoliderà un numero arbitrario di variabili statiche in un piccolo numero fisso di richieste di memoria e il linker consoliderà tutte quelle in un piccolo numero di allocazioni consolidate. Anche se un'applicazione dovrà ricevere un pezzo di memoria dal sistema operativo o dal caricatore, è il compilatore e il linker che sono responsabili dell'assegnazione di singoli pezzi di quel grosso pezzo a tutte le singole variabili che ne hanno bisogno.


13

Il nocciolo della tua domanda è questo: "Come viene" allocata "la memoria in un file compilato? La memoria non è sempre allocata nella RAM con tutte le cose di gestione della memoria virtuale? L'allocazione della memoria per definizione non è un concetto di runtime?"

Penso che il problema sia che ci sono due diversi concetti coinvolti nell'allocazione della memoria. Alla base, l'allocazione della memoria è il processo mediante il quale diciamo "questo elemento di dati è archiviato in questo specifico blocco di memoria". In un moderno sistema informatico, ciò comporta un processo in due fasi:

  • Alcuni sistemi vengono utilizzati per decidere l'indirizzo virtuale in cui l'elemento verrà archiviato
  • L'indirizzo virtuale è associato a un indirizzo fisico

Quest'ultimo processo è puramente runtime, ma il primo può essere eseguito in fase di compilazione, se i dati hanno una dimensione nota e ne è richiesto un numero fisso. Ecco come funziona:

  • Il compilatore vede un file sorgente contenente una riga che assomiglia un po 'a questa:

    int c;
  • Produce un output per l'assemblatore che indica di riservare memoria per la variabile 'c'. Potrebbe apparire così:

    global _c
    section .bss
    _c: resb 4
  • Quando viene eseguito l'assemblatore, mantiene un contatore che tiene traccia degli offset di ciascun elemento dall'inizio di un 'segmento' (o 'sezione') di memoria. Questo è come le parti di una 'struttura' molto grande che contiene tutto in tutto il file in cui non ha memoria effettiva allocata in questo momento e potrebbe essere ovunque. Annota in una tabella che _cha un offset particolare (diciamo 510 byte dall'inizio del segmento) e quindi incrementa il suo contatore di 4, quindi la successiva variabile sarà a (ad esempio) 514 byte. Per qualsiasi codice che richiede l'indirizzo di _c, inserisce semplicemente 510 nel file di output e aggiunge una nota che l'output richiede l'indirizzo del segmento che contiene l' _caggiunta in un secondo momento.

  • Il linker prende tutti i file di output dell'assemblatore e li esamina. Determina un indirizzo per ogni segmento in modo che non si sovrappongano e aggiunge gli offset necessari in modo che le istruzioni facciano ancora riferimento ai dati corretti. Nel caso di memoria non inizializzata come quella occupata dac(all'assemblatore è stato detto che la memoria non sarebbe stata inizializzata dal fatto che il compilatore la metteva nel segmento '.bss', che è un nome riservato alla memoria non inizializzata), include un campo di intestazione nel suo output che dice al sistema operativo quanto deve essere prenotato. Può essere trasferito (e di solito lo è) ma di solito è progettato per essere caricato in modo più efficiente in un determinato indirizzo di memoria e il sistema operativo proverà a caricarlo a questo indirizzo. A questo punto, abbiamo una buona idea di quale indirizzo virtuale verrà utilizzato c.

  • L'indirizzo fisico non sarà effettivamente determinato fino a quando il programma non sarà in esecuzione. Tuttavia, dal punto di vista del programmatore l'indirizzo fisico è in realtà irrilevante: non scopriremo mai nemmeno cosa sia, perché il sistema operativo di solito non si preoccupa di dirlo a nessuno, può cambiare frequentemente (anche mentre il programma è in esecuzione) e un lo scopo principale del sistema operativo è quello di sottrarlo comunque.


9

Un eseguibile descrive lo spazio da allocare per le variabili statiche. Questa allocazione viene eseguita dal sistema quando si esegue l'eseguibile. Quindi la tua variabile statica da 1kB non aumenterà la dimensione dell'eseguibile con 1kB:

static char[1024];

A meno che ovviamente non specifichi un inizializzatore:

static char[1024] = { 1, 2, 3, 4, ... };

Pertanto, oltre al "linguaggio macchina" (ovvero le istruzioni della CPU), un eseguibile contiene una descrizione del layout di memoria richiesto.


5

La memoria può essere allocata in molti modi:

  • nell'heap dell'applicazione (heap intero viene allocato per l'app dal sistema operativo all'avvio del programma)
  • nell'heap del sistema operativo (in modo da poterne prendere sempre di più)
  • nell'heap controllato dal Garbage Collector (uguale a entrambi sopra)
  • in pila (in modo da poter ottenere un overflow dello stack)
  • riservato nel segmento codice / dati del tuo binario (eseguibile)
  • in luogo remoto (file, rete - e ricevi un handle non un puntatore a quella memoria)

Ora la tua domanda è: "memoria allocata al momento della compilazione". Sicuramente è solo un detto erroneamente formulato, che dovrebbe riferirsi all'allocazione del segmento binario o all'allocazione dello stack, o in alcuni casi anche a un'allocazione dell'heap, ma in quel caso l'allocazione è nascosta agli occhi del programmatore da una chiamata del costruttore invisibile. O probabilmente la persona che ha detto che voleva solo dire che la memoria non è allocata sull'heap, ma non sapeva delle allocazioni di stack o segmenti (o non voleva entrare in quel tipo di dettagli).

Ma nella maggior parte dei casi la persona vuole solo dire che la quantità di memoria allocata è nota al momento della compilazione .

Le dimensioni binarie cambieranno solo quando la memoria è riservata nel segmento di codice o dati della tua app.


1
Questa risposta è confusa (o confusa) in quanto parla di "heap dell'applicazione", "heap del sistema operativo" e "heap GC" come se fossero tutti concetti significativi. Ne deduco che dal n. 1 stavi cercando di dire che alcuni linguaggi di programmazione potrebbero (ipoteticamente) utilizzare uno schema di "allocazione dell'heap" che alloca memoria da un buffer di dimensioni fisse nella sezione .data, ma che sembra abbastanza irrealistico da essere dannoso alla comprensione del PO. Con # 2 e # 3, la presenza di un GC non cambia nulla. E per quanto riguarda il numero 5, hai omesso la distinzione relativamente MOLTO più importante tra .datae .bss.
Quuxplusone,

4

Hai ragione. La memoria viene effettivamente allocata (paginata) al momento del caricamento, ovvero quando il file eseguibile viene portato nella memoria (virtuale). La memoria può anche essere inizializzata in quel momento. Il compilatore crea solo una mappa di memoria. [A proposito, anche gli spazi di stack e heap sono allocati al momento del caricamento!]


2

Penso che devi fare un passo indietro. Memoria allocata in fase di compilazione .... Cosa può significare? Può significare che la memoria sui chip che non sono stati ancora prodotti, per i computer che non sono ancora stati progettati, è in qualche modo riservata? No. No, viaggi nel tempo, niente compilatori in grado di manipolare l'universo.

Quindi, deve significare che il compilatore genera istruzioni per allocare quella memoria in qualche modo in fase di esecuzione. Ma se lo guardi dall'angolo retto, il compilatore genera tutte le istruzioni, quindi quale può essere la differenza. La differenza è che il compilatore decide e, in fase di esecuzione, il codice non può cambiare o modificare le sue decisioni. Se ha deciso di aver bisogno di 50 byte in fase di compilazione, in fase di esecuzione, non è possibile decidere di allocare 60 - tale decisione è già stata presa.


Mi piacciono le risposte che usano il metodo Socratic, ma ti ho ancora sottovalutato per l'errata conclusione che "il compilatore genera istruzioni per allocare quella memoria in qualche modo in fase di esecuzione". Controlla la risposta più votata per vedere come un compilatore può "allocare memoria" senza generare "istruzioni" di runtime. (Si noti che "istruzioni" in un contesto in linguaggio assembly ha un significato specifico, ovvero codici opzionali eseguibili. È possibile che si stia usando la parola colloquialmente per indicare qualcosa come "ricetta", ma in questo contesto ciò confonderà semplicemente l'OP. )
Quuxplusone,

1
@Quuxplusone: ho letto (e votato) quella risposta. E no, la mia risposta non affronta in modo specifico il problema delle variabili inizializzate. Inoltre non tratta il codice auto-modificante. Sebbene questa risposta sia eccellente, non ha affrontato quello che considero un problema importante: mettere le cose nel contesto. Da qui la mia risposta, che spero possa aiutare l'OP (e altri) a fermarsi e pensare a cosa sta succedendo o quando può succedere, quando hanno problemi che non comprendono.
jmoreno,

@Quuxplusone: scusami se sto facendo false accuse qui, ma presumo che tu fossi una delle persone che hanno risposto anche alla mia risposta. In tal caso, ti dispiacerebbe terribilmente sottolineare quale parte della mia risposta era la ragione principale per farlo, e ti interesserebbe anche controllare la mia modifica? So di aver saltato alcuni bit sui veri interni di come viene gestita la memoria dello stack, quindi ora ho aggiunto un po 'di non essere al 100% accurato alla mia risposta ora :)
Elias Van Ootegem

@jmoreno Il punto che hai fatto riguardo a "Può significare che la memoria su chip che non sono stati ancora prodotti, per computer che non sono ancora stati progettati, è in qualche modo riservata? No." è esattamente il significato falso che la parola "allocazione" implica che mi ha confuso fin dall'inizio. Mi piace questa risposta perché si riferisce esattamente al problema che stavo cercando di sottolineare. Nessuna delle risposte qui ha davvero toccato quel punto particolare. Grazie.
Talha Sayed,

2

Se apprendi la programmazione degli assiemi, vedrai che devi ritagliare segmenti per i dati, lo stack e il codice, ecc. Il segmento di dati è dove vivono le tue stringhe e numeri. Il segmento di codice è dove vive il tuo codice. Questi segmenti sono integrati nel programma eseguibile. Ovviamente anche le dimensioni dello stack sono importanti ... non vorrai un overflow dello stack !

Pertanto, se il segmento di dati è di 500 byte, il programma ha un'area di 500 byte. Se si modifica il segmento di dati in 1500 byte, la dimensione del programma sarà maggiore di 1000 byte. I dati vengono assemblati nel programma attuale.

Questo è ciò che accade quando compili linguaggi di livello superiore. L'area dati effettiva viene allocata quando viene compilata in un programma eseguibile, aumentando le dimensioni del programma. Il programma può anche richiedere memoria al volo, e questa è memoria dinamica. Puoi richiedere memoria dalla RAM e la CPU ti darà da usare, puoi lasciarla andare e il tuo garbage collector lo rilascerà nella CPU. Può anche essere scambiato su un disco rigido, se necessario, da un buon gestore di memoria. Queste funzionalità sono ciò che ti offrono le lingue di alto livello.


2

Vorrei spiegare questi concetti con l'aiuto di alcuni diagrammi.

Questo è vero che la memoria non può essere allocata in fase di compilazione, di sicuro. Ma poi cosa succede in effetti al momento della compilazione.

Ecco la spiegazione. Ad esempio, un programma ha quattro variabili x, y, z e k. Ora, in fase di compilazione, crea semplicemente una mappa di memoria, in cui viene accertata la posizione di queste variabili l'una rispetto all'altra. Questo diagramma lo illustrerà meglio.

Ora immagina, nessun programma è in esecuzione in memoria. Questo lo mostro con un grande rettangolo vuoto.

campo vuoto

Successivamente, viene eseguita la prima istanza di questo programma. Puoi visualizzarlo come segue. Questo è il momento in cui viene allocata effettivamente la memoria.

prima istanza

Quando è in esecuzione la seconda istanza di questo programma, la memoria appare come segue.

seconda istanza

E il terzo ..

terza istanza

Così via.

Spero che questa visualizzazione spieghi bene questo concetto.


2
Se questi diagrammi mostrassero la differenza tra memoria statica e dinamica sarebbero più utili per IMHO.
Bartek Banachewicz,

Ciò era stato deliberatamente evitato da me per mantenere le cose semplici. Il mio obiettivo è spiegare questo fondo con chiarezza senza ingombri tecnici. Per quanto riguarda questo significato per la variabile statica ... Questo punto è stato stabilito bene dalle risposte precedenti, quindi l'ho saltato.
user3258051

1
Eh, questo concetto non è particolarmente complicato, quindi non vedo perché renderlo più semplice di quanto debba essere, ma poiché è inteso solo come risposta gratuita, ok.
Bartek Banachewicz,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.