Dove sono in memoria le mie variabili archiviate in C?


156

Considerando che la memoria è divisa in quattro segmenti: dati, heap, stack e codice, dove fanno variabili globali, variabili statiche, tipi di dati costanti, variabili locali (definite e dichiarate nelle funzioni), variabili (nella funzione principale), puntatori e lo spazio allocato dinamicamente (usando malloc e calloc) viene archiviato in memoria?

Penso che sarebbero assegnati come segue:

  • Variabili globali -------> dati
  • Variabili statiche -------> dati
  • Tipi di dati costanti -----> codice
  • Variabili locali (dichiarate e definite nelle funzioni) --------> stack
  • Variabili dichiarate e definite nella funzione principale -----> heap
  • Puntatori (ad esempio, char *arr, int *arr) -------> mucchio
  • Spazio allocato dinamicamente (usando malloc e calloc) --------> stack

Mi riferisco a queste variabili solo dalla prospettiva C.

Per favore, correggimi se sbaglio perché sono nuovo di C.


4
I tipi non sono archiviati in memoria.

5
mainè solo un'altra funzione. Le variabili vanno in pila a meno che non siano malloccome altrove.
simonc

4
i puntatori sono (solitamente) memorizzati nello stack. La memoria che indicano (solitamente allocata tramite malloc / calloc) è (solitamente) nell'heap.
jpm

3
spazio allocato dinamicamente (usando malloc, calloc) --------> heap
One Man Crew

3
variabili dichiarate e definite nella funzione principale -----> stack
One Man Crew

Risposte:


217

