Debug-kompilierte ausführbare Datei:Warum nicht bei ungültigem Schreiben in NULL ordnungsgemäß abbrechen?

StackOverflow https://stackoverflow.com/questions/550106

Frage

Was ich an C/C++ nicht verstehe, ist:

Ja, jeder nutzt es, um rasend schnelle ausführbare Dateien zu erhalten, die dann mit aktivierter Optimierung kompiliert werden.

Bei der Kompilierung mit aktivierten Debug-Informationen ist uns die Geschwindigkeit jedoch egal.Warum also nicht mehr Informationen in diesen Kompilierungsmodus einbeziehen, um beispielsweise einige Segfaults zu erkennen, bevor sie auftreten?Fügen Sie effektiv eine ein assert(ptr != NULL) vor jedem Zugriff auf einen Zeiger ptr.Warum kann der Compiler das nicht?Auch das sollte standardmäßig deaktiviert sein, aber es sollte meiner Meinung nach eine solche Möglichkeit geben.

BEARBEITEN: Einige Leute sagten, dass die von mir vorgeschlagene Erkennung keinen Sinn ergibt oder nichts bewirkt, was im Bericht beschrieben wird segmentation fault würde es nicht schon tun.Aber was ich im Sinn habe, ist einfach ein eleganterer und informativerer Abbruch, der den Dateinamen und die Zeilennummer des fehlerhaften Codes ausgibt, genau wie ein assert() würdest du.

War es hilfreich?

Lösung

Es gibt ein paar großen Probleme mit Ihrem Vorschlag:

Welche Bedingungen wollen Sie den Compiler zu erkennen? Unter Linux / x86, kann nicht ausgerichteten Zugriff verursachen SIGBUS und Stapelüberlauf kann SIGSEGV verursachen, aber in beiden Fällen ist es technisch möglich, die Anwendung zu schreiben, diese Bedingungen und fail „graziös“ zu erkennen. NULL Zeiger Kontrollen festgestellt werden kann, aber die heimtückischsten Fehler sind durch Zeiger Zugriff auf ungültig, anstatt NULL Zeiger.

Die C- und C ++ Programmiersprachen bieten genügend Flexibilität, so dass es unmöglich ist, für eine Laufzeit mit 100% Erfolg, um zu bestimmen, ob eine gegebene Zufallsadresse ein gültiger Zeiger eines beliebigen Typs ist.

Was möchten Sie die Laufzeitumgebung zu tun, wenn es einen solchen Fall erkennt? Es kann nicht das Verhalten korrigieren (es sei denn, Sie an Magie glauben). Es kann nur die Ausführung oder den Ausgang weiter. Aber Moment mal ... das ist, was bereits passiert, wenn ein Signal geliefert wird! Das Programm verläßt, eine Core-Dump erzeugt wird, und dass Core-Dump kann von Anwendungsentwicklern verwendet werden, um den Zustand des Programms, um zu bestimmen, wenn es abgestürzt ist.

Was Sie befürworten klingt tatsächlich wie Sie Ihre Anwendung in einem Debugger (gdb) oder durch irgendeine Form der Virtualisierung (valgrind) ausgeführt werden soll. Das ist schon möglich, aber es macht keinen Sinn, es ist standardmäßig zu tun, weil sie keinen Nutzen für Nicht-Entwickler zur Verfügung stellen.

Update auf Kommentare zu antworten:

Es gibt keinen Grund, den Übersetzungsvorgang für Debug-Version zu ändern. Wenn Sie eine „sanfte“ Debug-Version der Anwendung benötigen, sollten Sie es innerhalb eines Debuggers laufen. Es ist sehr einfach die ausführbare Datei in einem Skript zu wickeln, dass diese transparent für Sie.

Andere Tipps

Was muss in diesem Fall das Programm tun? Wenn es den Benutzer über einen Fehler informiert, dann ist das, was der segfault der Fall ist.

Wenn es soll gehen halten und um den Fehler zu vermeiden, wie sie weiß, was? Tut

Ganz zu schweigen davon, dass, wenn es irgendwie magisch wußte, wie man richtig weiter, dann Sie einen Fehler in der Release-Build haben (debug soll baut man erkennen und Fehler beheben helfen - nicht versteckt sie).


Als Reaktion auf die zusätzliche Informationen zu der Frage hinzugefügt (Ich glaube, ich Ihre Absicht falsch verstanden):

  

, was ich im Sinne habe, ist nur ein anmutiger und informativer Abbruch, die den Namen Datei druckt und die Zeilennummer des betreffenden Code, wie ein assert () tun würde.

