Frage

Ich erinnere mich, als ich in einem Kurs der C-Programmierung war, ein Lehrer einmal vorgeschlagen, dass ich printf verwenden, um die Ausführung eines Programms zu sehen, dass ich zu debuggen versuche. Dieses Programm hatte einen Segmentierungsfehler mit einer Ursache, dass ich im Moment nicht erinnern kann. Ich folgte seinem Rat und die Segmentierungsfehler verschwunden. Glücklicherweise sagte ein kluger TA mir statt mit printfs zu debuggen. In diesem Fall war es eine nützliche Sache zu tun.

So, heute wollte ich jemanden zeigen, dass printf mit möglicherweise einem Fehler verbergen konnte, aber ich kann nicht, dass die alten Code finden, die diese bizarren Fehler hatten (Feature? Hmmm).

Frage: Hat jemand von euch dieses Verhalten auch auftreten? Wie könnte ich so etwas wie dies reproduzieren?

Edit:

Ich sehe, dass meine Frage Teil meiner Meinung nach ausrichtet, um „mit printf falsch ist“. Ich bin nicht genau sagen, und ich weiß nicht wie extreme Meinungen nehmen, so dass ich ein bisschen die Frage bin die Bearbeitung. Ich bin damit einverstanden, dass printf ist ein gutes Werkzeug, aber ich wollte nur einen Fall erstellen, wo printfs einen Segmentierungsfehler verschwinden zu lassen und damit beweisen, dass man vorsichtig sein muss.

War es hilfreich?

Lösung

Es gibt Fälle, wenn das Hinzufügen printf Abspaltungen ruft das Verhalten des Codes, aber es gibt auch Fälle, in denen das Debuggen das gleiche tut. Das prominenteste Beispiel ist das Debuggen multithreaded Code, in dem Anhalten der Ausführung eines Threads das Verhalten des Programms verändern kann, so dass der Fehler die Sie suchen kann nicht passieren.

So printf-Anweisungen hat gute Gründe. Ob zu debuggen oder printf sollte auf einem von Fall zu Fall entschieden werden. Beachten Sie, dass die beiden nicht exklusiv sowieso - Sie können Debug-Code, auch wenn es printf Anrufe enthält: -)

Andere Tipps

Sie würden eine sehr harte Zeit haben, um mich zu überzeugen nicht Protokollierung zu verwenden (und printf in dieser Situation ist eine hatte hoc Form von Logging) zu debuggen. Offensichtlich einen Absturz zu debuggen, die ersten Dinge, ist eine Rückverfolgung und Verwendung reinigen oder ein ähnliches Werkzeug zu bekommen, aber wenn die Ursache nicht offensichtlich ist die Protokollierung ist mit Abstand eines der besten Werkzeug können Sie verwenden. Ein Debugger ermöglicht es Ihnen, auf Details zu konzentrieren, Protokollierung gibt Ihnen ein größeres Bild. Beide sind nützlich.

Sounds wie man es zu tun mit einem Heisenbug .

Ich glaube nicht, dass es etwas von Natur aus „falsch“ mit dem Einsatz von printf als Debugging-Tool. Aber ja, wie jedes andere Werkzeug, es hat seine Fehler, und ja, es war mehr als ein occaision wo die Zugabe von printf Aussagen eine Heisenbug erstellt. Allerdings hat ich auch zeigen habe heisenbugs als Folge des Speicherlayouts Wechsel von einem Debugger eingeführt, wobei in diesem Fall printf von unschätzbarem Wert erwiesen, die Schritte bei der Verfolgung, dass führt zum Absturz bringen.

IMHO Jeder Entwickler verlässt sich immer noch hier und da auf den Ausdrucken. Wir haben gerade gelernt, sich „detaillierte Protokolle“ zu nennen.

Mehr zu dem Punkt, ist das Hauptproblem, dass ich gesehen habe, ist, dass die Menschen behandeln printfs wie sie sind unbesiegbar. Zum Beispiel ist es in Java nicht selten so etwas wie

zu sehen
System.out.println("The value of z is " + z + " while " + obj.someMethod().someOtherMethod());

Das ist großartig, mit der Ausnahme, dass z tatsächlich in dem Verfahren beteiligt war, sondern dass andere Aufgabe war es nicht, und es gibt, um sicherzustellen, werden Sie nicht eine Ausnahme von dem Ausdruck auf obj erhalten.