Hai capito bene, ma chiunque abbia scritto le domande ti ha ingannato almeno in una domanda:

  • variabili globali -------> dati (corretto)
  • variabili statiche -------> dati (corretto)
  • tipi di dati costanti -----> codice e / o dati. Considera i valori letterali di stringa per una situazione in cui una costante stessa verrebbe archiviata nel segmento di dati e i riferimenti ad essa verrebbero incorporati nel codice
  • variabili locali (dichiarate e definite in funzioni) --------> stack (corretto)
  • le variabili dichiarate e definite in mainfunzione -----> si accumulano anche heap (l'insegnante stava cercando di ingannarti)
  • puntatori (ex: char *arr, int *arr) -------> mucchio di dati o pila, a seconda del contesto. C consente di dichiarare un staticpuntatore globale o , nel qual caso il puntatore stesso finirebbe nel segmento di dati.
  • allocato dinamicamente spazio (usando malloc, calloc, realloc) --------> pila mucchio

Vale la pena ricordare che "stack" è ufficialmente chiamato "classe di archiviazione automatica".


6
Vale anche la pena ricordare che l'heap ufficialmente non si chiama affatto. La memoria allocata proviene da qualche parte, non esiste un nome nello standard per quel "da qualche parte".
Steve Jessop,

6
Su alcuni sistemi, (vale a dire Linux e * BSD), esiste anche un sistema allocache funziona in modo simile mallocma alloca l'allocazione.
Andreas Grapentin,

Dove vanno le variabili const dichiarate all'interno di un metodo?
Mahori,

@Ravi Lo stesso posto dove vanno le altre costanti (punto 3 sopra).
dasblinkenlight,

Sto usando GCC 4.8.1 e non sembra memorizzare la variabile const locale in main nel segmento DATA. Di seguito è riportato il codice e la mappa di memoria per 3 di questi programmi: Codice 1: int main (void) {// char a [10] = "HELLO"; // 1 // const char a [10] = "CIAO"; // 2 restituisce 0; } MAPPA DI MEMORIA SOPRA: dati di testo bss dec hex nome file 7264 1688 1040 9992 2708 a.exe MEMORIA MAPPA 2: dati di testo bss dec hex nome file 7280 1688 1040 10008 2718 a.exe MAPPA DI MEMORIA PER 3: dati di testo bss dec hex nome file 7280 1688 1040 10008 2718 a.exe
Mahori,

124

Per quei futuri visitatori che potrebbero essere interessati a conoscere quei segmenti di memoria, sto scrivendo punti importanti su 5 segmenti di memoria in C:

Alcuni avvertimenti:

  1. Ogni volta che viene eseguito un programma C, una memoria viene allocata nella RAM per l'esecuzione del programma. Questa memoria viene utilizzata per la memorizzazione del codice eseguito di frequente (dati binari), delle variabili del programma, ecc. I segmenti di memoria sottostanti parlano della stessa cosa:
  2. In genere ci sono tre tipi di variabili:
    • Variabili locali (chiamate anche come variabili automatiche in C)
    • Variabili globali
    • Variabili statiche
    • Puoi avere variabili statiche globali o statiche locali, ma i tre precedenti sono i tipi principali.

5 segmenti di memoria in C:

1. Segmento di codice

  • Il segmento di codice, noto anche come segmento di testo, è l'area della memoria che contiene il codice eseguito di frequente.
  • Il segmento di codice è spesso di sola lettura per evitare il rischio di essere sovrascritto dalla programmazione di bug come buffer-overflow, ecc.
  • Il segmento di codice non contiene variabili di programma come variabili locali ( chiamate anche variabili automatiche in C ), variabili globali, ecc.
  • Basato sull'implementazione C, il segmento di codice può contenere anche valori letterali di stringa di sola lettura. Ad esempio, quando lo fai, la printf("Hello, world")stringa "Hello, world" viene creata nel segmento codice / testo. Puoi verificarlo usando il sizecomando nel sistema operativo Linux.
  • Ulteriori letture

Segmento dati

Il segmento di dati è diviso nelle due parti seguenti e in genere si trova sotto l'area dell'heap o in alcune implementazioni sopra lo stack, ma il segmento di dati non si trova mai tra l'area dell'heap e dello stack.

2. Segmento di dati non inizializzato

  • Questo segmento è anche noto come bss .
  • Questa è la porzione di memoria che contiene:
    1. Variabili globali non inizializzate (comprese le variabili puntatore)
    2. Variabili globali costanti non inizializzate .
    3. Variabili statiche locali non inizializzate .
  • Qualsiasi variabile locale statica o globale che non è inizializzata verrà archiviata nel segmento di dati non inizializzato
  • Ad esempio: la variabile globale int globalVar;o la variabile locale statica static int localStatic;verrà archiviata nel segmento di dati non inizializzato.
  • Se dichiari una variabile globale e la inizializzi come 0o NULLallora andrebbe al segmento di dati non inizializzato o bss.
  • Ulteriori letture

3. Segmento di dati inizializzato

  • Questo segmento memorizza:
    1. Variabili globali inizializzate (comprese le variabili puntatore)
    2. Variabili globali costanti inizializzate .
    3. Variabili statiche locali inizializzate .
  • Ad esempio: la variabile globale int globalVar = 1;o la variabile locale statica static int localStatic = 1;verrà memorizzata nel segmento di dati inizializzato.
  • Questo segmento può essere ulteriormente classificato in area di sola lettura inizializzata e area di lettura-scrittura inizializzata . Le variabili globali costanti inizializzate andranno nell'area di sola lettura inizializzata mentre le variabili i cui valori possono essere modificati in fase di esecuzione andranno nell'area di lettura / scrittura inizializzata .
  • La dimensione di questo segmento è determinata dalla dimensione dei valori nel codice sorgente del programma e non cambia in fase di esecuzione .
  • Ulteriori letture

4. Segmento dello stack

  • Il segmento di stack viene utilizzato per memorizzare variabili create all'interno di funzioni (la funzione potrebbe essere la funzione principale o la funzione definita dall'utente ), variabile come
    1. Variabili locali della funzione (comprese le variabili puntatore)
    2. Gli argomenti passati alla funzione
    3. Indirizzo di ritorno
  • Le variabili archiviate nello stack verranno rimosse non appena termina l'esecuzione della funzione.
  • Ulteriori letture

5. Segmento di mucchio

  • Questo segmento supporta l'allocazione dinamica della memoria. Se il programmatore vuole allocare dinamicamente la memoria un po 'poi in C è fatto usando i malloc, calloco reallocmetodi.
  • Ad esempio, quando int* prt = malloc(sizeof(int) * 2)verranno allocati otto byte nell'heap e l'indirizzo di memoria di quella posizione verrà restituito e memorizzato in una ptrvariabile. La ptrvariabile sarà sullo stack o sul segmento di dati a seconda del modo in cui viene dichiarata / utilizzata.
  • Ulteriori letture

Non dovrebbe essere inizializzato anziché non inizializzato in 3. Segmento di dati inizializzato.
Suraj Jain,

Ri "archiviato nel segmento di dati non inizializzato" (istanze multiple): vuoi dire "archiviato nel segmento di dati non inizializzato" ?
Peter Mortensen,

@PeterMortensen Intendo entrambe le cose. "Qualsiasi variabile locale globale o statica che non è inizializzata verrà archiviata nel segmento di dati non inizializzato"
hagrawal,

come possiamo avere una variabile statica globale in C?

sotto "alcuni avvertimenti", ho trovato questo punto "Puoi avere variabili statiche globali o statiche locali, ma i tre precedenti sono i tipi principali." in cui ti riferivi al termine "statico globale". Il mio punto è che una variabile statica non può essere globale. ovvero, se una variabile deve essere globale, dovrebbe essere accessibile fino al completamento dell'esecuzione del programma. Per favore, spiega e aiuta se sbaglio.

11

Hai corretto le tue frasi sbagliate

constant data types ----->  code //wrong

variabili costanti locali -----> stack

variabile costante globale inizializzata -----> segmento di dati

variabile costante globale non inizializzata -----> bss

variables declared and defined in main function  ----->  heap //wrong

variabili dichiarate e definite nella funzione principale -----> stack

pointers(ex:char *arr,int *arr) ------->  heap //wrong

dynamically allocated space(using malloc,calloc) --------> stack //wrong

puntatori (es: char * arr, int * arr) -------> la dimensione di quella variabile del puntatore sarà nello stack.

Si consideri che si sta allocando la memoria di n byte (usando malloco calloc) in modo dinamico e quindi facendo variabile puntatore per puntarlo. Ora che i nbyte di memoria sono in heap e la variabile del puntatore richiede 4 byte (se la macchina a 64 bit 8 byte) che saranno in pila per memorizzare il puntatore iniziale dei nbyte del blocco di memoria.

Nota: le variabili del puntatore possono puntare la memoria di qualsiasi segmento.

int x = 10;
void func()
{
int a = 0;
int *p = &a: //Now its pointing the memory of stack
int *p2 = &x; //Now its pointing the memory of data segment
chat *name = "ashok" //Now its pointing the constant string literal 
                     //which is actually present in text segment.
char *name2 = malloc(10); //Now its pointing memory in heap
...
}

spazio allocato dinamicamente (usando malloc, calloc) --------> heap


i puntatori possono essere nello stack o nell'heap (vedi in particolare: puntatori a puntatori)
argentage

@airza: ora aggiornato. In realtà stavo aggiornando solo quei dettagli :)
rashok

