2012-06-18 8 views
6

Durante la scrittura del codice C++ ho appreso che utilizzare lo stack per archiviare la memoria è una buona idea.Rilevamento dello stack pieno

Ma di recente mi sono imbattuto in un problema:

ho avuto un esperimento che aveva il codice che si presentava così:

void fun(const unsigned int N) { 
    float data_1[N*N]; 
    float data_2[N*N]; 

    /* Do magic */ 
} 

Il codice exploted con un guasto seqmentation a caso, e non avevo idea perché.

Si è scoperto che il problema era che stavo cercando di memorizzare cose che erano grandi sul mio stack, c'è un modo per rilevarlo? O almeno a scoprire che è andato storto?

+1

La tua domanda su C o C++? –

+0

Penso che non sia possibile inizializzare una matrice sullo stack usando le variabili in C/C++! –

+0

Quanto sono grandi i tuoi dati? Suggerisco di memorizzare 100 KB + dati nell'heap usando malloc/free. (Altre persone potrebbero suggerire un limite ancor più basso) – nhahtdh

risposta

5
float data_1[N*N]; 
float data_2[N*N]; 

Questi sono array di lunghezza variabile (VLA), come N non è un'espressione costante. La constanza nel parametro garantisce solo che N sia di sola lettura. Non dice al compilatore che N è un'espressione costante.

VLA sono consentiti solo in C99; in altre versioni di C e tutte le versioni di C++ non sono consentite. Tuttavia, alcuni compilatori forniscono VLA come funzionalità di estensione del compilatore. Se stai compilando con GCC, quindi prova a usare l'opzione -pedantic, ti dirà che non è permesso.

Ora, perché il programma dà segfault, probabilmente a causa della stack-overflow a causa di grande valore N * N:


considerare l'utilizzo di std::vector come:

#include <vector> 

void fun(const unsigned int N) 
{ 
    std::vector<float> data_1(N*N); 
    std::vector<float> data_2(N*N); 

    //your code 
} 
+0

Ho aggiunto il const al questing :-) –

+2

parametro di funzione const non cambia nulla qui, il valore è ancora noto solo al runtime, quindi stai ancora facendo affidamento su un'estensione del compilatore. Comunque sia C o C++, il punto è: se hai bisogno di molta memoria, usa invece heap (tramite 'std :: vector'). – Kos

+1

@MartinKristiansen: Ho modificato la mia risposta, dicendo * "La costanza nel parametro assicura che' N' sia di sola lettura, non dice al compilatore che 'N' è un'espressione costante." * – Nawaz

1

Prova utilizzando invece funzioni come malloc. Restituirà null esplicitamente, se non è riuscito a trovare un blocco di memoria della dimensione richiesta.

Naturalmente, in tal caso, non dimenticare di liberare questa memoria alla fine della funzione, dopo aver terminato.

Inoltre, è possibile controllare le impostazioni del compilatore, con quale limite di memoria dello stack genera i file binari.

+2

Questo non è quello che sta chiedendo. –

+0

Malloc aiuta - e usare malloc è stata la mia solluizione, ma ciò significa che ora ho un puntatore da cui tenere traccia. –

+0

Sì, è vero per il puntatore, ma dal momento che risiede in una singola funzione, non c'è alcun problema. Basta aggiungere la cancellazione alla fine della funzione –

1

Uno dei motivi per cui le persone dicono che è meglio utilizzare lo stack anziché la memoria heap può essere dovuto al fatto che le variabili allocate in cima allo stack verranno estratte automaticamente quando si lascia il corpo della funzione. Per archiviare grandi blocchi di informazioni è usuale utilizzare la memoria heap e altre strutture dati come elenchi o alberi collegati. Anche i ricordi allocati nello stack sono limitati e molto meno di quanto è possibile allocare nello spazio heap. Penso che sia meglio gestire l'allocazione della memoria e rilasciarlo con più attenzione invece di provare a utilizzare lo stack per archiviare i big data.

