printf - Fehlerquelle? [geschlossen]

9

Ich verwende viel printffür das Verfolgen / Protokollieren in meinem Code. Ich habe festgestellt, dass dies eine Quelle für Programmierfehler ist. Ich fand den Einfügeoperator ( <<) immer etwas seltsam, aber ich fange an zu denken, dass ich einige dieser Fehler vermeiden könnte, wenn ich ihn stattdessen verwende.

Hat jemand jemals eine ähnliche Offenbarung gehabt oder greife ich hier nur nach Strohhalmen?

Einige nehmen Punkte weg

  • Meine derzeitige Meinung ist, dass die Typensicherheit den Nutzen der Verwendung von printf überwiegt. Das eigentliche Problem ist die Formatzeichenfolge und die Verwendung nicht typsicherer variabler Funktionen.
  • Vielleicht werde ich <<die stl-Ausgabestream-Varianten nicht verwenden, aber ich werde auf jeden Fall einen typsicheren Mechanismus verwenden, der sehr ähnlich ist.
  • Ein Großteil der Ablaufverfolgung / Protokollierung ist an Bedingungen geknüpft, aber ich möchte den Code immer ausführen, um Fehler in Tests nicht zu verpassen, nur weil es sich um einen selten verwendeten Zweig handelt.
John Leidegren
quelle
4
printfin der C ++ Welt? Ich vermisse hier etwas?
user827992
10
@ user827992: Fehlt Ihnen die Tatsache, dass der C ++ - Standard die C-Standardbibliothek als Referenz enthält? Die Verwendung printfin C ++ ist völlig legal . (Ob es eine gute Idee ist, ist eine andere Frage.)
Keith Thompson
2
@ user827992: printfhat einige Vorteile; siehe meine Antwort.
Keith Thompson
1
Diese Frage ist ziemlich grenzwertig. "Was meint ihr?" - Fragen werden oft geschlossen.
Dbracey
1
@ Vitaut Ich denke (danke für den Tipp). Ich bin nur ein bisschen ratlos über die aggressive Mäßigung. Es fördert nicht wirklich interessante Diskussionen über Programmiersituationen, von denen ich gerne mehr hätte.
John Leidegren

Antworten:

2

printf ist besonders in Fällen, in denen Sie sich für die Leistung interessieren (wie sprintf und fprintf), ein wirklich seltsamer Hack. Es überrascht mich immer wieder, dass Leute, die aufgrund des geringen Leistungsaufwands im Zusammenhang mit virtuellen Funktionen auf C ++ hämmern, dann Cs Io verteidigen.

Ja, um das Format unserer Ausgabe herauszufinden, das wir zur Kompilierungszeit zu 100% kennen, analysieren wir zur Laufzeit eine verdammte Formatzeichenfolge in einer massiv seltsamen Sprungtabelle mit unergründlichen Formatcodes!

Natürlich konnten diese Formatcodes nicht so erstellt werden, dass sie mit den Typen übereinstimmen, die sie darstellen. Das wäre zu einfach ... und Sie werden jedes Mal daran erinnert, wenn Sie nachschlagen, ob es sich um% llg oder% lg handelt, dass diese (stark typisierte) Sprache Sie dazu bringt Finden Sie Typen manuell heraus, um etwas zu drucken / zu scannen, UND wurde für Prozessoren vor 32 Bit entwickelt.

Ich gebe zu, dass C ++ mit der Formatbreite und -präzision sperrig umgeht und syntaktischen Zucker verbrauchen könnte, aber das bedeutet nicht, dass Sie den bizarren Hack verteidigen müssen, der das Haupt-Io-System von C ist. Die absoluten Grundlagen sind in beiden Sprachen ziemlich einfach (obwohl Sie wahrscheinlich sowieso so etwas wie eine benutzerdefinierte Fehlerfunktion / einen benutzerdefinierten Fehlerstrom für den Debug-Code verwenden sollten), die moderaten Fälle sind in C regex-artig (einfach zu schreiben, schwer zu analysieren / zu debuggen) ) und die komplexen Fälle, die in C. unmöglich sind.

(Wenn Sie überhaupt die Standardcontainer verwenden, schreiben Sie sich einige schnelle Vorlagenüberladungen für Operatoren <<, mit denen Sie beispielsweise das std::cout << my_list << "\n";Debuggen durchführen können, bei dem my_list vom Typ ist list<vector<pair<int,string> > >.)