Nella seguente mappa di memoria, potresti indicare dov'è stack e heap? Non sono sicuro che questa sia una domanda corretta in quanto lo stack e la memoria potrebbero essere applicabili solo in fase di esecuzione. MAPPA DI MEMORIA: "data text bss dec hex nomefile 7280 1688 1040 10008 2718 a.exe"
Mahori,

7

Una popolare architettura desktop divide la memoria virtuale di un processo in diversi segmenti :

  • Segmento di testo: contiene il codice eseguibile. Il puntatore dell'istruzione assume valori in questo intervallo.

  • Segmento dati: contiene variabili globali (cioè oggetti con collegamento statico). Suddiviso in dati di sola lettura (come costanti di stringa) e dati non inizializzati ("BSS").

  • Segmento di stack: contiene la memoria dinamica per il programma, ovvero l'archivio libero ("heap") e i frame di stack locali per tutti i thread. Tradizionalmente lo stack C e l'heap C crescevano nel segmento dello stack da estremità opposte, ma credo che la pratica sia stata abbandonata perché troppo pericolosa.

Il programma CA in genere inserisce oggetti con durata dell'archiviazione statica nel segmento di dati, oggetti allocati dinamicamente nell'archivio libero e oggetti automatici nello stack di chiamate del thread in cui risiede.

