Behebung von Segmentierungsfehlern in C ++

90

Ich schreibe ein plattformübergreifendes C ++ - Programm für Windows und Unix. Auf der Fensterseite wird der Code problemlos kompiliert und ausgeführt. Auf der Unix-Seite wird es jedoch kompiliert, wenn ich versuche, es auszuführen, wird ein Segmentierungsfehler angezeigt. Meine anfängliche Vermutung ist, dass es ein Problem mit Zeigern gibt.

Was sind gute Methoden, um Segmentierungsfehler zu finden und zu beheben?

Elpezmuerto
quelle

Antworten:

130
  1. Kompilieren Sie Ihre Anwendung mit -g, dann haben Sie Debug-Symbole in der Binärdatei.

  2. Verwenden Sie gdbdiese Option, um die GDB-Konsole zu öffnen.

  3. Verwenden fileSie die Binärdatei Ihrer Anwendung und übergeben Sie sie in der Konsole.

  4. Verwenden Sie runalle Argumente, die Ihre Anwendung zum Starten benötigt, und übergeben Sie sie.

  5. Tun Sie etwas, um einen Segmentierungsfehler zu verursachen .

  6. Geben Sie btdie gdbKonsole ein, um eine Stapelverfolgung des Segmentierungsfehlers abzurufen .

Svisstack
quelle
Was bedeutet es, es gim Kontext von kompilieren zu lassen CMake?
Schütze
1
Aktivieren Sie den Debug-Build-Typ. Ein Weg ist cmake -DCMAKE_BUILD_TYPE=Debug.
Antonin Décimo
36

Manchmal ist der Absturz selbst nicht die eigentliche Ursache des Problems - vielleicht wurde der Speicher zu einem früheren Zeitpunkt zerstört, aber es dauerte eine Weile, bis sich die Korruption zeigte. Schauen Sie sich valgrind an , das viele Überprüfungen auf Zeigerprobleme enthält (einschließlich der Überprüfung der Arraygrenzen). Hier erfahren Sie, wo das Problem beginnt und nicht nur in welcher Zeile der Absturz auftritt.

Paläozogt
quelle
19

Versuchen Sie, das Problem so weit wie möglich zu vermeiden, bevor es auftritt:

  • Kompilieren Sie Ihren Code und führen Sie ihn so oft wie möglich aus. Es ist einfacher, das fehlerhafte Teil zu finden.
  • Versuchen Sie, Routinen auf niedriger Ebene / fehleranfällig zu kapseln, damit Sie selten direkt mit dem Speicher arbeiten müssen (achten Sie auf die Modellierung Ihres Programms).
  • Pflegen Sie eine Testsuite. Wenn Sie einen Überblick darüber haben, was gerade funktioniert, was nicht mehr funktioniert usw., können Sie herausfinden, wo das Problem liegt ( Boost-Test ist eine mögliche Lösung, ich verwende ihn nicht selbst, aber die Dokumentation kann helfen, zu verstehen, welche Art von Problem vorliegt Informationen müssen angezeigt werden).

Verwenden Sie zum Debuggen geeignete Tools. Unter Unix:

  • GDB kann Ihnen sagen, wo Sie einen Programmabsturz haben, und lässt Sie sehen, in welchem ​​Kontext.
  • Valgrind hilft Ihnen dabei, viele speicherbezogene Fehler zu erkennen.
  • Mit GCC können Sie auch Schmutzfänger verwenden. Mit GCC, Clang und seit Oktober experimentell MSVC können Sie Address / Memory Sanitizer verwenden . Es kann einige Fehler erkennen, die Valgrind nicht erkennt, und der Leistungsverlust ist geringer. Es wird durch Kompilieren mit dem -fsanitize=addressFlag verwendet.

Zum Schluss würde ich die üblichen Dinge empfehlen. Je besser Ihr Programm lesbar, wartbar, klar und ordentlich ist, desto einfacher ist das Debuggen.

log0
quelle
5

Unter Unix können valgrindSie Probleme finden. Es ist kostenlos und mächtig. Wenn Sie es lieber selbst tun möchten, können Sie die Operatoren newund überladen delete, um eine Konfiguration einzurichten, bei der Sie 0xDEADBEEFvor und nach jedem neuen Objekt 1 Byte haben . Verfolgen Sie dann, was bei jeder Iteration passiert. Dies kann nicht alles erfassen (es ist nicht garantiert, dass Sie diese Bytes berühren), aber es hat in der Vergangenheit auf einer Windows-Plattform für mich funktioniert.

