Pädagogisches Beispiel, um zu zeigen, dass printf manchmal als Debugging einen Bug verstecken kann

7

Ich erinnere mich, als ich in einem Kurs der C-Programmierung war, hat ein Lehrer einmal vorgeschlagen, dass ich printf verwende, um die Ausführung eines Programms zu beobachten, das ich zu debuggen versuchte. Dieses Programm hatte einen Segmentierungsfehler mit einer Ursache, an die ich mich im Moment nicht erinnern kann. Ich folgte seinem Rat und der Segmentierungsfehler verschwand. Glücklicherweise hat mir ein cleverer TA gesagt, ich solle debuggen, anstatt printf s zu benutzen. In diesem Fall war es eine nützliche Sache zu tun.

Also, heute wollte ich jemandem zeigen, dass die Verwendung von printf möglicherweise einen Bug verstecken könnte, aber ich kann den alten Code, der diesen bizarren Bug hatte (Feature? hmmm) nicht finden.

Frage: Ist jemand von euch auch auf dieses Verhalten gestoßen? Wie könnte ich so etwas reproduzieren?

Bearbeiten:

Ich sehe, dass mein Frageteil meine Meinung zu "using printf is wrong" orientiert. Ich sage das nicht genau und ich mag es nicht, extreme Meinungen zu äußern, also schreibe ich ein bisschen die Frage. Ich stimme zu, dass printf ein gutes Werkzeug ist, aber ich wollte nur einen Fall neu erstellen, in dem printf s einen Segmentierungsfehler verschwinden lässt und daher beweisen, dass man vorsichtig sein muss.

    
YuppieNetworking 24.06.2010, 14:09
quelle

11 Antworten

18

Es gibt Fälle, in denen das Hinzufügen von printf -Aufrufen das Verhalten des Codes ändert, aber es gibt auch Fälle, in denen das Debugging dasselbe tut. Das prominenteste Beispiel ist das Debuggen von Multithread-Code, bei dem das Stoppen der Ausführung eines Threads das Verhalten des Programms ändern kann. Daher kann der Fehler, den Sie suchen, nicht auftreten.

Die Verwendung von printf -Anweisungen hat also berechtigte Gründe. Ob Debug oder printf sollte von Fall zu Fall entschieden werden. Beachten Sie, dass die beiden nicht exklusiv sind - Sie können Code debuggen, selbst wenn er printf Aufrufe enthält: -)

    
Péter Török 24.06.2010, 14:13
quelle
7

Es würde Ihnen sehr schwer fallen, mich davon zu überzeugen, die Protokollierung nicht zu verwenden (und printf ist in dieser Situation eine Hoc-Form der Protokollierung), um zu debuggen. Um einen Absturz zu debuggen, ist es das erste Ziel, ein Backtrace zu erhalten und purify oder ein ähnliches Tool zu verwenden, aber wenn die Ursache nicht offensichtlich ist, ist das Loggen mit Abstand eines der besten Tools, die Sie verwenden können. Ein Debugger ermöglicht es Ihnen, sich auf Details zu konzentrieren, Logging gibt Ihnen ein größeres Bild. Beide sind nützlich.

    
AProgrammer 24.06.2010 14:18
quelle
4

Klingt so, als hätten Sie es mit einem Heisenbug zu tun.

Ich glaube nicht, dass irgendetwas mit der Verwendung von printf als Debugging-Tool von Natur aus "falsch" ist. Aber ja, wie jedes andere Werkzeug hat es seine Fehler, und ja, es gab mehr als eine Gelegenheit, wo das Hinzufügen von printf-Anweisungen einen Heisenbug erzeugte. Allerdings hatte ich auch Heisenbugs als Ergebnis von Speicherlayout Änderungen durch einen Debugger angezeigt, in welchem ​​Fall printf erwies sich als unschätzbar bei der Verfolgung der Schritte, die zum Absturz führen.

    
torak 24.06.2010 14:17
quelle
2