Das ist etwas, der Compiler tun könnte - wie Sie sagen, würde der Compiler im Wesentlichen automatisch eine assert() überall ein Zeiger dereferenziert wurde eingeführt wird. Dies könnte ziemlich deutlich auf die Größe eines Debug-Build hinzuzufügen, aber es wäre wahrscheinlich noch für viele akzeptabel sein (oder die meisten) Zwecke. Ich denke, dies ist eine vernünftige Option für einen Compiler wäre zu liefern.

Ich bin nicht sicher, was Compiler-Anbieter sagen würde ... Vielleicht eine Anfrage veröffentlicht auf Microsoft Connect-Website für die VC ++ Produkt und sehen, was sie sagen.

ich mit Michael Burr einig, dass dies nicht wirklich tun oder etwas helfen.

Darüber hinaus würde dies noch nicht für baumelnden Zeiger arbeitet, die weit mehr heimtückisch und schwer aufzuspüren als Null-Zeiger zu neigen.

Mindestens mit Null-Zeiger ist es einfach genug, um sie gültig sind, um sicherzustellen, bevor Sie sie deref.

ich glaube, das ursprüngliche Plakat die App will im Debugger zu stoppen. Sie würden den Zugang zu allen von den Stack-Variablen haben und stapeln, so würden Sie eine Chance haben, um herauszufinden, warum Ihr Programm in diesem Zustand befindet.

Wenn Sie in C / C ++ entwickeln, kann ein Debug-Speichermanager Ihnen eine Menge Zeit sparen. Pufferüberläufe, Zugreifen gelöscht Speicher, Speicherlecks, und so weiter sind ziemlich leicht zu finden und zu beheben. Es gibt mehr auf dem Markt oder Sie können 2 oder 3 Tage verbringen Sie Ihre eigenen zu schreiben und zu 90% der benötigten Funktionalität zu erhalten. Wenn Sie Anwendungen, ohne sie zu schreiben, machen Sie Ihren Job viel schwieriger, als es sein muss.

Es gibt noch einen weiteren einfachen Grund assert(ptr != NULL) funktioniert nicht, bevor die Dereferenzierung eines Zeigers nicht funktioniert:Nicht jeder ungültige Zeiger (auch diejenigen, die als NULL begonnen haben) ist tatsächlich gleich 0.

Betrachten Sie zunächst den Fall, dass Sie eine Struktur mit mehreren Mitgliedern haben:

struct mystruct {
    int first;
    int second;
    int third;
    int fourth;
};

Wenn Sie einen Zeiger haben ptr Zu mystruct und Sie versuchen, darauf zuzugreifen ptr->second, generiert der Compiler Code, der 4 hinzufügt (unter der Annahme von 32-Bit-Ganzzahlen). ptr und auf diesen Speicherort zugreifen.Wenn ptr 0 ist, ist der tatsächliche Speicherort, auf den zugegriffen wird, 4.Das ist immer noch ungültig, würde aber nicht durch eine einfache Behauptung erfasst werden.(Vom Compiler könnte vernünftigerweise erwartet werden, dass er die Adresse von überprüft ptr bevor 4 hinzugefügt wird. In diesem Fall würde die Behauptung es abfangen.)

Betrachten Sie zweitens den Fall, in dem Sie über ein Array von verfügen struct mystruct und Sie übergeben ein beliebiges Element an eine andere Funktion.Wenn Sie versuchen, auf das zweite Element des Arrays zuzugreifen, beginnt es 16 Bytes hinter dem ersten Zeiger.Man kann vernünftigerweise nicht erwarten, dass der Compiler in allen Fällen zuverlässig das tut, was Sie wollen, ohne legitime Zeigerarithmetik abzufangen.

Was Sie wirklich tun möchten, ist, das Betriebssystem und die Hardware zu nutzen, um ungültige und nicht ausgerichtete Speicherzugriffe abzufangen und Ihre Anwendung zu beenden, und dann herauszufinden, wie Sie an die benötigten Debugging-Informationen gelangen.Der einfachste Weg besteht darin, einfach einen Debugger auszuführen.Wenn Sie gcc unter Linux verwenden, lesen Sie So generieren Sie einen Stacktace, wenn meine C++-App abstürzt.Ich gehe davon aus, dass es ähnliche Möglichkeiten gibt, dasselbe mit anderen Compilern zu tun.

Es ist bereits das Äquivalent eines assert (prt! = NULL) als Teil des Betriebssystems. Deshalb sollten Sie eine segfault bekommen, anstatt nur wichtig, wichtige Daten an der Adresse 0 überschrieben wird und dann das System wirklich vermasselt.

Da Sie eine Symboldatei für die ausführbare Datei haben, ist es möglich, den Ort des Absturzes zu einer Zeilennummer zuzuordnen. Der Debugger tut dies für Sie, wenn Sie es im Debugger ausgeführt werden, wie andere erwähnt haben. Visual C ++ bietet sogar „just-in-time“ Debugging, wo, wann das Programm abstürzt, können Sie einen Debugger an den abgestürzten Prozess anhängen, um zu sehen, wo das Problem ist.

