Der Versuch, ein Makro zu erstellen, das zum Drucken von Debug-Meldungen verwendet werden kann, wenn DEBUG definiert ist, wie der folgende Pseudocode:
#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)
Wie wird dies mit einem Makro erreicht?
c
c-preprocessor
jfarrell
quelle
quelle
Antworten:
Wenn Sie einen C99 oder höher Compiler verwenden
Es wird davon ausgegangen, dass Sie C99 verwenden (die Notation der Liste variabler Argumente wird in früheren Versionen nicht unterstützt). Die
do { ... } while (0)
Redewendung stellt sicher, dass sich der Code wie eine Anweisung verhält (Funktionsaufruf). Die bedingungslose Verwendung des Codes stellt sicher, dass der Compiler immer überprüft, ob Ihr Debug-Code gültig ist. Der Optimierer entfernt den Code jedoch, wenn DEBUG 0 ist.Wenn Sie mit #ifdef DEBUG arbeiten möchten, ändern Sie die Testbedingung:
Und dann benutze DEBUG_TEST, wo ich DEBUG benutzt habe.
Wenn Sie darauf bestehen , auf einem Stringliteral für den Format - String (wahrscheinlich eine gute Idee sowieso), können Sie auch vorstellen Dinge wie
__FILE__
,__LINE__
und__func__
in den Ausgang, der die Diagnostik verbessern kann:Dies hängt von der Verkettung von Zeichenfolgen ab, um eine Zeichenfolge mit größerem Format zu erstellen, als der Programmierer schreibt.
Wenn Sie einen C89-Compiler verwenden
Wenn Sie mit C89 nicht weiterkommen und keine nützliche Compiler-Erweiterung haben, gibt es keine besonders saubere Möglichkeit, damit umzugehen. Die Technik, die ich verwendet habe, war:
Und dann schreiben Sie in den Code:
Die doppelten Klammern sind entscheidend - und deshalb haben Sie die lustige Notation in der Makroerweiterung. Nach wie vor überprüft der Compiler den Code immer auf syntaktische Gültigkeit (was gut ist), aber der Optimierer ruft die Druckfunktion nur auf, wenn das DEBUG-Makro einen Wert ungleich Null ergibt.
Dies erfordert eine Unterstützungsfunktion - im Beispiel dbg_printf () -, um Dinge wie 'stderr' zu handhaben. Sie müssen wissen, wie man Varargs-Funktionen schreibt, aber das ist nicht schwer:
Sie können diese Technik natürlich auch in C99 verwenden, aber die
__VA_ARGS__
Technik ist ordentlicher, da sie die reguläre Funktionsnotation verwendet, nicht den Hack in doppelten Klammern.Warum ist es wichtig, dass der Compiler immer den Debug-Code sieht?
[ Aufwärmen von Kommentaren zu einer anderen Antwort. ]]
Eine zentrale Idee hinter den obigen C99- und C89-Implementierungen ist, dass der eigentliche Compiler die debuggenden printf-ähnlichen Anweisungen immer sieht. Dies ist wichtig für Langzeitcode - Code, der ein oder zwei Jahrzehnte hält.
Angenommen, ein Code ist seit einigen Jahren größtenteils ruhend (stabil), muss aber jetzt geändert werden. Sie aktivieren die Debugging-Ablaufverfolgung wieder - es ist jedoch frustrierend, den Debugging-Code (Ablaufverfolgungscode) debuggen zu müssen, da er sich auf Variablen bezieht, die während der Jahre stabiler Wartung umbenannt oder neu eingegeben wurden. Wenn der Compiler (Post-Pre-Prozessor) die print-Anweisung immer sieht, stellt er sicher, dass alle umgebenden Änderungen die Diagnose nicht ungültig gemacht haben. Wenn der Compiler die print-Anweisung nicht sieht, kann er Sie nicht vor Ihrer eigenen Nachlässigkeit (oder der Nachlässigkeit Ihrer Kollegen oder Mitarbeiter) schützen. Siehe ' Die Praxis des Programmierens ' von Kernighan und Pike, insbesondere Kapitel 8 (siehe auch Wikipedia zu TPOP ).
Dies ist die Erfahrung, dass ich dort gewesen bin und dies getan habe. Ich habe im Wesentlichen die in anderen Antworten beschriebene Technik verwendet, bei der der Nicht-Debug-Build die printf-ähnlichen Anweisungen für einige Jahre (mehr als ein Jahrzehnt) nicht sieht. Aber ich bin auf den Rat in TPOP gestoßen (siehe meinen vorherigen Kommentar) und habe dann nach einigen Jahren Debugging-Code aktiviert und bin auf Probleme mit einem geänderten Kontext gestoßen, der das Debugging unterbrochen hat. Wenn ich den Druck immer validiert habe, habe ich mich mehrmals vor späteren Problemen bewahrt.
Ich verwende NDEBUG nur zur Steuerung von Zusicherungen und ein separates Makro (normalerweise DEBUG), um zu steuern, ob die Debug-Ablaufverfolgung in das Programm integriert ist. Selbst wenn die Debug-Ablaufverfolgung integriert ist, möchte ich häufig nicht, dass die Debug-Ausgabe bedingungslos angezeigt wird. Daher kann ich steuern, ob die Ausgabe angezeigt wird (Debug-Ebenen, und anstatt
fprintf()
direkt aufzurufen , rufe ich eine Debug-Druckfunktion auf, die nur bedingt gedruckt wird Daher kann derselbe Code-Build basierend auf den Programmoptionen gedruckt oder nicht gedruckt werden. Ich habe auch eine Version des Codes mit mehreren Subsystemen für größere Programme, so dass ich verschiedene Abschnitte des Programms haben kann, die unterschiedliche Mengen an Trace erzeugen - unter Laufzeitkontrolle.Ich befürworte, dass der Compiler für alle Builds die diagnostischen Anweisungen sehen sollte; Der Compiler generiert jedoch keinen Code für die Debugging-Trace-Anweisungen, es sei denn, Debug ist aktiviert. Grundsätzlich bedeutet dies, dass Ihr gesamter Code bei jeder Kompilierung vom Compiler überprüft wird - ob für die Freigabe oder das Debuggen. Das ist eine gute Sache!
debug.h - Version 1.2 (1990-05-01)
debug.h - Version 3.6 (2008-02-11)
Einzelargumentvariante für C99 oder höher
Kyle Brandt fragte:
Es gibt einen einfachen, altmodischen Hack:
Die unten gezeigte Nur-GCC-Lösung bietet auch Unterstützung dafür.
Sie können dies jedoch mit dem geraden C99-System tun, indem Sie Folgendes verwenden:
Im Vergleich zur ersten Version verlieren Sie die eingeschränkte Prüfung, für die das Argument 'fmt' erforderlich ist. Dies bedeutet, dass jemand versuchen könnte, 'debug_print ()' ohne Argumente aufzurufen (aber das nachfolgende Komma in der Argumentliste kann
fprintf()
nicht kompiliert werden). . Ob der Verlust der Überprüfung überhaupt ein Problem darstellt, ist umstritten.GCC-spezifische Technik für ein einzelnes Argument
Einige Compiler bieten möglicherweise Erweiterungen für andere Methoden zur Behandlung von Argumentlisten mit variabler Länge in Makros an. Wie in den Kommentaren von Hugo Ideler zum ersten Mal erwähnt , können Sie mit GCC das Komma weglassen, das normalerweise nach dem letzten 'festen' Argument für das Makro erscheint. Sie können auch
##__VA_ARGS__
den Makroersetzungstext verwenden, der das Komma vor der Notation löscht, wenn, nur wenn das vorherige Token ein Komma ist:Diese Lösung bietet weiterhin den Vorteil, dass das Formatargument erforderlich ist, während optionale Argumente nach dem Format akzeptiert werden.
Diese Technik wird auch von Clang aus Gründen der GCC-Kompatibilität unterstützt.
Warum die Do-While-Schleife?
Sie möchten das Makro so verwenden können, dass es wie ein Funktionsaufruf aussieht, dh es folgt ein Semikolon. Daher müssen Sie den Makrokörper entsprechend verpacken. Wenn Sie eine
if
Anweisung ohne Umgebung verwendendo { ... } while (0)
, haben Sie:Angenommen, Sie schreiben:
Leider spiegelt diese Einrückung nicht die tatsächliche Steuerung des Flusses wider, da der Präprozessor einen entsprechenden Code erzeugt (eingerückt und Klammern hinzugefügt, um die tatsächliche Bedeutung hervorzuheben):
Der nächste Versuch mit dem Makro könnte sein:
Und dasselbe Codefragment erzeugt jetzt:
Und das
else
ist jetzt ein Syntaxfehler. Diedo { ... } while(0)
Schleife vermeidet diese beiden Probleme.Es gibt eine andere Möglichkeit, das Makro zu schreiben, die möglicherweise funktioniert:
Dadurch bleibt das angezeigte Programmfragment gültig. Die
(void)
Umwandlung verhindert, dass sie in Kontexten verwendet wird, in denen ein Wert erforderlich ist. Sie kann jedoch als linker Operand eines Kommaoperators verwendet werden, in dem diedo { ... } while (0)
Version dies nicht kann. Wenn Sie der Meinung sind, dass Sie Debug-Code in solche Ausdrücke einbetten sollten, bevorzugen Sie dies möglicherweise. Wenn Sie es vorziehen, dass der Debug-Druck als vollständige Anweisung fungiert, ist diedo { ... } while (0)
Version besser. Beachten Sie, dass Sie nur diedo { ... } while(0)
Notation verwenden können, wenn der Hauptteil des Makros Semikolons enthält (grob gesagt) . Es funktioniert immer; Der Ausdrucksanweisungsmechanismus kann schwieriger anzuwenden sein. Möglicherweise erhalten Sie vom Compiler auch Warnungen mit dem Ausdrucksformular, das Sie lieber vermeiden möchten. Dies hängt vom Compiler und den von Ihnen verwendeten Flags ab.TPOP war zuvor unter http://plan9.bell-labs.com/cm/cs/tpop und http://cm.bell-labs.com/cm/cs/tpop, aber beide sind jetzt (10.08.2015) gebrochen.
Code in GitHub
Wenn Sie neugierig, können Sie sich diesen Code in GitHub aussehen in meinem SOQ (Stack Overflow Questions) Repository als Dateien
debug.c
,debug.h
undmddebug.c
im src / libsoq Unterverzeichnis.quelle
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__FILE__, __LINE__, __func__, __VA_ARGS__
wird es nicht kompiliert, wenn Sie keine printf-Parameter haben, dh wenn Sie nur aufrufen.debug_print("Some msg\n");
Sie können dies mithilfe vonfprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
## __ VA_ARGS__ beheben, indem Sie keine Parameter an die Funktion übergeben.#define debug_print(fmt, ...)
und#define debug_print(...)
. Das erste erfordert mindestens ein Argument, das Format string (fmt
) und null oder mehr andere Argumente. Die zweite erfordert insgesamt null oder mehr Argumente. Wenn Siedebug_print()
mit dem ersten verwenden, erhalten Sie vom Präprozessor einen Fehler bezüglich des Missbrauchs des Makros, während der zweite dies nicht tut. Es werden jedoch immer noch Kompilierungsfehler angezeigt, da der Ersatztext nicht gültig ist. C. Es ist also wirklich kein großer Unterschied - daher die Verwendung des Begriffs "eingeschränkte Prüfung".-D input=4,macros=9,rules=2
die Debug-Ebene des Eingabesystems auf 4 und das Makrosystem auf 9 setzen kann (einer intensiven Prüfung unterzogen) ) und das Regelsystem zu 2. Es gibt endlose Variationen des Themas; Verwenden Sie, was auch immer zu Ihnen passt.Ich benutze so etwas:
Dann benutze ich einfach D als Präfix:
Der Compiler sieht den Debug-Code, es gibt kein Komma-Problem und er funktioniert überall. Es funktioniert auch, wenn
printf
es nicht ausreicht, beispielsweise wenn Sie ein Array sichern oder einen Diagnosewert berechnen müssen, der für das Programm selbst redundant ist.EDIT: Ok, es könnte ein Problem erzeugen, wenn es
else
irgendwo in der Nähe gibt, das durch diese Injektion abgefangen werden kannif
. Dies ist eine Version, die darüber geht:quelle
for(;0;)
könnte es ein Problem erzeugen , wenn Sie so etwas wie schreibenD continue;
oderD break;
.Für eine tragbare Implementierung (ISO C90) können Sie wie folgt doppelte Klammern verwenden.
oder (hackisch, würde es nicht empfehlen)
quelle
Hier ist die Version, die ich benutze:
quelle
Ich würde so etwas tun
Ich finde das sauberer.
quelle
assert()
von der stdlib funktioniert genauso und ich verwende dasNDEBUG
Makro normalerweise nur für meinen eigenen Debugging-Code ...Laut http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html sollte es ein
##
Vorher geben__VA_ARGS__
.Andernfalls
#define dbg_print(format, ...) printf(format, __VA_ARGS__)
kompiliert ein Makro das folgende Beispiel nicht :dbg_print("hello world");
.quelle
quelle
Das benutze ich:
Es hat den schönen Vorteil, printf auch ohne zusätzliche Argumente richtig zu handhaben. Im Fall von DBG == 0 erhält selbst der dümmste Compiler nichts zum Kauen, sodass kein Code generiert wird.
quelle
Mein Favorit von den folgenden ist
var_dump
, die, wenn genannt als:var_dump("%d", count);
erzeugt Ausgabe wie:
patch.c:150:main(): count = 0
Dank an @ "Jonathan Leffler". Alle sind C89-glücklich:
Code
quelle
Wenn ich gcc benutze, mag ich:
Weil es in Code eingefügt werden kann.
Angenommen, Sie versuchen zu debuggen
Dann können Sie es ändern in:
Und Sie können eine Analyse erhalten, welcher Ausdruck zu welchem ausgewertet wurde.
Es ist gegen das Problem der doppelten Bewertung geschützt, aber das Fehlen von Gensymen lässt es für Namenskollisionen offen.
Es nistet jedoch:
Ich denke also, solange Sie vermeiden, g2rE3 als Variablennamen zu verwenden, sind Sie in Ordnung.
Sicherlich habe ich es (und verwandte Versionen für Strings und Versionen für Debug-Levels usw.) von unschätzbarem Wert gefunden.
quelle
Ich habe jahrelang darüber nachgedacht, wie das geht, und schließlich eine Lösung gefunden. Ich wusste jedoch nicht, dass es hier bereits andere Lösungen gibt. Erstens im Unterschied zu Lefflers Antwort sehe ich im Gegensatz sein Argument nicht, dass Debug-Drucke immer kompiliert werden sollten. Ich möchte lieber nicht Tonnen von nicht benötigtem Code in meinem Projekt ausführen lassen, wenn er nicht benötigt wird, in Fällen, in denen ich testen muss und sie möglicherweise nicht optimiert werden.
Nicht jedes Mal zu kompilieren klingt möglicherweise schlechter als in der Praxis. Sie erhalten Debug-Drucke, die manchmal nicht kompiliert werden, aber es ist nicht so schwer, sie zu kompilieren und zu testen, bevor Sie ein Projekt abschließen. Wenn Sie mit diesem System drei Debug-Ebenen verwenden, setzen Sie es einfach auf Debug-Nachrichtenebene drei, beheben Sie Ihre Kompilierungsfehler und suchen Sie nach anderen, bevor Sie Ihren Code finalisieren. (Da das Kompilieren von Debug-Anweisungen natürlich keine Garantie dafür ist, dass sie weiterhin wie beabsichtigt funktionieren.)
Meine Lösung bietet auch Debug-Detailebenen. und wenn Sie es auf die höchste Ebene setzen, werden alle kompiliert. Wenn Sie kürzlich eine hohe Debug-Detailstufe verwendet haben, konnten alle zu diesem Zeitpunkt kompilieren. Endgültige Updates sollten ziemlich einfach sein. Ich habe nie mehr als drei Level gebraucht, aber Jonathan sagt, er hat neun benutzt. Diese Methode (wie die von Leffler) kann auf eine beliebige Anzahl von Ebenen erweitert werden. Die Verwendung meiner Methode kann einfacher sein; Bei Verwendung in Ihrem Code sind nur zwei Anweisungen erforderlich. Ich codiere jedoch auch das CLOSE-Makro - obwohl es nichts bewirkt. Es könnte sein, wenn ich an eine Datei sende.
Gegen die Kosten ist der zusätzliche Schritt, sie zu testen, um zu sehen, dass sie vor der Auslieferung kompiliert werden
Zweige sind in modernen Pre-Fetching-Prozessoren relativ teuer. Vielleicht keine große Sache, wenn Ihre App nicht zeitkritisch ist. Aber wenn Leistung ein Problem ist, dann ja, eine Sache, die groß genug ist, dass ich mich lieber für einen etwas schneller ausgeführten Debug-Code entscheiden würde (und möglicherweise eine schnellere Veröffentlichung, in seltenen Fällen, wie bereits erwähnt).
Ich wollte also ein Debug-Druckmakro, das nicht kompiliert wird, wenn es nicht gedruckt werden soll, sondern wenn es gedruckt wird. Ich wollte auch Debugging-Ebenen, damit ich beispielsweise, wenn ich wollte, dass leistungskritische Teile des Codes manchmal nicht gedruckt werden, aber zu anderen Zeiten, eine Debug-Ebene festlegen und zusätzliche Debug-Drucke starten können Es gab eine Möglichkeit, Debug-Levels zu implementieren, die feststellten, ob der Druck überhaupt kompiliert wurde oder nicht. Ich habe es so erreicht:
DebugLog.h:
DebugLog.cpp:
Verwenden der Makros
Um es zu benutzen, machen Sie einfach:
Um in die Protokolldatei zu schreiben, gehen Sie einfach wie folgt vor:
Um es zu schließen, tun Sie:
obwohl dies derzeit technisch gesehen nicht einmal notwendig ist, da es nichts tut. Ich verwende derzeit jedoch immer noch CLOSE, falls ich es mir anders überlege, wie es funktioniert, und die Datei zwischen den Protokollierungsanweisungen offen lassen möchte.
Wenn Sie dann den Debug-Druck aktivieren möchten, bearbeiten Sie einfach die erste #define in der Header-Datei, um beispielsweise zu sagen
Führen Sie Folgendes aus, damit Protokollierungsanweisungen zu nichts kompiliert werden
Wenn Sie Informationen von einem häufig ausgeführten Code benötigen (dh einen hohen Detaillierungsgrad), möchten Sie möglicherweise Folgendes schreiben:
Wenn Sie DEBUG als 3 definieren, werden die Protokollierungsstufen 1, 2 und 3 kompiliert. Wenn Sie es auf 2 setzen, erhalten Sie die Protokollierungsstufen 1 und 2. Wenn Sie es auf 1 setzen, erhalten Sie nur Anweisungen der Protokollierungsstufe 1.
In Bezug auf die do-while-Schleife wird die Schleife nicht benötigt, da sie entweder eine einzelne Funktion oder nichts anstelle einer if-Anweisung ergibt. OK, beschuldigen Sie mich, C anstelle von C ++ IO zu verwenden (und Qts QString :: arg () ist auch in Qt eine sicherere Methode zum Formatieren von Variablen - es ist ziemlich clever, erfordert aber mehr Code und die Formatierungsdokumentation ist nicht so organisiert wie es sein mag - aber ich habe immer noch Fälle gefunden, in denen es vorzuziehen ist), aber Sie können jeden Code in die gewünschte CPP-Datei einfügen. Es könnte auch eine Klasse sein, aber dann müssten Sie sie instanziieren und mithalten oder ein neues () erstellen und speichern. Auf diese Weise legen Sie einfach die Anweisungen #include, init und optional close in Ihrer Quelle ab und können sie verwenden. Es wäre jedoch eine gute Klasse, wenn Sie so geneigt sind.
Ich hatte zuvor viele Lösungen gesehen, aber keine entsprach meinen Kriterien so gut wie diese.
Nicht besonders wichtig, aber zusätzlich:
DEBUGLOG_LOG(3, "got here!");
); Auf diese Weise können Sie beispielsweise die sicherere .arg () - Formatierung von Qt verwenden. Es funktioniert auf MSVC und somit wahrscheinlich gcc. Es wird##
im#define
s verwendet, was nicht dem Standard entspricht, wie Leffler betont, aber weitgehend unterstützt wird. (Sie können es neu codieren, um es##
bei Bedarf nicht zu verwenden , aber Sie müssen einen Hack verwenden, wie er ihn bereitstellt.)Warnung: Wenn Sie vergessen, das Argument der Protokollierungsstufe anzugeben, behauptet MSVC unbeholfen, dass der Bezeichner nicht definiert ist.
Möglicherweise möchten Sie einen anderen Präprozessorsymbolnamen als DEBUG verwenden, da eine Quelle dieses Symbol ebenfalls definiert (z. B. Progs mit
./configure
Befehle zur Vorbereitung der Erstellung verwenden). Es schien mir natürlich, als ich es entwickelte. Ich habe es in einer Anwendung entwickelt, in der die DLL von etwas anderem verwendet wird, und es ist üblicher, Protokolldrucke an eine Datei zu senden. Aber es in vprintf () zu ändern, würde auch gut funktionieren.Ich hoffe, dies erspart vielen von Ihnen Kummer darüber, wie Sie die Debug-Protokollierung am besten durchführen können. oder zeigt Ihnen eine, die Sie vielleicht bevorzugen. Ich habe seit Jahrzehnten halbherzig versucht, dies herauszufinden. Funktioniert in MSVC 2012 & 2015 und somit wahrscheinlich auf gcc; sowie wahrscheinlich an vielen anderen arbeiten, aber ich habe es nicht an ihnen getestet.
Ich möchte auch eines Tages eine Streaming-Version davon machen.
Hinweis: Vielen Dank an Leffler, der mir herzlich geholfen hat, meine Nachricht für StackOverflow besser zu formatieren.
quelle
if (DEBUG)
Anweisungen zur Laufzeit ausführen , die nicht optimiert werden" - was bei Windmühlen kippt . Der springende Punkt des Systems I beschrieben ist , dass der Code vom Compiler geprüft (wichtig, und automatisch - keine spezielle Build erforderlich) , aber der Debug - Code wird nicht erzeugt , weil es wird optimiert aus (so gibt es Null - Laufzeit Auswirkungen auf Codegröße oder Leistung, da der Code zur Laufzeit nicht vorhanden ist).((void)0)
- es ist einfach.Ich glaube, diese Variation des Themas bietet Debug-Kategorien, ohne dass ein separater Makroname pro Kategorie erforderlich ist.
Ich habe diese Variante in einem Arduino-Projekt verwendet, bei dem der Programmspeicher auf 32 KB und der dynamische Speicher auf 2 KB begrenzt ist. Das Hinzufügen von Debug-Anweisungen und Trace-Debug-Zeichenfolgen beansprucht schnell Speicherplatz. Daher ist es wichtig, den Debug-Trace, der zur Kompilierungszeit enthalten ist, auf das Minimum zu beschränken, das bei jeder Codeerstellung erforderlich ist.
debug.h
Aufruf der CPP-Datei
quelle