Su altre piattaforme, come la vecchia modalità reale x86 o su dispositivi integrati, le cose possono ovviamente essere radicalmente diverse.


"Credo che la pratica sia stata abbandonata perché troppo sicura" - e rende impossibile implementare i thread, da allora è necessario più di uno stack per programma e non possono essere tutti alla fine :-)
Steve Jessop

@SteveJessop: Sì, ci stavo pensando anch'io. Ma i fili esistono da molto tempo - non so se anche tutti gli stack di fili crescessero all'indietro, o se sarebbero cresciuti come il mucchio ... comunque, oggigiorno tutto va nella stessa direzione e ci sono guardie pagine.
Kerrek SB,

6

Mi riferisco a queste variabili solo dalla prospettiva C.

Dal punto di vista del linguaggio C , tutto ciò che conta è l'estensione, la portata, il collegamento e l'accesso; esattamente come gli elementi sono mappati su diversi segmenti di memoria dipende dalla singola implementazione e questo varierà. Lo standard di lingua non parla di segmenti di memoria a tutti . La maggior parte delle architetture moderne agisce principalmente allo stesso modo; variabili di blocco-blocco e argomenti di funzione verranno allocati dallo stack, portata di file e variabili statiche verranno allocate da un segmento di dati o di codice, la memoria dinamica verrà allocata da un mucchio, alcuni dati costanti verranno archiviati in segmenti di sola lettura , eccetera.


3

Una cosa da tenere a mente per l'archiviazione è la regola as-if . Il compilatore non è tenuto a posizionare una variabile in un posto specifico, ma può posizionarla dove preferisce per tutto il tempo in cui il programma compilato si comporta come se fosse eseguito nella macchina C astratta secondo le regole della macchina C astratta. Questo vale per tutte le durate di archiviazione . Per esempio:

  • una variabile a cui non si accede tutto può essere eliminata completamente - non ha memoria ... ovunque. Esempio : vedere come c'è 42nel codice assembly generato ma nessun segno di 404.
  • una variabile con durata di memorizzazione automatica per la quale non è stato preso l'indirizzo non deve essere salvata in memoria. Un esempio potrebbe essere una variabile di ciclo.
  • una variabile che è consto constnon deve necessariamente essere in memoria. Esempio : il compilatore può dimostrare che fooè efficace conste ne incorpora l'utilizzo nel codice. barha un collegamento esterno e il compilatore non può provare che non verrebbe modificato al di fuori del modulo corrente, quindi non è integrato.
  • un oggetto allocato con mallocnecessità non risiede nella memoria allocata dall'heap! Esempio : notate come il codice non ha una chiamata malloce il valore 42 non è mai memorizzato, è conservato in un registro!
  • quindi un oggetto che è stato allocato da malloce il riferimento viene perso senza deallocare l'oggetto con free necessità di non perdere memoria ...
  • l'oggetto assegnato da mallocnon deve trovarsi all'interno dell'heap sotto l'interruzione di programma ( sbrk(0)) su Unixen ...

1

puntatori (es: char * arr, int * arr) -------> heap

No, possono essere nello stack o nel segmento di dati. Possono puntare ovunque.


Anche le dichiarazioni sulle mainvariabili allocate dinamicamente sono sbagliate
simonc

Non solo nello stack o nel segmento di dati. Pensa a un puntatore che punta a una matrice di puntatori. In questo caso i puntatori nell'array vengono archiviati nell'heap.
Sebi2020,

1
  • Variabili / variabili automatiche ---> sezione stack
  • Variabili allocate dinamicamente ---> sezione heap
  • Variabili globali inizializzate -> sezione dati
  • Variabili globali non inizializzate -> sezione dati (bss)
  • Variabili statiche -> sezione dati
  • Costanti stringa -> sezione testo / sezione codice
  • Funzioni -> sezione testo / sezione codice
  • Codice testo -> sezione testo / sezione codice
  • Registri -> registri CPU
  • Input da riga di comando -> sezione ambientale / riga di comando
  • Variabili ambientali -> sezione ambientale / riga di comando

Cos'è la sezione ambientale / riga di comando? Esistono in Linux?
Haoyuan Ge,