Wenn Sie jedoch diese Funktionalität auf Maschinen haben wollen, auf den Visual C ++ nicht installiert ist, ist es immer noch möglich, es mit einiger Codierung zu tun. Sie können einen Exception-Handler mit SetUnhandledExceptionFilter einrichten, die aufgerufen wird, wenn das Programm abstürzt. Im Handler können Sie auf dem Ausnahmedatensatz suchen und SymGetLineFromAddr64 verwenden, um festzustellen, welche Source-Leitung, die Ausführung wurde. Es gibt viele Funktionen in der „Debug-Hilfe“ Bibliothek, die Sie alle Arten von Informationen können extrahiert werden. Siehe die Artikel auf MSDN , und auch die Artikel über www.debuginfo.com .

Sie sagen also, dass, bevor das System einen Fehler wirft, sollte es einen Fehler werfen .... warnen Sie vor dem bevorstehenden Fehler?

Was hat den Punkt sein? Wenn ich eine segfault bekommen, ich weiß, es bedeutet, dass ich ein segfault bekam. Ich brauche keine separate Nachricht zuerst sagen: „Sie werden jetzt eine segfault bekommen“.

Fehle ich völlig den Punkt hier? : P

Edit: Ich sehe, was Sie in Ihrem bearbeiten bedeuten, aber es ist nicht einfach zu implementieren. Das Problem ist, dass es nicht der Compiler oder die Sprache oder die Laufzeit, die entscheidet, was passieren soll, wenn Sie einen schlechten Zeiger zuzugreifen. Die Sprache macht offiziell keine Versprechen oder Garantien darüber. Stattdessen wird die OS feuern einen Fehler, ohne zu wissen, dass dies ein Debug ausführbar, ohne zu wissen, welche Zeilennummer ausgelöst, das Problem, oder irgendetwas anderes. Das einzige, was dieser Fehler sagt, ist „Sie versucht, Adresse X zugreifen zu können, und das kann ich nicht zulassen. Die“. Was soll der Compiler damit?

Also, wer sollte diese hilfreiche Fehlermeldung generieren? Und wie? Der Compiler könnte es tun, sicher, aber Einwickeln alle einziger Zeiger Zugriff auf Fehlerbehandlung, um sicherzustellen, wenn eine segfault / Zugriffsverletzung auftritt, wir fangen und stattdessen eine Assertion auslösen. Das Problem ist, das wäre unglaublich langsam . Nicht nur „zu langsam für die Freigabe“, sondern „zu langsam verwendbar zu sein“. Es geht auch davon aus, dass der Compiler hat Zugriff auf alle Code, den Sie rufen in. Was passiert, wenn Sie eine Funktion in einem Dritt Bibliothek nennen? Pointer greift im Innern, das nicht in der Fehlerbehandlung Code gewickelt werden kann, da der Compiler erzeugt keinen Code für diese Bibliothek.

Das O könnte es tun, vorausgesetzt, es war bereit, / die Lage, die entsprechenden Symboldateien zu laden, irgendwie erkennen, ob Sie eine Debug ausführbar laufen und so weiter ... Genau so ist es ausdrucken kann eine Zeilennummer. Sprechen Sie über die Overengineering. Das ist kaum die Aufgabe des OS.

Und schließlich, was würden Sie gewinnen, indem Sie diese? Warum nicht einfach Ihre Debugger anwerfen? Es automatisch bricht, wenn so etwas passiert, Sie genaue Zeilennummer und alles was sonst.

Es könnte getan werden, aber es wäre furchtbar kompliziert sein, und sowohl die Compiler und das Betriebssystem beinhaltet, und der Nutzen wäre sehr gering sein. Sie würden ein Popup-Fenster erhalten Sie Informationen zu sagen, dass Ihr Debugger bereits in der Lage ist, Ihnen zu sagen. Und mit dieser Information, würden Sie dann ... starten Sie Ihren Debugger sowieso , um herauszufinden, was falsch gelaufen ist.

Gute Idee, aber nur in einem Fall. Das heißt, bevor Sie einen funciton Zeiger sind dereferencing. Der Grund dafür ist, dass der Debugger immer in kicken, aber nach einem Null-Funktionszeiger dereferencing der Stapel Schuss. Daher haben Sie Probleme, die den fehlerhaften Code zu finden. Wenn der Anrufer vor dem Aufruf überprüft, würde der Debugger können Sie einen vollständigen Stapel geben.

Eine allgemeine Kontrolle wäre, wenn der Zeiger auf Speicher, um zu sehen, die ausführbar ist. NULL ist nicht, aber viele Betriebssysteme können auch CPU-Funktionen verwenden, um bestimmte Speichersegmente nicht ausführbar zu machen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top