IMHO Jeder Entwickler verlässt sich immer noch hier und da auf Ausdrucke. Wir haben gerade gelernt, sie als "detaillierte Protokolle" zu bezeichnen.

Genauer gesagt, das Hauptproblem, das ich gesehen habe, ist, dass Leute printfs so behandeln, als wären sie unbesiegbar. Zum Beispiel ist es in Java nicht selten, etwas wie

zu sehen %Vor%

Das ist großartig, außer dass z tatsächlich an der Methode beteiligt war, aber das andere Objekt nicht, und es soll sichergestellt werden, dass Sie keine Ausnahme vom Ausdruck auf obj erhalten.

Eine andere Sache, die Ausdrucke tun, ist, dass sie Verzögerungen einführen. Ich habe gesehen, dass Code mit Race Conditions "manchmal behoben" wird, wenn Ausdrucke eingeführt werden. Ich wäre nicht überrascht, wenn ein Code das nutzt.

    
Uri 24.06.2010 14:14
quelle
2

Ich erinnere mich, einmal versucht zu haben, ein Programm auf dem Macintosh (ca. 1991) zu debuggen, bei dem der generierte Cleanup-Code des Compilers für einen Stack-Frame zwischen 32K und 64K fehlerhaft war, weil er einen 16-Bit-Adresszusatz verwendete (Eine 16-Bit-Menge, die einem Adressregister hinzugefügt wird, wird auf dem 68000 vorzeichenerweitert). Die Sequenz war so etwas wie:

%Vor%

Der Nettoeffekt war, dass alles in Ordnung war außer dass die gespeicherten Register beschädigt waren und einer von ihnen eine Konstante enthielt (die Adresse eines globalen Arrays). Wenn der Compiler während eines Codeabschnitts eine Variable für ein Register optimiert, meldet er dies in der Debug-Informationsdatei, damit der Debugger sie korrekt ausgeben kann. Wenn eine Konstante so optimiert ist, enthält der Compiler anscheinend keine solche Information, da dies nicht notwendig sein sollte. Ich habe Dinge aufgespürt, indem ich einen "printf" der Adresse des Arrays gemacht habe, und Haltepunkte gesetzt, damit ich die Adresse vor und nach dem printf sehen konnte. Der Debugger gab die Adresse vor und nach dem Druck korrekt an, aber der Ausdruck gab den falschen Wert aus, also zerlegte ich den Code und sah, dass printf das Register A3 auf den Stapel drückte; Betrachten des Registers A3 vor dem Drucken zeigte, daß es einen Wert hatte, der sich ziemlich von der Adresse des Feldes unterschied (der Druck zeigte den Wert A3, der tatsächlich gehalten wurde).

Ich weiß nicht, wie ich das jemals herausgefunden hätte, wenn ich nicht in der Lage gewesen wäre, sowohl den Debugger als auch printf zusammen zu benutzen (oder, wenn ich 68000 Assemblercode nicht verstanden hätte).

    
supercat 24.06.2010 15:38
quelle
1

Ich habe es geschafft. Ich habe Daten aus einer flachen Datei gelesen. Mein fehlerhafter Algorithmus ging wie folgt:

  1. länge die Eingabedatei in Bytes
  2. weist ein Array variabler Länge von Zeichen zu, das als Puffer dient
    • Die Dateien sind klein, also mache ich mir keine Sorgen wegen des Stack-Überlaufs, aber was ist mit Eingabedateien mit einer Länge von Null? Hoppla!
  3. gibt einen Fehlercode zurück, wenn die Länge der Eingabedatei 0 ist

Ich fand heraus, dass meine Funktion zuverlässig einen seg-Fehler auslösen würde - es sei denn, es gab irgendwo im Hauptteil der Funktion einen Ausdruck, in welchem ​​Fall er genau so funktionieren würde, wie ich es beabsichtigte. Die Fehlerbehebung für den Seg-Fehler bestand darin, die Länge der Datei plus eins in Schritt 2 zuzuweisen.

    
Cyan 27.03.2011 06:21
quelle
1