jkerian
quelle
1
Das Problem der Standard-C ++ - Bibliothek ist, dass die meisten Inkarnationen operator<<(ostream&, T)durch Aufrufen implementiert werden ... na ja sprintf! Die Leistung von sprintfist nicht optimal, aber aufgrund dessen ist die Leistung von iostreams im Allgemeinen noch schlechter.
Jan Hudec
@ JanHudec: Das war zu diesem Zeitpunkt seit ungefähr einem Jahrzehnt nicht mehr wahr. Das eigentliche Drucken erfolgt mit denselben zugrunde liegenden Systemaufrufen, und dafür rufen C ++ - Implementierungen häufig C-Bibliotheken auf ... aber das ist nicht dasselbe wie das Weiterleiten von std :: cout durch printf.
Jkerian
16

Das Mischen der Ausgabe im C-Stil printf()(oder puts()oder putchar()oder ...) mit der Ausgabe im C ++ - Stil std::cout << ...kann unsicher sein. Wenn ich mich richtig erinnere, können sie separate Puffermechanismen haben, sodass die Ausgabe möglicherweise nicht in der beabsichtigten Reihenfolge angezeigt wird. (Wie AProgrammer in einem Kommentar erwähnt, sync_with_stdiospricht dies an).

printf()ist grundsätzlich typunsicher. Der für ein Argument erwartete Typ wird durch die Formatzeichenfolge bestimmt ( "%d"erfordert eine intoder etwas, zu dem befördert wird int, "%s"erfordert eine, char*die auf eine korrekt terminierte Zeichenfolge im C-Stil verweisen muss usw.), aber das Übergeben des falschen Argumenttyps führt zu undefiniertem Verhalten , kein diagnostizierbarer Fehler. Einige Compiler, wie z. B. gcc, warnen recht gut vor Typinkongruenzen, können dies jedoch nur, wenn die Formatzeichenfolge ein Literal ist oder zur Kompilierungszeit anderweitig bekannt ist (was der häufigste Fall ist) - und so weiter Warnungen werden von der Sprache nicht benötigt. Wenn Sie die falsche Art von Argument übergeben, können willkürlich schlechte Dinge passieren.

Die Stream-E / A von C ++ sind dagegen viel typsicherer, da der <<Operator für viele verschiedene Typen überlastet ist. std::cout << xmuss nicht den Typ angeben x; Der Compiler generiert den richtigen Code für jeden Typ x.

Auf der anderen Seite sind printfdie Formatierungsoptionen meiner Meinung nach viel praktischer. Wenn ich einen Gleitkommawert mit 3 Nachkommastellen drucken möchte, kann ich ihn verwenden "%.3f"- und er hat keine Auswirkungen auf andere Argumente, selbst innerhalb desselben printfAufrufs. C ++ 's setprecisionwirkt sich dagegen auf den Status des Streams aus und kann die spätere Ausgabe durcheinander bringen, wenn Sie nicht sehr vorsichtig sind, um den vorherigen Status des Streams wiederherzustellen. (Dies ist mein persönlicher Ärger mit Haustieren. Wenn ich einen sauberen Weg vermisse, um ihn zu vermeiden, kommentieren Sie ihn bitte.)

Beide haben Vor- und Nachteile. Die Verfügbarkeit von printfist besonders nützlich, wenn Sie zufällig einen C-Hintergrund haben und mit diesem besser vertraut sind oder wenn Sie C-Quellcode in ein C ++ - Programm importieren. std::cout << ...ist für C ++ idiomatischer und erfordert nicht so viel Sorgfalt, um Typinkongruenzen zu vermeiden. Beide sind gültiges C ++ (der C ++ - Standard enthält den größten Teil der C-Standardbibliothek als Referenz).

Es ist wahrscheinlich am besten, es std::cout << ...für andere C ++ - Programmierer zu verwenden, die möglicherweise an Ihrem Code arbeiten, aber Sie können beide verwenden - insbesondere in Trace-Code, den Sie wegwerfen werden.

Und natürlich lohnt es sich, etwas Zeit mit dem Umgang mit Debuggern zu verbringen (aber in einigen Umgebungen ist dies möglicherweise nicht möglich).

