Auf dieser Seite:
- Übersicht
- Beispiele für häufige Segmentierungsfehler
- Array-Referenzen außerhalb der Grenzen finden
- Shell-Grenzwerte überprüfen
- Verwenden Sie Debugger, um segmentierungsfehler
Übersicht
Ein Segmentierungsfehler (auch bekannt als Segfault) ist eine häufige Bedingung, die zum Absturz von Programmen führt. Sie werden oft mit einer Datei namens core
. Segfaults werden durch ein Programm verursacht, das versucht, einen ungültigen Speicherort zu lesen oder zu schreiben.
Der Programmspeicher ist in verschiedene Segmente unterteilt: ein Textsegment für Programmanweisungen, ein Datensegment für Variablen und Arrays, die zur Kompilierungszeit definiert wurden, ein Stapelsegment für temporäre (oder automatische) Variablen, die in Unterprogrammen und Funktionen definiert sind, und ein Heapsegment für Variablen, die während der Laufzeit von Funktionen zugewiesen wurden, z. B. malloc
(in C) und allocate
(in Fortran). Weitere Informationen finden Sie unter Über Programmsegmente.
Ein Segfault tritt auf, wenn ein Verweis auf eine Variable außerhalb des Segments liegt, in dem sich diese Variable befindet, oder wenn versucht wird, an eine Position zu schreiben, die sich in einem schreibgeschützten Segment befindet. In der Praxis sind Segfaults fast immer darauf zurückzuführen, dass versucht wird, ein nicht vorhandenes Array-Element zu lesen oder zu schreiben, einen Zeiger vor der Verwendung nicht richtig zu definieren oder (in C-Programmen) versehentlich den Wert einer Variablen als Adresse zu verwenden (siehe das Beispiel scanf
unten).
Beispiele für häufige Segfaults
- Wenn Sie beispielsweise
memset()
wie unten gezeigt aufrufen, würde ein Programm zu einem Segfault führen:memset((char *)0x0, 1, 100);
- Die folgenden drei Fälle veranschaulichen die häufigsten Arten von Array-bezogenen Segfaults:
Fall A /* "Array out of bounds" error valid indices for array foo are 0, 1, ... 999 */ int foo; for (int i = 0; i <= 1000 ; i++) foo = i;
Fall B /* Illegal memory access if value of n is not in the range 0, 1, ... 999 */ int n; int foo; for (int i = 0; i < n ; i++) foo = i;
Fall C /* Illegal memory access because no memory is allocated for foo2 */ float *foo, *foo2; foo = (float*)malloc(1000); foo2 = 1.0;
- Im Fall A array
foo
ist definiert fürindex = 0,1, 2, ... 999
. In der letzten Iteration der Schleifefor
versucht das Programm jedoch auffoo
zuzugreifen. Dies führt zu einem Segfault, wenn dieser Speicherort außerhalb des Speichersegments liegt, in dem sichfoo
befindet. Auch wenn es keinen Segfault verursacht, ist es immer noch ein Fehler. - In Fall B könnte integer
n
ein beliebiger Zufallswert sein. Wie in Fall A kann es zu einem Segfault kommen, wenn es nicht im Bereich0, 1, ... 999
liegt. Ob es das tut oder nicht, es ist sicherlich ein Fehler. - In Fall C wurde die Speicherzuweisung für die Variable
foo2
übersehen, sodassfoo2
auf einen zufälligen Speicherort zeigt. Der Zugriff auffoo2
führt wahrscheinlich zu einem Segfault.
- Im Fall A array
- Ein weiterer häufiger Programmierfehler, der zu Segfaults führt, ist ein Versehen bei der Verwendung von Zeigern. Zum Beispiel erwartet die C-Funktion
scanf()
die Adresse einer Variablen als zweiten Parameter; Daher führt Folgendes wahrscheinlich dazu, dass das Programm mit einem segfault abstürzt:int foo = 0; scanf("%d", foo); /* Note missing & sign ; correct usage would have been &foo */
Die Variable
foo
könnte am Speicherort definiert sein1000
, aber der obige Funktionsaufruf würde versuchen, ganzzahlige Daten in den Speicherort zu lesen0
gemäß der Definition vonfoo
. - Ein Segfault tritt auf, wenn ein Programm versucht, an einem Speicherort in einer Weise zu arbeiten, die nicht zulässig ist (z. B. würde der Versuch, einen schreibgeschützten Speicherort zu schreiben, zu einem Segfault führen).
- Segfaults können auch auftreten, wenn Ihr Programm keinen Stapelplatz mehr hat. Dies ist möglicherweise kein Fehler in Ihrem Programm, sondern darauf zurückzuführen, dass Ihre Shell die Stapelgrößenbeschränkung zu klein eingestellt hat.
Array-Referenzen außerhalb der Grenzen finden
Die meisten Fortran-Compiler haben eine Option, die Code einfügt, um die Grenzen aller Array-Referenzen während der Laufzeit zu überprüfen. Wenn ein Zugriff außerhalb des für ein Array definierten Indexbereichs liegt, wird das Programm angehalten und Ihnen mitgeteilt, wo dies geschieht. Für die meisten Fortran-Compiler lautet die Option -C
oder -check
gefolgt von einem Schlüsselwort. Im Benutzerhandbuch Ihres Compilers finden Sie die genaue Option. Verwenden Sie die Begrenzungsprüfung nur beim Debuggen, da dies Ihr Programm verlangsamt. Einige C-Compiler haben auch eine Option zur Überprüfung von Grenzen.
Shell-Limits prüfen
Wie im letzten Beispiel oben erwähnt, sind einige Segfault-Probleme nicht auf Fehler in Ihrem Programm zurückzuführen, sondern werden stattdessen durch zu niedrige Systemspeicherlimits verursacht. Normalerweise ist es die Begrenzung der Stapelgröße, die diese Art von Problem verursacht. Verwenden Sie zum Überprüfen der Speicherbeschränkungen den Befehl ulimit
in bash
oder ksh
oder den Befehl limit
in csh
odertcsh
. Versuchen Sie, die Stacksize höher einzustellen, und führen Sie Ihr Programm erneut aus, um festzustellen, ob der Segfault verschwindet.
Verwenden Sie Debugger, um Segfaults zu diagnostizieren
Wenn Sie das Problem auf andere Weise nicht finden können, können Sie einen Debugger ausprobieren. Zum Beispiel könnten Sie Gnus bekannten Debugger GDB
verwenden, um den Backtrace einer core
-Datei anzuzeigen, die von Ihrem Programm gespeichert wurde; Wenn Programme einen Segfault ausführen, geben sie normalerweise den Inhalt (ihres Abschnitts des) Speichers zum Zeitpunkt des Absturzes in eine core
-Datei aus. Starten Sie Ihren Debugger mit dem Befehl gdb core
und verwenden Sie dann den Befehl backtrace
, um zu sehen, wo sich das Programm beim Absturz befand. Mit diesem einfachen Trick können Sie sich auf diesen Teil des Codes konzentrieren.
Wenn die Verwendung von backtrace
in der core
g-Datei das Problem nicht findet, müssen Sie das Programm möglicherweise unter Debugger-Kontrolle ausführen und dann jeweils eine Funktion oder eine Quellcodezeile durch den Code gehen. Dazu müssen Sie Ihren Code ohne Optimierung und mit dem Flag -g
kompilieren, damit Informationen zu Quellcodezeilen in die ausführbare Datei eingebettet werden. Weitere Informationen finden Sie unter Schritt-für-Schritt-Beispiel für die Verwendung von GDB in Emacs zum Debuggen eines C- oder C ++ – Programms.