Ich hatte gerade eine ähnliche Erfahrung. Hier ist mein spezifisches Problem und die Ursache:

%Vor%

Das Problem liegt in der Schleifenbedingung - ich habe '\ n' anstelle des Nullzeichens '\ 0' verwendet. Nun, ich weiß nicht genau, wie printf funktioniert, aber aufgrund dieser Erfahrung vermute ich, dass es einen Speicherplatz nach meinen Variablen als temporären / Arbeitsraum verwendet. Wenn eine printf-Anweisung dazu führt, dass ein '\ n'-Zeichen an einer Stelle geschrieben wird, an der mein Wort gespeichert ist, kann die FixCap-Funktion an einem bestimmten Punkt anhalten. Wenn ich printf entferne, wird es weitergeschleift und nach einem '\ n' gesucht, aber nie gefunden, bis es defaults ist.

Am Ende ist die Ursache meines Problems, dass ich manchmal '\ n' eintippe, wenn ich '\ 0' meine. Es ist ein Fehler, den ich vorher gemacht habe, und wahrscheinlich einen, den ich wieder machen werde. Aber jetzt weiß ich, um danach zu suchen.

    
Anthony Deschamps 20.02.2012 18:13
quelle
0

Nun, vielleicht könnten Sie ihm beibringen, wie man gdb oder andere Debugging-Programme benutzt? Sag ihm, wenn ein Bug dank eines "printf" verschwindet, dann verschwindet er nicht wirklich und könnte später wieder erscheinen. Ein Fehler sollte behoben werden, nicht ignoriert werden.

    
Pikrass 24.06.2010 14:14
quelle
0

Damit erhalten Sie beim Entfernen der Printf-Zeile eine Division durch 0:

%Vor%     
Andy 24.06.2010 14:23
quelle
0

Was wäre der Debugging-Fall? Drucken eines char *[] -Arrays vor dem Aufruf von exec() , nur um zu sehen, wie es in Token umgewandelt wurde - ich denke, das ist eine ziemlich sinnvolle Verwendung für printf() .

Wenn jedoch das an printf() zugeführte Format ausreichend teuer und komplex ist, so dass es tatsächlich die Programmausführung ändern kann (meistens Geschwindigkeit), ist ein Debugger möglicherweise der bessere Weg. Aber auch Debugger und Profiler haben ihren Preis. Beide können Rassen aufdecken, die in ihrer Abwesenheit nicht auftauchen.

Es hängt alles davon ab, was du schreibst und den Bug, den du verfolgst. Die verfügbaren Tools sind Debugger, printf() (Gruppierung von Loggern in printf) sowie Assertions und Profiler.

Ist ein Klingenschraubendreher besser als andere? Hängt davon ab, was Sie brauchen. Beachte, ich sage nicht, dass Behauptungen gut oder schlecht sind. Sie sind nur ein weiteres Werkzeug.

    
Tim Post 24.06.2010 14:42
quelle
0

Eine Möglichkeit, damit umzugehen, besteht darin, ein System von Makros einzurichten, das es einfach macht, printfs zu deaktivieren, ohne sie in Ihrem Code löschen zu müssen. Ich benutze so etwas:

%Vor%

logging_messagef() ist eine Funktion, die in einer separaten .c -Datei definiert ist. Verwenden Sie die XMESSAGE (...) Makros in Ihrem Code abhängig von dem Zweck der Nachricht. Das Beste an diesem Setup ist, dass es gleichzeitig für Debugging und Logging funktioniert, und die Funktion logging_messagef() kann geändert werden, um verschiedene Dinge zu tun (printf in stderr, in eine Logdatei, syslog oder eine andere Systemprotokollierung) Einrichtung, usw.) und Nachrichten unterhalb einer bestimmten Stufe können in logging_messagef() ignoriert werden, wenn Sie sie nicht benötigen. PV_DBGMESSAGE() ist für die umfangreichen Debug-Nachrichten, die Sie in der Produktion sicher ausschalten wollen.

    
Tim Schaeffer 24.06.2010 15:02
quelle