Keith Thompson
quelle
Keine Erwähnung des Mischens in der ursprünglichen Frage.
Dbracey
1
@dbracey: Nein, aber ich dachte, es wäre erwähnenswert als möglicher Nachteil von printf.
Keith Thompson
6
Informationen zum Synchronisierungsproblem finden Sie unter std::ios_base::sync_with_stdio.
AProgrammer
1
+1 Die Verwendung von std :: cout zum Drucken von Debug-Informationen in einer Multithread-App ist zu 100% nutzlos. Zumindest bei printf ist es nicht so wahrscheinlich, dass Dinge von Mensch oder Maschine verschachtelt und nicht analysiert werden können.
James
@ James: Liegt das daran, dass std::coutfür jedes gedruckte Element ein separater Aufruf verwendet wird? Sie können dies umgehen, indem Sie vor dem Drucken eine Ausgabezeile in einer Zeichenfolge sammeln. Und natürlich können Sie auch jeweils einen Artikel mit drucken printf. Es ist nur bequemer, eine Zeile (oder mehrere) in einem Anruf zu drucken.
Keith Thompson
2

Ihr Problem ist höchstwahrscheinlich auf die Mischung von zwei sehr unterschiedlichen Standard-Output-Managern zurückzuführen, von denen jeder seine eigene Agenda für dieses arme kleine STDOUT hat. Sie erhalten keine Garantie dafür, wie sie implementiert sind, und es ist durchaus möglich, dass sie widersprüchliche Dateideskriptoroptionen festlegen, beide versuchen, unterschiedliche Aktionen auszuführen usw. Außerdem haben die Einfügeoperatoren einen großen Vorteil printf: printfSie können dies tun:

printf("%d", SomeObject);

Während <<nicht.

Hinweis: Zum Debuggen verwenden Sie nicht printfoder cout. Sie verwenden fprintf(stderr, ...)und cerr.

Linuxios
quelle
Keine Erwähnung des Mischens in der ursprünglichen Frage.
Dbracey
Natürlich können Sie die Adresse eines Objekts drucken, aber der große Unterschied besteht darin, dass die printfTypensicherheit nicht typsicher ist, und ich bin der aktuellen Meinung, dass die Typensicherheit den Nutzen der Verwendung überwiegt printf. Das Problem ist wirklich das Format - String und die nicht typsichere variadische Funktion.
John Leidegren
@ JohnLeidegren: Aber was ist, wenn SomeObjectes kein Zeiger ist? Sie erhalten beliebige Binärdaten, die der Compiler darstellt SomeObject.
Linuxios
Ich glaube, ich habe deine Antwort rückwärts gelesen ... nvm.
John Leidegren
1

Es gibt viele Gruppen - zum Beispiel Google -, die keine Streams mögen.

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(Öffnen Sie das Dreieck-Ding, damit Sie die Diskussion sehen können.) Ich denke, der Google C ++ - Styleguide enthält VIELE sehr vernünftige Ratschläge.

Ich denke, der Nachteil ist, dass Streams sicherer sind, aber printf klarer zu lesen ist (und es einfacher ist, genau die gewünschte Formatierung zu erhalten).

dbracey
quelle
2
Der Google Style Guide ist nett, ABER er enthält einige Elemente, die nicht für einen Allzweck-Guide geeignet sind . (Das ist in Ordnung, denn es ist immerhin Googles Leitfaden für Code, der bei / für Google ausgeführt wird.)
Martin Ba
1

printfkann aufgrund mangelnder Typensicherheit Fehler verursachen. Es gibt ein paar Möglichkeiten der Adressierung dass ohne Schalt iostream‚s <<Betreiber und kompliziertere Formatierung:

  • Einige Compiler (wie GCC und Clang) können optional Ihre Formatzeichenfolgen anhand printfder printfArgumente überprüfen und Warnungen wie die folgenden anzeigen, wenn sie nicht übereinstimmen.
    Warnung: Die Konvertierung gibt den Typ 'int' an, das Argument hat jedoch den Typ 'char *'.
  • Das Skript typesafeprintf kann Ihre printfAufrufe im Stil vorverarbeiten , um sie typsicher zu machen.
  • Mit Bibliotheken wie Boost.Format und FastFormat können Sie printfähnliche Formatzeichenfolgen verwenden (insbesondere die von Boost.Format sind fast identisch mit printf), während die Typensicherheit und die Typerweiterbarkeit erhalten bleiben iostreams.
Josh Kelley
quelle
1