-1

Esempi minimizzabili di Linux con analisi di disassemblaggio

Dato che si tratta di un dettaglio dell'implementazione non specificato dagli standard, diamo un'occhiata a cosa sta facendo il compilatore in una particolare implementazione.

In questa risposta, collegherò a risposte specifiche che eseguono l'analisi, o fornirò l'analisi direttamente qui, e riassumerò tutti i risultati qui.

Tutti questi sono in varie versioni di Ubuntu / GCC e i risultati sono probabilmente abbastanza stabili tra le versioni, ma se troviamo delle variazioni specificiamo versioni più precise.

Variabile locale all'interno di una funzione

Sia esso maino qualsiasi altra funzione:

void f(void) {
    int my_local_var;
}

Come mostrato in: Cosa significa <valore ottimizzato> in gdb?

  • -O0: stack
  • -O3: registra se non si rovesciano, impila diversamente

Per motivi sul perché esiste lo stack, vedere: Qual è la funzione delle istruzioni push / pop utilizzate sui registri nell'assembly x86?

Variabili globali e staticvariabili di funzione

/* BSS */
int my_global_implicit;
int my_global_implicit_explicit_0 = 0;

/* DATA */
int my_global_implicit_explicit_1 = 1;

void f(void) {
    /* BSS */
    static int my_static_local_var_implicit;
    static int my_static_local_var_explicit_0 = 0;

    /* DATA */
    static int my_static_local_var_explicit_1 = 1;
}

char * e char c[]

Come mostrato in: Dove sono archiviate le variabili statiche in C e C ++?

void f(void) {
    /* RODATA / TEXT */
    char *a = "abc";

    /* Stack. */
    char b[] = "abc";
    char c[] = {'a', 'b', 'c', '\0'};
}

TODO verranno messi in pila anche letterali di stringhe molto grandi? Oppure .data? O la compilazione fallisce?

Argomenti di funzione

void f(int i, int j);

È necessario seguire la convenzione di chiamata pertinente, ad esempio: https://en.wikipedia.org/wiki/X86_calling_conventions per X86, che specifica registri specifici o posizioni di stack per ogni variabile.

Quindi, come mostrato in Cosa significa <valore ottimizzato> in gdb? , -O0quindi inserisce tutto nello stack, mentre -O3tenta di utilizzare i registri il più possibile.

Se la funzione viene tuttavia incorporata, vengono trattate come normali locali.

const

Credo che non faccia alcuna differenza perché puoi scriverlo via.

Al contrario, se il compilatore è in grado di determinare che alcuni dati non vengono mai scritti, in teoria potrebbe posizionarli .rodataanche se non cost.

Analisi TODO.

puntatori

Sono variabili (che contengono indirizzi, che sono numeri), così come tutto il resto :-)

malloc

La domanda non ha molto senso per malloc, poiché mallocè una funzione e in:

int *i = malloc(sizeof(int));

*i è una variabile che contiene un indirizzo, quindi rientra nel caso precedente.

Per quanto riguarda il funzionamento interno di malloc, quando lo chiami il kernel Linux segna alcuni indirizzi come scrivibili sulle sue strutture di dati interne e quando vengono toccati inizialmente dal programma, si verifica un errore e il kernel abilita le tabelle delle pagine, che consentono l'accesso succede senza segfaul: come funziona il paging x86?

Si noti tuttavia che questo è fondamentalmente esattamente ciò che fa il execsyscall quando si tenta di eseguire un eseguibile: contrassegna le pagine in cui si desidera caricare e scrive lì il programma, vedere anche: In che modo il kernel ottiene un file binario eseguibile in esecuzione sotto Linux? Tranne che execha alcune limitazioni extra su dove caricare (ad es. Il codice non è trasferibile ).

L'esatto syscall usato mallocè mmapnelle moderne implementazioni 2020 e in passato è brkstato usato: malloc () usa brk () o mmap ()?

Librerie dinamiche

Fondamentalmente mmapvai in memoria: /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710

variabili envinroment e main'sargv

Sopra lo stack iniziale: /unix/75939/where-is-the-environment-string-actual-stored TODO perché non in .data?

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.