È possibile utilizzare il framework che gestisce le allocazioni di memoria. Puoi anche usare VDL per controllare le perdite di memoria e i ricordi che non vengono rilasciati.

+0

Questo è il mio punto, ho scritto il codice per essere "principalmente" usato su piccole "N", ma una volta un grande N si accorgerà. –

1

c'è un modo per rilevarlo?

No, in generale.

Le dimensioni dello stack dipendono dalla piattaforma.In genere, il sistema operativo decide la dimensione della pila. Quindi puoi controllare il tuo sistema operativo (ulimit -s su linux) per vedere quanta memoria di stack assegna al tuo programma.

Se il compilatore supporta stackavail(), è possibile verificarlo. È meglio utilizzare la memoria allocata su heap in situazioni in cui non si è certi di superare il limite di stack.

+0

ma ecco la cosa, stavo sviluppando su una scatola Linux con un hardware di alto livello, e spendo molto tempo a capire perché il mio codice non è stato eseguito sul mio MacBookPro. La scatola Linux non ha avuto problemi con uno stack di 4 MB, ma la scatola OSX non gli piaceva. –

2

È estremamente difficile rilevare che lo stack è pieno e non è affatto portatile. Uno dei maggiori problemi è che i frame stack sono di dimensioni variabili (specialmente quando si usano array di lunghezza variabile, che sono in realtà solo un modo più standard di fare ciò che le persone facevano prima con alloca()) quindi non si possono usare semplici proxy come il numero di frame dello stack.

Uno dei metodi più semplici che è principalmente portatile è quello di mettere una variabile (probabilmente di tipo char modo che un puntatore ad essa è un char*) ad una profondità nota in pila e quindi misurare la distanza da tale puntare a una variabile (dello stesso tipo) nel frame dello stack corrente con l'aritmetica del puntatore semplice. Aggiungi una stima di quanto spazio stai per allocare, e puoi avere una buona idea se lo stack sta per esplodere su di te. I problemi con questo sono che non si conosce la direzione in cui cresce lo stack (no, non crescono tutti nella stessa direzione!) E l'elaborazione della dimensione dello spazio di stack è di per sé piuttosto disordinata (è possibile provare cose come i limiti del sistema, ma sono davvero piuttosto scomodi). Inoltre il fattore di hacking è molto alto.

L'altro trucco che ho visto usare a 32 bit solo per Windows è stato quello di cercare di alloca() sufficiente spazio e gestire l'eccezione sistema che si verificherebbe se ci fosse spazio sufficiente.

int have_enough_stack_space(void) { 
    int enough_space = 0; 

    __try {   /* Yes, that's got a double-underscore. */ 
     alloca(SOME_VALUE_THAT_MEANS_ENOUGH_SPACE); 
     enough_space = 1; 
    } __except (EXCEPTION_EXECUTE_HANDLER) {} 

    return enough_space; 
} 

Questo codice è molto non portabile (ad esempio, non contate su di esso a lavorare su Windows a 64 bit) e la costruzione con gcc anziani richiede qualche brutta assembler inline, invece! La gestione strutturata delle eccezioni (di cui questo è un uso) è tra le più nere delle arti nere su Windows. (E non return dall'interno del costrutto __try.)

+0

Questo era tutto basato sul codice di controllo dello stack che era nell'implementazione di Tcl 8.4; il codice con uso SEH è stato convertito in usando il primo metodo basato su puntatore in 8.5, e completamente abbandonato in 8.6 (che utilizza invece un'implementazione del motore "senza stack", e quindi non ha bisogno di questo genere di cose quasi tanto). –

+0

BTW, la memorizzazione degli array di dimensioni variabili sull'heap sarà molto più affidabile, soprattutto se è possibile utilizzare le tecniche RAII per garantire l'eliminazione affidabile. –