Die Printf-Syntax ist grundsätzlich in Ordnung, abzüglich einiger unklarer Eingaben. Wenn Sie denken, dass es falsch ist, warum C #, Python und andere Sprachen das sehr ähnliche Konstrukt verwenden? Das Problem in C oder C ++: Es ist nicht Teil einer Sprache und wird daher vom Compiler nicht auf korrekte Syntax (*) überprüft und bei Optimierung der Geschwindigkeit nicht in eine Reihe nativer Aufrufe zerlegt. Beachten Sie, dass bei einer Größenoptimierung printf-Aufrufe möglicherweise effizienter werden! Die C ++ - Streaming-Syntax ist alles andere als gut. Es funktioniert, Typensicherheit ist da, aber die ausführliche Syntax ... bleh. Ich meine, ich benutze es, aber ohne Freude.

(*) Einige Compiler führen diese Überprüfung sowie fast alle statischen Analysewerkzeuge durch (ich verwende Lint und hatte seitdem keine Probleme mit printf).

Beschädigen
quelle
1
Es gibt Boost.Format , das die praktische Syntax ( format("fmt") % arg1 % arg2 ...;) mit Typensicherheit kombiniert . Auf Kosten einer höheren Leistung, da Stringstream-Aufrufe generiert werden, die in vielen Implementierungen intern Sprintf-Aufrufe generieren.
Jan Hudec
0

printfist meiner Meinung nach ein weitaus flexibleres Ausgabewerkzeug für den Umgang mit Variablen als alle CPP-Stream-Ausgaben. Beispielsweise:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

Möglicherweise möchten Sie den CPP- <<Operator jedoch verwenden, wenn Sie ihn für eine bestimmte Methode überladen ... um beispielsweise einen Speicherauszug eines Objekts abzurufen, das die Daten einer bestimmten Person enthält PersonData.

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

Dafür wäre es weitaus effektiver zu sagen (vorausgesetzt, es ahandelt sich um ein Objekt von PersonData).

std::cout << a;

als:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

Ersteres entspricht weitaus mehr dem Prinzip der Kapselung (keine Notwendigkeit, die Einzelheiten zu kennen, private Mitgliedsvariablen) und ist auch leichter zu lesen.

Aviator45003
quelle
0

Sie sollten nicht printfin C ++ verwenden. Je. Der Grund ist, wie Sie richtig bemerkt haben, dass es eine Fehlerquelle ist und die Tatsache, dass das Drucken von benutzerdefinierten Typen und in C ++ fast alles benutzerdefinierte Typen sein sollte, schmerzhaft ist. C ++ - Lösung sind die Streams.

Es gibt jedoch ein kritisches Problem, das Streams für eine vom Benutzer sichtbare Ausgabe ungeeignet macht! Das Problem sind Übersetzungen. Ein Ausleihbeispiel aus dem gettext-Handbuch besagt, dass Sie schreiben möchten:

cout << "String '" << str << "' has " << str.size() << " characters\n";

Jetzt kommt der deutsche Übersetzer und sagt: Ok, auf Deutsch sollte die Nachricht sein

n Zeichen lang ist die Zeichenkette ' s '

Und jetzt bist du in Schwierigkeiten, weil er die Teile herummischen muss. Es sollte gesagt werden, dass sogar viele Implementierungen printfProbleme damit haben. Es sei denn, sie unterstützen die Erweiterung, damit Sie sie verwenden können

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

Das Boost.Format unterstützt Printf -Formate und verfügt über diese Funktion. Also schreibst du:

cout << format("String '%1' has %2 characters\n") % str % str.size();

Leider ist die Leistung etwas beeinträchtigt, da intern ein Stringstream erstellt wird und der <<Operator zum Formatieren jedes Bits verwendet wird. In vielen Implementierungen <<ruft der Operator intern auf sprintf. Ich vermute, dass eine effizientere Implementierung möglich wäre, wenn dies wirklich gewünscht wäre.

Jan Hudec
quelle
-1

Sie erledigen eine Menge nutzloser Arbeit, abgesehen von der Tatsache, ob stlböse oder nicht, debuggen Sie Ihren Code mit einer Reihe von printfnur 1 weiteren möglichen Fehlern.

Verwenden Sie einfach einen Debugger und lesen Sie etwas über Ausnahmen und wie man sie fängt und wirft. Versuche nicht ausführlicher zu sein, als du eigentlich sein musst.

PS

printf wird in C für das C ++ verwendet, das Sie haben std::cout

user827992
quelle
Sie verwenden keine Ablaufverfolgung / Protokollierung anstelle eines Debuggers.
John Leidegren