2015-02-17 24 views
6

Sto cercando di capire come C alloca la memoria sullo stack. Ho sempre pensato che le variabili sullo stack potessero essere rappresentate come le variabili dei membri delle strutture, occupano blocchi di byte successivi e contigui all'interno dello stack. Per aiutare a illustrare questo problema che ho trovato da qualche parte, ho creato questo piccolo programma che riproduceva il fenomeno.Le variabili di stack C sono memorizzate al contrario?

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

void function(int *i) { 
    int *_prev_int = (int *) ((long unsigned int) i - sizeof(int)) ; 
    printf("%d\n", *_prev_int);  
} 

void main(void) 
{ 
    int x = 152; 
    int y = 234; 
    function(&y); 
} 

Vedi cosa sto facendo? Supponiamo che sizeof(int) sia 4: sto osservando 4 byte dietro il puntatore passato, in quanto questo leggerà i 4 byte prima di dove int y nello stack del chiamante .

Non è stato stampato il 152. Stranamente quando guardo i prossimi 4 byte:

int *_prev_int = (int *) ((long unsigned int) i + sizeof(int)) ; 

e ora funziona, stampe qualunque in x all'interno dello stack del chiamante. Perché x ha un indirizzo inferiore a ? Le variabili stack sono memorizzate al contrario?

+0

stack è memorizzato verso il basso – Ajit

+2

Penso che sia l'implementazione definito/o non specificato. Controlla questa risposta http://stackoverflow.com/a/4105123/1673391 –

+3

Questo è completamente dipendente dalla piattaforma, ma molte piattaforme mainstream in realtà aumentano l'accumulo dalla sezione codice/dati e lo stack verso il basso dalla parte superiore di (disponibile) memoria. Dovresti ** non fare mai ** affidamento su quello nel tuo codice C, anche se ... – DevSolar

risposta

10

L'organizzazione dello stack è completamente non specificata ed è specifica di implementazione. In pratica, dipende molto dal compilatore (anche della sua versione) e dai flag di ottimizzazione.

Alcune variabili non siedono nemmeno sullo stack (ad esempio perché sono semplicemente conservate in alcuni registri, o perché il compilatore le ha ottimizzate, ad esempio mediante inlining, piegatura costante, ecc.).

BTW, potresti avere qualche ipotetica implementazione in C che non usa stack (anche se non riesco a nominare tale implementazione).

Per capire di più su pile:

  • Leggi la WikiPage su call stacks, tail calls, threads, e su continuations

  • familiarizzare con del computer architecture & instruction set (ad esempio x86) & ABI, quindi ...

  • chiedere al compilatore di mostrare il codice assemblatore e/o alcune rappresentazioni intermedie del compilatore. Se si utilizza GCC, compilare un codice semplice con gcc -S -fverbose-asm (per ottenere il codice assembler foo.s durante la compilazione di foo.c) e provare diversi livelli di ottimizzazione (almeno -O0, , -O2 ....). Prova anche l'opzione -fdump-tree-all (scarica centinaia di file che mostrano alcune rappresentazioni interne del compilatore per il tuo codice sorgente). Si noti che GCC prevede anche vecchia carta return address builtins

  • Leggi Appel di garbage collection can be faster than stack allocation su, e capire garbage collection tecniche (dal momento che spesso hanno bisogno di controllare ed eventualmente modificare alcune indicazioni all'interno stack frame di chiamata). Per saperne di più su GC, leggi lo GC handbook.

Purtroppo, io non conosco linguaggio di basso livello (come C, D, Rust, C++, Go, ...) in cui lo stack di chiamate è accessibile a livello di linguaggio.Questo è il motivo per cui la codifica di un garbage collector per C è difficile (dal momento che GC-s deve analizzare i puntatori dello stack delle chiamate) ... Ma vedi Boehm's conservative GC per una soluzione molto pratica e pragmatica.

+0

è necessario citare lo standard per tale asserzione/commenti –

+4

AFAIK, lo standard C99 non menziona la/qualsiasi pila in modo normativo. –

+1

@GrijeshChauhan: No, non ha bisogno di virgolette dallo standard, perché è ** non specificato ** dallo standard. Questo è il motivo per cui non dovresti fare affidamento sul suo comportamento. – DevSolar

3

Quasi tutte le architetture dei processori supportano attualmente le istruzioni di manipolazione dello stack (ad esempio LDM, istruzioni STM in ARM). I compilatori con l'aiuto di quegli stack di attrezzi. Nella maggior parte dei casi, quando i dati vengono inseriti nello stack, i decrementi del puntatore dello stack (crescente verso il basso) e gli incrementi quando i dati vengono estratti dallo stack.

Quindi dipende dall'architettura del processore e dal compilatore in che modo viene implementato lo stack.