Eine andere Sache, dass Ausdrucke zu tun ist, dass sie Verzögerungen einführen. Ich habe manchmal gesehen Code mit Race Conditions „repariert“, wenn Ausdrucke eingeführt werden. Ich würde nicht, wenn einige Code-Anwendungen überrascht sein, dass.

Ich erinnere mich, einmal ein Programm auf dem Macintosh zu debuggen versuchen (circa 1991), wo die generierten Compiler Bereinigungscode für einen Stack-Frame zwischen 32K und 64K fehlerhaft war, weil es eine 16-Bit-Adresse zusätzlich verwendet anstelle eines 32-Bit ein (eine 16-Bit-Menge hinzugefügt, um ein Adressenregister wird auf den 68000 vorzeichenerweitert wird). Die Folge war so etwas wie:

  copy stack pointer to some register
  push some other registers on stack
  subtract about 40960 from stack pointer
  do some stuff which leaves saved stack-pointer register alone
  add -8192 (signed interpretation of 0xA000) to stack pointer
  pop registers
  reload stack pointer from that other register

Der Nettoeffekt war, dass alles in Ordnung war außer , dass die gesicherten Register beschädigt wurden, und einer von ihnen hielt eine Konstante (die Adresse eines globalen Array). Wenn der Compiler eine Variable in ein Register während eines Codeabschnittes optimiert, meldet es, dass in der Debug-Informationsdatei so der Debugger korrekt ausgegeben, es kann. Wenn eine Konstante so optimiert, wird der Compiler offenbar solche Informationen nicht enthalten, da sollte es keine Notwendigkeit. Ich verfolgte die Dinge nach unten durch einen „printf“ der Adresse des Arrays zu tun, und setzen Sie Breakpoints, damit ich die Adresse vor und nach der printf sehen konnte. Der Debugger berichtete korrekt die Adresse vor und nach dem printf, aber die printf dem falschen Wert ausgegeben, so dass ich den Code und die Sägen zerlegt, dass printf Register A3 auf den Stapel schiebt; Sichtregister A3 vor dem printf zeigte, daß es von der Adresse des Arrays einen Wert etwas anders war (zeigte der printf den Wert A3 tatsächlich gehalten wird).

Ich weiß nicht, wie ich jemals, dass man aufgespürt hätte, wenn ich hatte sowohl den Debugger und printf zusammen verwenden nicht in der Lage (oder, was das betrifft, wenn ich nicht 68000 Assembler-Code verstanden hatte).

ich es geschafft, dies zu tun. Ich war Daten in aus einer flachen Datei zu lesen. Mein fehlerhafter Algorithmus ging wie folgt:

  1. get Länge von Eingabedatei in Bytes
  2. zuzuteilen ein variabler Länge Array von Zeichen als Puffer zu dienen,
    • die Dateien sind klein, so dass ich über Stapelüberlauf nicht besorgt bin, aber was ist mit Null-Länge-Eingabedateien? oops!
  3. Rückkehr einen Fehlercode Eingangsdateilänge 0

Ich fand, dass meine Funktion zuverlässig einen seg Fehler werfen würde - es sei denn, ein printf irgendwo im Körper der Funktion war, in diesem Fall ist es genau funktionieren würde, wie ich gedacht. Das Update für den seg Fehler war es, die Länge der Datei plus eins in Schritt 2.

zuzuteilen

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

// Makes the first character of a word capital, and the rest small
// (Must be compiled with -std=c99)
void FixCap( char *word )
{
  *word = toupper( *word );
  for( int i=1 ; *(word+i) != '\n' ; ++i )
    *(word+i) = tolower( *(word+i) );
}

Das Problem ist mit der Schleifenbedingung - I verwendet ‚\ n‘ anstelle der Nullzeichen ‚\ 0‘. Nun, ich weiß nicht genau, wie printf funktioniert, aber aus dieser Erfahrung Ich vermute, dass es etwas Speicherplatz nach meinen Variablen als temporärer / Arbeitsraum verwendet. Wenn ein printf-Anweisung führt zu einem ‚\ n‘ Zeichen an einer Stelle geschrieben wird, nachdem wo mein Wort gespeichert ist, dann wird die FixCap Funktion der Lage sein, an einem gewissen Punkt zu stoppen. Wenn ich die printf entfernen, dann hält sie auf Looping, die Suche nach einem ‚\ n‘, aber nie, es zu finden, bis es Segfaults.