Wheaties
quelle
1
Nun, das wären 4 Bytes statt 1 ... aber das Prinzip ist in Ordnung.
Jonas Wagner
1
Darf ich einen Link zu meinem nicht aufdringlichen Heap-Debugger erstellen ? :-)
Fredoverflow
Tue es. Es geht uns darum, anderen hier zu helfen, daher sollte alles hinzugefügt werden, was helfen kann.
Wheaties
Obwohl Überladen newund deletesehr nützlich sein kann, ist die Verwendung -fsanitize=addresseine bessere Option, da der Compiler bei der Laufzeiterkennung nach Problemen kompiliert und den Speicher automatisch auf dem Bildschirm ablegt, was das Debuggen erleichtert.
Tarick Welling
3

Ja, es gibt ein Problem mit Zeigern. Sehr wahrscheinlich verwenden Sie eine, die nicht richtig initialisiert wurde, aber es ist auch möglich, dass Sie Ihre Speicherverwaltung mit doppelten Freigaben oder Ähnlichem durcheinander bringen.

Um nicht initialisierte Zeiger als lokale Variablen zu vermeiden, versuchen Sie, sie so spät wie möglich zu deklarieren, vorzugsweise (und dies ist nicht immer möglich), wenn sie mit einem aussagekräftigen Wert initialisiert werden können. Überzeugen Sie sich selbst, dass sie einen Wert haben, bevor sie verwendet werden, indem Sie den Code untersuchen. Wenn Sie damit Schwierigkeiten haben, initialisieren Sie sie mit einer Nullzeigerkonstante (normalerweise als NULLoder geschrieben 0) und überprüfen Sie sie.

Um nicht initialisierte Zeiger als Elementwerte zu vermeiden, stellen Sie sicher, dass sie im Konstruktor ordnungsgemäß initialisiert und in Kopierkonstruktoren und Zuweisungsoperatoren ordnungsgemäß behandelt werden. Verlassen Sie sich nicht auf eineinit bei der Speicherverwaltung Funktion, auch bei anderen Initialisierungen.

Wenn Ihre Klasse keine Kopierkonstruktoren oder Zuweisungsoperatoren benötigt, können Sie diese als private Elementfunktionen deklarieren und niemals definieren. Dies führt zu einem Compilerfehler, wenn diese explizit oder implizit verwendet werden.

Verwenden Sie gegebenenfalls intelligente Zeiger. Der große Vorteil dabei ist, dass Sie das Schreiben vollständig vermeiden können, wenn Sie sich an sie halten und sie konsequent verwenden, deleteund nichts wird doppelt gelöscht.

Verwenden Sie nach Möglichkeit C ++ - Zeichenfolgen und Containerklassen anstelle von Zeichenfolgen und Arrays im C-Stil. Verwenden Sie .at(i)lieber als [i], da dies die Überprüfung der Grenzen erzwingt. Überprüfen Sie, ob Ihr Compiler oder Ihre Bibliothek so eingestellt werden kann, dass die Grenzen überprüft werden[i] zumindest im Debug-Modus werden. Segmentierungsfehler können durch Pufferüberläufe verursacht werden, die Müll über einwandfreie Zeiger schreiben.

Dadurch wird die Wahrscheinlichkeit von Segmentierungsfehlern und anderen Speicherproblemen erheblich verringert. Sie werden zweifellos nicht in der Lage sein, alles zu reparieren, und deshalb sollten Sie ab und zu Valgrind verwenden, wenn Sie keine Probleme haben, und Valgrind und GDB, wenn Sie dies tun.

David Thornley
quelle
1

Ich kenne keine Methode, um solche Probleme zu beheben. Ich denke nicht, dass es möglich wäre, eine zu finden, denn das eigentliche Problem ist, dass das Verhalten Ihres Programms undefiniert ist (ich kenne keinen Fall, in dem SEGFAULT nicht durch irgendeine Art von UB verursacht wurde). .

Es gibt alle Arten von "Methoden", um das Problem zu vermeiden, bevor es auftritt. Ein wichtiger ist RAII.

Außerdem musst du nur deine besten psychischen Energien darauf werfen.

Edward Strange
quelle