Ein Segmentierungsfehler tritt auf, wenn ein Programm versucht, auf Speicher außerhalb des ihm zugewiesenen Bereichs zuzugreifen.
In diesem Fall kann ein erfahrener C-Programmierer sehen, dass das Problem in der Zeile auftritt, in der er sprintf
aufgerufen wird. Wenn Sie jedoch nicht erkennen können, wo Ihr Segmentierungsfehler auftritt, oder wenn Sie sich nicht die Mühe machen möchten, den Code zu lesen, um ihn herauszufinden, können Sie Ihr Programm mit Debug-Symbolen erstellen (mit gcc
, das -g
Flag tut dies ) und führen Sie es dann durch einen Debugger.
Ich habe Ihren Quellcode kopiert und in eine von mir genannte Datei eingefügt slope.c
. Dann habe ich es so gebaut:
gcc -Wall -g -o slope slope.c
( -Wall
Dies ist optional. Es dient nur dazu, Warnungen für weitere Situationen zu erstellen. Dies kann auch dazu beitragen, herauszufinden, was möglicherweise falsch ist.)
Dann habe ich das Programm im Debugger ausgeführt, gdb
indem ich zuerst ausgeführt habe gdb ./slope
, um gdb
mit dem Programm zu beginnen , und dann, einmal im Debugger, dem Debugger den run
Befehl gegeben habe:
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
(Machen Sie sich keine Sorgen um meine you have broken Linux kernel i386 NX
... support
Nachricht. Sie verhindert nicht, dass gdb
dieses Programm effektiv zum Debuggen verwendet wird.)
Diese Informationen sind sehr kryptisch ... und wenn Sie keine Debug-Symbole für libc installiert haben, erhalten Sie eine noch kryptischere Nachricht mit einer hexadezimalen Adresse anstelle des symbolischen Funktionsnamens _IO_default_xsputn
. Zum Glück spielt es keine Rolle, denn wir möchten wirklich wissen, wo in Ihrem Programm das Problem auftritt.
Die Lösung besteht also darin, rückwärts zu schauen, um zu sehen, welche Funktionsaufrufe vor diesem bestimmten Funktionsaufruf in einer Systembibliothek stattgefunden haben, in der das SIGSEGV
Signal schließlich ausgelöst wurde.
gdb
(und jeder Debugger) hat diese Funktion eingebaut: Sie wird als Stack-Trace oder Backtrace bezeichnet . Ich benutze den bt
Debugger-Befehl, um eine Rückverfolgung zu generieren 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)
Sie können sehen, dass Ihre main
Funktion die calc_slope
Funktion (die Sie beabsichtigt haben) calc_slope
aufruft und dann aufruft sprintf
, die (auf diesem System) mit Aufrufen einiger anderer verwandter Bibliotheksfunktionen implementiert wird.
Was Sie allgemein interessiert, ist der Funktionsaufruf in Ihrem Programm , der eine Funktion außerhalb Ihres Programms aufruft . Sofern in der Bibliothek / den Bibliotheken selbst, die Sie verwenden, kein Fehler vorliegt (in diesem Fall die in der Bibliotheksdatei libc
bereitgestellte Standard-C-Bibliothek libc.so.6
), befindet sich der Fehler, der den Absturz verursacht, in Ihrem Programm und befindet sich häufig in oder in der Nähe von letzter Aufruf in Ihrem Programm.
In diesem Fall ist das:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
Dort ruft Ihr Programm auf sprintf
. Wir wissen das, weil sprintf
es der nächste Schritt ist. Aber auch ohne dass dies angegeben ist, wissen Sie das, denn genau das passiert in Zeile 26 und es heißt:
... at slope.c:26
In Ihrem Programm enthält Zeile 26:
sprintf(s,"%d",curr);
(Sie sollten immer einen Texteditor verwenden, der automatisch Zeilennummern anzeigt, zumindest für die Zeile, in der Sie sich gerade befinden. Dies ist sehr hilfreich bei der Interpretation sowohl von Fehlern bei der Kompilierung als auch von Laufzeitproblemen, die bei der Verwendung eines Debuggers auftreten.)
Wie in Dennis Kaarsemakers Antwort erläutert , s
handelt es sich um ein Ein-Byte-Array. (Nicht Null, da der Wert , den Sie zugewiesen haben, ""
ist ein Byte lang, das heißt, ist es gleich { '\0' }
, in der gleichen Art und Weise , die "Hello, world!\n"
zu gleich { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
.)
Warum könnte dies auf einer Plattform immer noch funktionieren (und anscheinend auch, wenn es mit VC9 für Windows kompiliert wurde)?
Die Leute sagen oft, dass, wenn Sie Speicher zuweisen und dann versuchen, auf Speicher außerhalb davon zuzugreifen, dies einen Fehler erzeugt. Das stimmt aber nicht wirklich. Nach den technischen Standards von C und C ++ führt dies tatsächlich zu undefiniertem Verhalten.
Mit anderen Worten, alles kann passieren!
Dennoch sind einige Dinge wahrscheinlicher als andere. Warum scheint ein kleines Array auf dem Stapel in einigen Implementierungen wie ein größeres Array auf dem Stapel zu funktionieren?
Dies hängt davon ab, wie die Stapelzuweisung implementiert wird, die von Plattform zu Plattform variieren kann. Ihre ausführbare Datei weist ihrem Stapel möglicherweise mehr Speicher zu, als tatsächlich zu einem bestimmten Zeitpunkt verwendet werden soll. Manchmal können Sie auf diese Weise in Speicherorte schreiben, auf die Sie in Ihrem Code keinen expliziten Anspruch erhoben haben. Es ist sehr wahrscheinlich, dass dies passiert, wenn Sie Ihr Programm in VC9 erstellen.
Allerdings sollten Sie nicht verlassen sich auf dieses Verhalten auch in VC9. Dies kann möglicherweise von verschiedenen Versionen von Bibliotheken abhängen, die auf verschiedenen Windows-Systemen vorhanden sein können. Aber noch wahrscheinlicher ist das Problem , dass die zusätzlichen Stapelspeicher mit der Absicht zugeordnet ist , dass es tatsächlich verwendet werden, und so kann es tatsächlich verwendet werden.Dann erleben Sie den Albtraum eines "undefinierten Verhaltens", bei dem in diesem Fall mehr als eine Variable an derselben Stelle gespeichert werden kann, an der das Schreiben in eine Variable die andere überschreibt ... aber nicht immer, weil manchmal in Variablen geschrieben wird werden in Registern zwischengespeichert und nicht sofort ausgeführt (oder Lesevorgänge in Variablen können zwischengespeichert werden, oder es kann angenommen werden, dass eine Variable dieselbe ist wie zuvor, da dem Compiler bekannt ist, dass der ihr zugewiesene Speicher nicht durchgeschrieben wurde die Variable selbst).
Und das bringt mich zu der anderen wahrscheinlichen Möglichkeit, warum das Programm bei der Erstellung mit VC9 funktioniert hat. Es ist möglich und etwas wahrscheinlich, dass ein Array oder eine andere Variable tatsächlich von Ihrem Programm zugewiesen wurde (einschließlich der Zuweisung durch eine Bibliothek, die Ihr Programm verwendet), um den Speicherplatz nach dem Ein-Byte-Array zu verwenden s
. Wenn Sie also s
als Array behandeln, das länger als ein Byte ist, hat dies den Effekt, dass Sie auf den Inhalt dieser / dieser Variablen / Arrays zugreifen, was ebenfalls schlecht sein kann.
Wenn Sie einen solchen Fehler haben, können Sie glücklicherweise einen Fehler wie "Segmentierungsfehler" oder "Allgemeiner Schutzfehler" erhalten. Wenn Sie das nicht haben, werden Sie möglicherweise erst herausfinden, wenn es zu spät ist, dass Ihr Programm ein undefiniertes Verhalten aufweist.