Wenn Sie einen C99 oder höher Compiler verwenden
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
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:
#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif
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:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); } while (0)
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:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
Und dann schreiben Sie in den Code:
TRACE(("message %d\n", var));
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:
#include <stdarg.h>
#include <stdio.h>
void dbg_printf(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
}
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)
/*
@(#)File: $RCSfile: debug.h,v $
@(#)Version: $Revision: 1.2 $
@(#)Last changed: $Date: 1990/05/01 12:55:39 $
@(#)Purpose: Definitions for the debugging system
@(#)Author: J Leffler
*/
#ifndef DEBUG_H
#define DEBUG_H
/* -- Macro Definitions */
#ifdef DEBUG
#define TRACE(x) db_print x
#else
#define TRACE(x)
#endif /* DEBUG */
/* -- Declarations */
#ifdef DEBUG
extern int debug;
#endif
#endif /* DEBUG_H */
debug.h - Version 3.6 (2008-02-11)
/*
@(#)File: $RCSfile: debug.h,v $
@(#)Version: $Revision: 3.6 $
@(#)Last changed: $Date: 2008/02/11 06:46:37 $
@(#)Purpose: Definitions for the debugging system
@(#)Author: J Leffler
@(#)Copyright: (C) JLSS 1990-93,1997-99,2003,2005,2008
@(#)Product: :PRODUCT:
*/
#ifndef DEBUG_H
#define DEBUG_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif /* HAVE_CONFIG_H */
/*
** Usage: TRACE((level, fmt, ...))
** "level" is the debugging level which must be operational for the output
** to appear. "fmt" is a printf format string. "..." is whatever extra
** arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
** -- See chapter 8 of 'The Practice of Programming', by Kernighan and Pike.
*/
#ifdef DEBUG
#define TRACE(x) db_print x
#else
#define TRACE(x) do { if (0) db_print x; } while (0)
#endif /* DEBUG */
#ifndef lint
#ifdef DEBUG
/* This string can't be made extern - multiple definition in general */
static const char jlss_id_debug_enabled[] = "@(#)*** DEBUG ***";
#endif /* DEBUG */
#ifdef MAIN_PROGRAM
const char jlss_id_debug_h[] = "@(#)$Id: debug.h,v 3.6 2008/02/11 06:46:37 jleffler Exp $";
#endif /* MAIN_PROGRAM */
#endif /* lint */
#include <stdio.h>
extern int db_getdebug(void);
extern int db_newindent(void);
extern int db_oldindent(void);
extern int db_setdebug(int level);
extern int db_setindent(int i);
extern void db_print(int level, const char *fmt,...);
extern void db_setfilename(const char *fn);
extern void db_setfileptr(FILE *fp);
extern FILE *db_getfileptr(void);
/* Semi-private function */
extern const char *db_indent(void);
/**************************************\
** MULTIPLE DEBUGGING SUBSYSTEMS CODE **
\**************************************/
/*
** Usage: MDTRACE((subsys, level, fmt, ...))
** "subsys" is the debugging system to which this statement belongs.
** The significance of the subsystems is determined by the programmer,
** except that the functions such as db_print refer to subsystem 0.
** "level" is the debugging level which must be operational for the
** output to appear. "fmt" is a printf format string. "..." is
** whatever extra arguments fmt requires (possibly nothing).
** The non-debug macro means that the code is validated but never called.
*/
#ifdef DEBUG
#define MDTRACE(x) db_mdprint x
#else
#define MDTRACE(x) do { if (0) db_mdprint x; } while (0)
#endif /* DEBUG */
extern int db_mdgetdebug(int subsys);
extern int db_mdparsearg(char *arg);
extern int db_mdsetdebug(int subsys, int level);
extern void db_mdprint(int subsys, int level, const char *fmt,...);
extern void db_mdsubsysnames(char const * const *names);
#endif /* DEBUG_H */
Einzelargumentvariante für C99 oder höher
Kyle Brandt fragte:
Wie auch immer, funktioniert das debug_print
immer noch, auch wenn es keine Argumente gibt? Beispielsweise:
debug_print("Foo");
Es gibt einen einfachen, altmodischen Hack:
debug_print("%s\n", "Foo");
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:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
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:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
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?
Was ist der Zweck des do while
hier?
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 verwenden do { ... } while (0)
, haben Sie:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) fprintf(stderr, __VA_ARGS__)
Angenommen, Sie schreiben:
if (x > y)
debug_print("x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
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):
if (x > y)
{
if (DEBUG)
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
}
Der nächste Versuch mit dem Makro könnte sein:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
Und dasselbe Codefragment erzeugt jetzt:
if (x > y)
if (DEBUG)
{
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
}
; // Null statement from semi-colon after macro
else
do_something_useful(x, y);
Und das else
ist jetzt ein Syntaxfehler. Die do { ... } while(0)
Schleife vermeidet diese beiden Probleme.
Es gibt eine andere Möglichkeit, das Makro zu schreiben, die möglicherweise funktioniert:
/* BAD - BAD - BAD */
#define debug_print(...) \
((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))
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 die do { ... } 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 die do { ... } while (0)
Version besser. Beachten Sie, dass Sie nur die do { ... } 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
und mddebug.c
im
src / libsoq
Unterverzeichnis.