Per capire meglio cosa sta succedendo, immaginiamo di avere solo un sistema operativo molto primitivo in esecuzione su un processore a 16 bit in grado di eseguire un solo processo alla volta. Vale a dire: solo un programma può essere eseguito contemporaneamente. Inoltre, facciamo finta che tutti gli interrupt siano disabilitati.
C'è un costrutto nel nostro processore chiamato stack. Lo stack è un costrutto logico imposto sulla memoria fisica. Diciamo che la nostra RAM esiste negli indirizzi da E000 a FFFF. Ciò significa che il nostro programma in esecuzione può utilizzare questa memoria come preferiamo. Immaginiamo che il nostro sistema operativo dica che E000 a EFFF è lo stack e che F000 a FFFF è l'heap.
Lo stack è gestito da hardware e da istruzioni macchina. Non c'è davvero molto che dobbiamo fare per mantenerlo. Tutto quello che dobbiamo fare (o il nostro sistema operativo) è assicurarci di impostare un indirizzo corretto per l'inizio dello stack. Il puntatore dello stack è un'entità fisica che risiede nell'hardware (processore) ed è gestito dalle istruzioni del processore. In questo caso, il nostro puntatore stack verrebbe impostato su EFFF (supponendo che lo stack aumenti INDIETRO, che è piuttosto comune, -). Con un linguaggio compilato come C, quando si chiama una funzione, si spinge qualsiasi argomento passato alla funzione nello stack. Ogni argomento ha una certa dimensione. int è in genere 16 o 32 bit, il char è in genere 8 bit, ecc. Supponiamo che sul nostro sistema, int e int * siano 16 bit. Per ogni argomento, il puntatore dello stack è DECREMENTED (-) per sizeof (argomento) e l'argomento viene copiato nello stack. Quindi, tutte le variabili che hai dichiarato nell'ambito sono spinte nello stack nello stesso modo, ma i loro valori non sono inizializzati.
Diamo riconsiderare due esempi simili ai vostri due esempi.
int hello(int eeep)
{
int i;
int *p;
}
Cosa accade qui sul nostro sistema a 16 bit è il seguente: 1) EEEP spinta nello stack. Ciò significa che decrementiamo il puntatore dello stack su EFFD (poiché sizeof (int) è 2) e quindi effettivamente copiamo eeep per indirizzare EFFE (il valore corrente del nostro puntatore dello stack, meno 1 perché il nostro puntatore dello stack punta al primo spot disponibile dopo l'assegnazione). A volte ci sono istruzioni che possono fare entrambe le cose in un colpo solo (supponendo che tu stia copiando i dati che si inseriscono in un registro, altrimenti dovresti copiare manualmente ogni elemento di un tipo di dati nella giusta posizione nello stack - ordine!).
2) creare spazio per i. Questo probabilmente significa solo decrementare il puntatore dello stack su EFFB.
3) creare spazio per p. Questo probabilmente significa solo decrementare il puntatore dello stack su EFF9.
Allora il nostro programma viene eseguito, ricordare dove vivono le nostre variabili (EEEP inizia alle EFFE, ho a EFFC, e p a EFFA). La cosa importante da ricordare è che anche se lo stack conta INDIETRO, le variabili continuano a funzionare IN AVANTI (questo dipende in realtà da endianness, ma il punto è che & eeep == EFFE, non EFFF).
Quando la funzione chiude, semplicemente incremento (++) il puntatore stack 6, (perché 3 "oggetti", non C++ genere, di misura 2 sono state spinte in pila.
Ora, il secondo scenario è molto più difficile da spiegare, perché ci sono tanti metodi per realizzare è che è quasi impossibile da spiegare su internet.
int hello(int eeep)
{
int *p = malloc(sizeof(int));//C's pseudo-equivalent of new
free(p);//C's pseudo-equivalent of delete
}
EEEP e P sono ancora spinti e allocati sullo stack come nel precedente esempio, in questo caso, inizializziamo p al risultato di una chiamata di funzione: che malloc (o new, ma new fa di più in C++. trotori quando appropriato, e tutto il resto.) fa è andare a questa scatola nera chiamata HEAP e ottiene un indirizzo di memoria libera. Il nostro sistema operativo gestirà l'heap per noi, ma dobbiamo informarlo quando vogliamo la memoria e quando abbiamo finito.
Nell'esempio, quando chiamiamo malloc(), il sistema operativo restituirà un blocco di 2 byte (sizeof (int) sul nostro sistema è 2) fornendoci l'indirizzo iniziale di questi byte. Diciamo che la prima chiamata ci ha dato l'indirizzo F000. Il sistema operativo tiene quindi traccia degli indirizzi F000 e F001 attualmente in uso. Quando chiamiamo libero (p), il sistema operativo trova il blocco di memoria a cui punta p e segna 2 byte come non usato (perché sizeof (stella p) è 2). Se invece allociamo più memoria, l'indirizzo F002 verrà probabilmente restituito come blocco iniziale della nuova memoria. Si noti che malloc() stesso è una funzione. Quando p viene messo in pila per la chiamata di malloc(), il p viene copiato nuovamente nello stack al primo indirizzo aperto che ha abbastanza spazio sullo stack per adattarsi alla dimensione di p (probabilmente EFFB, perché abbiamo solo premuto 2 le cose nello stack questa volta di dimensione 2, e sizeof (p) è 2), e il puntatore dello stack è nuovamente decrementato su EFF9, e malloc() metterà le sue variabili locali nello stack a partire da questa posizione. Quando termina Malloc, esso espelle tutti gli elementi dallo stack e imposta il puntatore dello stack su ciò che era prima che venisse chiamato. Il valore di ritorno di malloc(), una stella vuota, sarà probabilmente inserito in un registro (di solito l'accumulatore su molti sistemi) per il nostro uso.
In fase di implementazione, entrambi gli esempi REALMENTE non sono così semplici. Quando assegni la memoria dello stack, per una nuova chiamata di funzione, devi assicurarti di salvare il tuo stato (salva tutti i registri) in modo che la nuova funzione non cancelli i valori in modo permanente. Questo di solito comporta anche spingerli in pila. Allo stesso modo, in genere si salva il registro del programma in modo da poter tornare al punto corretto dopo il ritorno della subroutine. I gestori della memoria usano la memoria per "ricordare" quale memoria è stata distribuita e cosa no. La memoria virtuale e la segmentazione della memoria complicano ulteriormente questo processo, e gli algoritmi di gestione della memoria devono continuamente spostare i blocchi (e proteggerli anche) al fine di prevenire la frammentazione della memoria (un argomento a sé stante), e questo si lega alla memoria virtuale anche. Il secondo esempio è davvero un grande contenitore di vermi rispetto al primo esempio. Inoltre, l'esecuzione di più processi rende tutto ciò molto più complicato, poiché ogni processo ha il proprio stack e l'heap può essere utilizzato da più processi (il che significa che deve proteggersi). Inoltre, ogni architettura del processore è diversa. Alcune architetture si aspettano che tu imposti il puntatore dello stack sul primo indirizzo libero nello stack, altri si aspettano che tu lo punti al primo punto non libero.
Spero che questo abbia aiutato. Per favore mi faccia sapere.
avviso, tutti gli esempi precedenti sono per una macchina fittizia che è eccessivamente semplificata. Su hardware reale, questo diventa un po 'più peloso.
modifica: gli asterischi non vengono visualizzati. Li ho sostituiti con la parola "stella"
Per quello che vale, se usiamo (soprattutto) lo stesso codice negli esempi, in sostituzione di "ciao" con "example1" e "esempio2", rispettivamente, abbiamo ottenere il seguente output di assembly per Intel su Windows.
.file "test1.c"
.text
.globl _example1
.def _example1; .scl 2; .type 32; .endef
_example1:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
leave
ret
.globl _example2
.def _example2; .scl 2; .type 32; .endef
_example2:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $4, (%esp)
call _malloc
movl %eax, -4(%ebp)
movl -4(%ebp), %eax
movl %eax, (%esp)
call _free
leave
ret
.def _free; .scl 3; .type 32; .endef
.def _malloc; .scl 3; .type 32; .endef
Come possono avere la stessa allocazione di memoria? Il primo alloca una dimensione per un int (in pila) e un puntatore (in pila). Il secondo alloca un puntatore sullo stack e lo spazio per un int sull'heap. – bobbyalex
Fondamentalmente la mia domanda è "Al momento dell'esecuzione, qual è la differenza tra stack e heap?" – Tarquila
Prova qui: http://stackoverflow.com/questions/79923/what-and-where-are-the-stack-and-heap – Alex