Cosa può causare errori di segmentazione in C ++?

Ho notato che non c’è dubbio con un elenco di cause comuni di errori di segmentazione in C ++, quindi ho pensato di aggiungerlo.

Naturalmente è Wiki di comunità, dal momento che non esiste una risposta corretta.

Penso che questo potrebbe essere utile per i nuovi programmatori che imparano C ++, sentiti libero di chiuderlo se non sei d’accordo.

L’errore di segmentazione è causato da cattivi accessi alla memoria, solo se il sistema operativo ha una MMU. Altrimenti, non lo capirai, ma solo un comportamento strano.

La memoria virtuale (l’intera memoria accessibile per te = 2 ^ sizeof(pointer type) ) viene mappata alla memoria fisica in unità denominate pagine o segmenti (segmentazione di ricerca per superamento pagina, ma sono ancora utilizzate).

Ogni pagina ha alcuni diritti di protezione, se provi a leggere da una pagina con accesso senza lettura otterrai un segfault. Se provi a scrivere in una posizione di sola lettura riceverai un SIGSEGV.

Se hai un puntatore unitializzato e lo usi, potrebbe accadere che punti ad un’altra buona posizione, in modo da non ottenere un segfault. Se hai un piccolo array che legge dopo che è rilegato, può corrompere altre aree di memoria se non supera il limite della pagina.

Inoltre, poiché ci sono molte pagine, non tutte sono realmente mappate. Se tocchi una pagina non mappata otterrai un segfault. In realtà, qualsiasi accesso a una pagina non mappata dovrà tenere conto della copia su scrittura, pagine su swap, caricamento pigro, file mappati in memoria e altre cose. Vedi questo articolo sulla gestione degli errori di pagina , in particolare il secondo diagramma lì, pubblicato qui sotto anche qui (ma leggi l’articolo per ulteriori spiegazioni)

gestione degli errori di pagina

Ti interessa principalmente ciò che accade nello spazio utente e tutti i percorsi che portano a SIGSEGV. ma lo spazio del kernel è anche interessante.

Dereferenziamento di puntatori NULL.

 #include  //For NULL. int* p1 = NULL; //p1 points to no memory address *p1 = 3; //Segfault. 

Accesso a un array fuori limite (Possibile):

 int ia[10]; ia[10] = 4; // Someone forgot that arrays are 0-indexed! Possible Segfault. 

Molti dei modi per “segfault” C ++ non sono necessariamente garantiti , infatti, è il caso della maggior parte degli esempi pubblicati qui. È semplicemente buona fortuna (o sfortuna, a seconda di come la si guarda!) Se è ansible eseguire queste operazioni senza che si verifichi un segfault.

Questo è in realtà una delle cose in C ++ che lo separa dalle altre lingue; comportamento indefinito. Considerando che in Java o C # potresti ottenere una ‘InvalidOperationException’ o qualcosa di simile, che è garantito che avvenga quando queste operazioni vengono eseguite; in C ++, lo standard dice solo ‘comportamento indefinito’, che è fondamentalmente la fortuna del sorteggio, e non vuoi mai che ciò accada.

Uno dei miei preferiti:

 #include  struct A { virtual void f() { std::cout << "A::f();\n"; } int i; }; struct B : A { virtual void f() { std::cout << "B::f();\n"; } int j; }; void seti(A* arr, size_t size) { for (size_t i = 0; i < size; ++i) arr[i].i = 0; } int main() { B b[10]; seti(b, 10); b[3].f(); } 

Come con la maggior parte delle cose che possono causare un segfault, anche questo può fallire. Su ideone, ad esempio, b[3].f() fallisce, ma b[2].f() funziona.

La risposta ovvia è “comportamento indefinito”, ma questo solleva la domanda per un programmatore inesperto, e alcuni tipi di comportamento non definito sono molto meno probabilità di causare un errore di segmentazione (o un altro tipo di arresto anomalo) rispetto ad altri. Le cause più frequenti di errori di segmentazione sono generalmente legate al puntatore: dereferenziamento di un puntatore non inizializzato, un puntatore nullo o un puntatore precedentemente liberato; accedere oltre la fine (o di fronte all’inizio, ma è meno frequente) di un object (matrice o altro); utilizzando i risultati di un cast puntatore illegale ( static_cast su un tipo derivato, quando l’object non ha effettivamente quel tipo o la maggior parte reinterpret_cast ); eccetera.

Forse il punto più importante da tenere presente qui, tuttavia, è che, in generale, non è garantito che ciò causi un errore di segmentazione e che spesso l’errore di segmentazione che causano si verifichi solo in seguito, in un’operazione completamente non correlata. Quindi, scrivere oltre la fine di un array locale di solito “funziona”, ma modificherà qualsiasi cosa accada a seguire l’array nello stack: qualche altra variabile locale (modificando il vptr di un object sullo stack può portare a un errore di segmentazione quando si tenta di chiamare una funzione virtuale sull’object), il puntatore del frame della funzione chiamante (che probabilmente causerà un errore di segmentazione in quella funzione, dopo che è stato restituito), o l’indirizzo di ritorno (che potrebbe causare ogni sorta di strano comportamento: un errore di segmentazione o una trappola di istruzioni illegale sono probabilmente i migliori che possono verificarsi). Scrivere oltre la fine della memoria liberata, o attraverso un puntatore già liberato, può corrompere l’arena dello spazio libero, causando un errore di segmentazione in una allocazione (molto, molto, molto) successiva o gratuita; può anche modificare qualche altro object totalmente non correlato, corrompendo il suo vptr o qualche altro puntatore nell’object, o solo alcuni dati casuali – di nuovo, un errore di segmentazione è probabilmente il miglior risultato ansible (di gran lunga preferibile continuare con dati corrotti).

Dimenticando di inizializzare i puntatori, lasciandoli con indirizzi di memoria casuali. Nota: questo potrebbe non sempre segfault, ma potrebbe.

 int* p1; //No initialization. *p1 = 3; //Possible segfault. 

Il dereferenziamento della memoria liberata potrebbe potenzialmente causare un segfault.

 SomeClass* someObject = new SomeClass(); delete someObject; someObject->someMethod(); //Could cause a segfault. 

Cercando di modificare stringhe letterali:

 char* mystr = "test"; mystr[2] = 'w'; 

Questo CAN può causare un errore di segmentazione.