Si verifica un errore di segmentazione quando un programma tenta di accedere alla memoria al di fuori dell'area che è stata allocata per esso.
In questo caso, un programmatore C esperto può vedere che il problema si sta verificando nella linea in cui sprintf
viene chiamato. Ma se non riesci a capire dove si sta verificando l'errore di segmentazione o se non vuoi disturbarti a leggere il codice per provare a capirlo, puoi costruire il tuo programma con simboli di debug (con gcc
, il -g
flag fa questo ) e quindi eseguirlo tramite un debugger.
Ho copiato il tuo codice sorgente e incollato in un file che ho nominato slope.c
. Quindi l'ho costruito in questo modo:
gcc -Wall -g -o slope slope.c
( -Wall
È facoltativo. È solo per far sì che produca avvisi per più situazioni. Questo può aiutare a capire anche cosa potrebbe essere sbagliato.)
Quindi ho eseguito il programma nel debugger gdb
eseguendo prima gdb ./slope
di iniziare gdb
con il programma e quindi, una volta nel debugger, dando il run
comando al debugger:
ek@Kip:~/source$ gdb ./slope
GNU gdb (GDB) 7.5-ubuntu
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ek/source/slope...done.
(gdb) run
Starting program: /home/ek/source/slope
warning: Cannot call inferior functions, you have broken Linux kernel i386 NX (non-executable pages) support!
Program received signal SIGSEGV, Segmentation fault.
0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
(Non preoccuparti per il mio you have broken Linux kernel i386 NX
... support
messaggio; non impedisce gdb
di essere utilizzato in modo efficace per il debug di questo programma.)
Queste informazioni sono altamente criptiche ... e se non hai installato i simboli di debug per libc, otterrai un messaggio ancora più criptico che ha un indirizzo esadecimale invece del nome della funzione simbolica _IO_default_xsputn
. Fortunatamente, non importa, perché quello che vogliamo davvero sapere è dove si sta verificando il problema nel tuo programma .
Quindi, la soluzione è guardare indietro, per vedere quali chiamate di funzione hanno avuto luogo prima di quella particolare chiamata di funzione in una libreria di sistema in cui il SIGSEGV
segnale è stato finalmente attivato.
gdb
(e qualsiasi debugger) ha questa funzione integrata: si chiama stack trace o backtrace . Uso il bt
comando debugger per generare un backtrace in gdb
:
(gdb) bt
#0 0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
#1 0x00178e04 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#2 0x0019b234 in vsprintf () from /lib/i386-linux-gnu/libc.so.6
#3 0x0017ff7b in sprintf () from /lib/i386-linux-gnu/libc.so.6
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
#5 0x08048578 in main () at slope.c:52
(gdb)
Puoi vedere che la tua main
funzione chiama la calc_slope
funzione (che hai previsto), e quindi calc_slope
chiama sprintf
, che è (su questo sistema) implementato con chiamate a un paio di altre funzioni di libreria correlate.
Ciò a cui sei generalmente interessato è la chiamata di funzione nel tuo programma che chiama una funzione al di fuori del tuo programma . A meno che non ci sia un bug nella libreria / librerie stesse che stai usando (in questo caso, la libreria C standard libc
fornita dal file della libreria libc.so.6
), il bug che causa l'arresto anomalo è nel tuo programma e spesso si troverà in o vicino al ultima chiamata nel tuo programma.
In questo caso, questo è:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
Ecco dove il tuo programma chiama sprintf
. Lo sappiamo perché sprintf
è il prossimo passo in avanti. Ma anche senza affermarlo, lo sai perché è quello che succede sulla linea 26 , e dice:
... at slope.c:26
Nel tuo programma, la riga 26 contiene:
sprintf(s,"%d",curr);
(È necessario utilizzare sempre un editor di testo che mostri automaticamente i numeri di riga, almeno per la riga attualmente in uso. Questo è molto utile nell'interpretazione sia degli errori di compilazione sia dei problemi di runtime rilevati durante l'utilizzo di un debugger.)
Come discusso nella risposta di Dennis Kaarsemaker , s
è un array a un byte. (Non zero, poiché il valore che gli è stato assegnato ""
, è lungo un byte, ovvero è uguale a { '\0' }
, nello stesso modo in cui "Hello, world!\n"
è uguale a{ 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
.)
Quindi, perché potrebbe funzionare ancora su alcune piattaforme (e apparentemente funziona quando compilato con VC9 per Windows)?
Le persone spesso dicono che quando si alloca memoria e quindi si tenta di accedere alla memoria al di fuori di essa, ciò produce un errore. Ma questo non è proprio vero. Secondo gli standard tecnici C e C ++, ciò che realmente produce è comportamento indefinito.
In altre parole, tutto può succedere!
Tuttavia, alcune cose sono più probabili di altre. Perché un piccolo array nello stack sembrerà funzionare, in alcune implementazioni, come un array più grande nello stack?
Questo dipende dal modo in cui viene implementata l'allocazione dello stack, che può variare da piattaforma a piattaforma. Il file eseguibile potrebbe allocare più memoria nel suo stack di quanto si intenda effettivamente utilizzare in qualsiasi momento. A volte ciò può consentire di scrivere in posizioni di memoria che non sono state esplicitamente rivendicate nel codice. È molto probabile che questo sia ciò che accade quando costruisci il tuo programma in VC9.
Tuttavia, non dovresti fare affidamento su questo comportamento anche in VC9. Potrebbe dipendere potenzialmente da diverse versioni di librerie che potrebbero esistere su diversi sistemi Windows. Ma è ancora più probabile il problema che lo spazio di stack aggiuntivo sia allocato con l'intenzione che verrà effettivamente utilizzato, e così potrebbe effettivamente essere utilizzato.Quindi si sperimenta l'intero incubo del "comportamento indefinito", in cui, in questo caso, più di una variabile potrebbe finire memorizzata nello stesso posto, dove scrivere su una sovrascrive l'altra ... ma non sempre, perché a volte scrive su variabili sono memorizzati nella cache nei registri e non vengono effettivamente eseguiti immediatamente (o le letture delle variabili possono essere memorizzate nella cache o si può presumere che una variabile sia la stessa di prima perché la memoria assegnata ad essa è nota dal compilatore per non essere stata scritta attraverso la variabile stessa).
E questo mi porta all'altra probabile possibilità per cui il programma ha funzionato quando è stato creato con VC9. È possibile, e un po 'probabile, che un array o un'altra variabile sia stata effettivamente allocata dal programma (che può includere l'assegnazione da una libreria utilizzata dal programma) per utilizzare lo spazio dopo l'array a un byte s
. Quindi, trattare s
come un array più lungo di un byte avrebbe l'effetto di accedere al contenuto di quello / quelle variabili / array, che potrebbe anche essere cattivo.
In conclusione, quando si verifica un errore come questo, è fortunato ad avere un errore come "Errore di segmentazione" o "Errore di protezione generale". Quando non lo possiedi, potresti non scoprire fino a quando non è troppo tardi che il tuo programma abbia un comportamento indefinito.