Also am Ende, die Ursache für mein Problem ist, dass ich ‚\ n‘ manchmal geben, wenn ich meine ‚\ 0‘. Es ist ein Fehler, den ich vorher gemacht habe, und wahrscheinlich eine werde ich wieder machen. Aber jetzt weiß ich es zu suchen.

Nun, vielleicht könnten Sie ihm beibringen, wie GDB oder andere Debugging-Programme benutzen? Sagen Sie ihm, dass, wenn ein Fehler juste dank einer „printf“ verschwinden, dann hat es nicht wirklich verschwinden und wieder erscheinen letztere könnte. Ein Fehler wird behoben werden soll, nicht ignoriert.

Dies wird Ihnen eine Division durch 0, wenn die printf Zeile entfernen:

int a=10;
int b=0;
float c = 0.0;

int CalculateB()
{
  b=2;
  return b;
}
float CalculateC()
{
  return a*1.0/b;
}
void Process()
{
  printf("%d", CalculateB()); // without this, b remains 0
  c = CalculateC();
}

Was wäre der Debuggen Fall sein? Drucken eines char *[] Array vor dem Aufruf exec() nur um zu sehen, wie es in Token aufgeteilt wurde. - Ich denke, das ist eine ziemlich gültige Verwendung für printf()

Wenn jedoch das Format printf() gespeist ist von ausreichend Kosten und Komplexität, dass es tatsächlich alte Programmausführung (Geschwindigkeit, meistens), ein Debugger der bessere Weg sein kann, um zu gehen. Dann wieder, Debugger und Profiler auch zu Kosten kommen. Entweder man kann Rennen aussetzen, die nicht in ihrer Abwesenheit der Oberfläche könnte.

Es hängt alles davon ab, was Sie schreiben und die Fehler, den Sie jagen. Die Werkzeuge zur Verfügung stehen Debugger, printf() Behauptungen und Profilometer (WBV in printf als auch die Gruppierung).

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

Ein Weg, damit umzugehen ist, ein System von Makros einzurichten, die es einfach macht printfs auszuschalten w / o mit ihnen in Ihrem Code zu löschen. Ich benutze so etwas wie folgt aus:

#define LOGMESSAGE(LEVEL, ...) logging_messagef(LEVEL, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__);

/* Generally speaking, user code should only use these macros.  They
 * are pithy. You can use them like a printf:
 *
 *    DBGMESSAGE("%f%% chance of fnords for the next %d days.", fnordProb, days);
 *
 * You don't need to put newlines in them; the logging functions will
 * do that when appropriate.
 */
#define FATALMESSAGE(...) LOGMESSAGE(LOG_FATAL, __VA_ARGS__);
#define EMERGMESSAGE(...) LOGMESSAGE(LOG_EMERG, __VA_ARGS__);
#define ALERTMESSAGE(...) LOGMESSAGE(LOG_ALERT, __VA_ARGS__);
#define CRITMESSAGE(...) LOGMESSAGE(LOG_CRIT, __VA_ARGS__);
#define ERRMESSAGE(...) LOGMESSAGE(LOG_ERR, __VA_ARGS__);
#define WARNMESSAGE(...) LOGMESSAGE(LOG_WARNING, __VA_ARGS__);
#define NOTICEMESSAGE(...) LOGMESSAGE(LOG_NOTICE, __VA_ARGS__);
#define INFOMESSAGE(...) LOGMESSAGE(LOG_INFO, __VA_ARGS__);
#define DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__);
#if defined(PAINFULLY_VERBOSE)
#   define PV_DBGMESSAGE(...) LOGMESSAGE(LOG_DEBUG, __VA_ARGS__);
#else
#   define PV_DBGMESSAGE(...) ((void)0);
#endif

logging_messagef() ist eine Funktion in einer separaten .c-Datei definiert. Verwenden Sie die xmessage (...) Makros im Code je nach Zweck der Nachricht. Das Beste an diesem Setup ist, dass es für das Debugging und Protokollierung zur gleichen Zeit arbeitet, und die logging_messagef() Funktion geändert werden kann verschiedene Dinge (printf zu stderr, in einer Protokolldatei, die Verwendung syslog oder eine anderen System-Logging-Anlage zu tun, etc.), und Meldungen unter einem bestimmten Pegel kann in logging_messagef() ignoriert werden, wenn Sie sie nicht brauchen. PV_DBGMESSAGE() ist für die reichlich Debug-Nachrichten, die Sie sicherlich in der Produktion deaktivieren wollen.

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