webentwicklung-frage-antwort-db.com.de

C # Define-Makro für Debug-Druck

Beim Versuch, ein Makro zu erstellen, das zum Drucken von Debug-Meldungen verwendet werden kann, wenn DEBUG definiert ist, wie im folgenden Pseudocode

#define DEBUG 1
#define debug_print(args ...) if (DEBUG) fprintf(stderr, args)

Wie wird dies mit einem Makro erreicht?

179
jfarrell

Wenn Sie einen C99 oder neueren 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 Listennotation für variable Argumente wird in früheren Versionen nicht unterstützt). Das do { ... } while (0) -Idiom stellt sicher, dass der Code wie eine Anweisung (Funktionsaufruf) wirkt. 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 auf einem String-Literal für das Format String bestehen (wahrscheinlich sowieso eine gute Idee), können Sie auch Dinge wie __FILE__, __LINE__ und __func__ in die Ausgabe einfügen, was die Diagnose verbessern kann:

#define debug_print(fmt, ...) \
        do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
                                __LINE__, __func__, __VA_ARGS__); } while (0)

Dies beruht auf der Verkettung von Zeichenfolgen, um eine Zeichenfolge mit größerem Format zu erstellen, als der Programmierer schreibt.

Wenn Sie einen C89-Compiler verwenden

Wenn Sie mit C89 und keiner nützlichen Compiler-Erweiterung nicht weiterkommen, gibt es keine besonders saubere Möglichkeit, damit umzugehen. Die Technik, die ich benutzte, war:

#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)

Und dann schreiben Sie im Code:

TRACE(("message %d\n", var));

Die doppelten Klammern sind entscheidend - und deshalb haben Sie die lustige Notation in der Makro-Erweiterung. Wie zuvor prüft der Compiler den Code immer auf syntaktische Gültigkeit (was gut ist), aber das Optimierungsprogramm 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. Es erfordert, dass Sie 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 Technik __VA_ARGS__ ist besser, da sie die reguläre Funktionsnotation verwendet, nicht den Doppelklammer-Hack.

Warum ist es wichtig, dass der Compiler immer den Debug-Code sieht?

[ Kommentare zu einer anderen Antwort wiederholen. ]

Eine zentrale Idee sowohl für die C99- als auch für die C89-Implementierung ist, dass der eigentliche Compiler das Debuggen von printf-ähnlichen Anweisungen immer sieht. Dies ist wichtig für langfristigen Code - Code, der ein oder zwei Jahrzehnte dauern wird.

Angenommen, ein Teil des Codes ist seit einigen Jahren größtenteils ruhend (stabil), muss aber jetzt geändert werden. Sie aktivieren den Debugging-Trace erneut. Es ist jedoch frustrierend, den Debugging-Code (Trace-Code) debuggen zu müssen, da er sich auf Variablen bezieht, die während der Jahre der stabilen Wartung umbenannt oder neu typisiert wurden. Wenn der Compiler (Post-Pre-Prozessor) die Druckanweisung immer sieht, stellt er sicher, dass alle umgebenden Änderungen die Diagnose nicht ungültig gemacht haben. Wenn der Compiler die Druckanweisung 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, die ich gemacht habe - ich habe im Wesentlichen die Technik verwendet, die in anderen Antworten beschrieben wurde, in denen der Nicht-Debug-Build die printf-ähnlichen Anweisungen für eine Reihe von Jahren (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 geändertem Kontext gestoßen, wodurch das Debugging unterbrochen wurde. Mehrmals hat es mich vor späteren Problemen bewahrt, den Ausdruck immer validiert zu haben.

Ich verwende NDEBUG, um nur Assertions zu steuern, 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 habe ich einen Mechanismus, mit dem ich steuern kann, ob die Ausgabe angezeigt wird (Debug-Ebenen). Anstatt direkt fprintf() aufzurufen, rufe ich einen Debug-Ausdruck auf Funktion, die nur bedingt druckt, sodass derselbe Build des Codes basierend auf den Programmoptionen gedruckt oder nicht gedruckt werden kann). Ich habe auch eine "Multiple-Subsystem" -Version des Codes für größere Programme, so dass ich verschiedene Programmabschnitte haben kann, die unterschiedliche Mengen an Trace erzeugen - unter Laufzeitkontrolle.

Ich befürworte, dass für alle Builds der Compiler die Diagnoseanweisungen 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 jedes Mal vom Compiler überprüft wird, wenn Sie kompilieren - sei es zur Veröffentlichung oder zum 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 (11.02.2008)

/*
@(#)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, um dies zu tun, funktioniert debug_print immer noch, auch wenn es keine Argumente gibt? Zum Beispiel:

    debug_print("Foo");

Es gibt einen einfachen, altmodischen Hack:

debug_print("%s\n", "Foo");

Die unten gezeigte GCC-only-Lösung bietet auch Unterstützung dafür.

Sie können dies jedoch mit dem direkten 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, die das Argument 'fmt' erfordert. Dies bedeutet, dass jemand versuchen könnte, 'debug_print ()' ohne Argumente aufzurufen (aber das nachstehende Komma in der Argumentliste auf fprintf() würde nicht kompilieren). Ob der Kontrollverlust ü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 variabler Länge in Makros an. Wie in den Kommentaren von Hugo Ideler zum ersten Mal erwähnt, können Sie in GCC das Komma weglassen, das normalerweise nach dem letzten 'festen' Argument im Makro erscheint. Sie können auch ##__VA_ARGS__ im Makroersetzungstext verwenden, wodurch das Komma vor der Notation gelöscht wird, jedoch nur dann, 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 aus Gründen der GCC-Kompatibilität auch von Clang unterstützt.


Warum die Do-While-Schleife?

Wozu dient der do while hier?

Sie möchten in der Lage sein, das Makro so zu verwenden, dass es wie ein Funktionsaufruf aussieht, was bedeutet, dass darauf ein Semikolon folgt. Daher müssen Sie den Makrokörper entsprechend verpacken. Wenn Sie eine if -Anweisung ohne das umgebende do { ... } while (0) verwenden, 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 Datenflusses wider, da der Präprozessor einen entsprechenden Code erzeugt (eingerückt und geschweifte 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 das gleiche 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 Art, das Makro zu schreiben, die funktionieren könnte:

/* BAD - BAD - BAD */
#define debug_print(...) \
            ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))

Dadurch bleibt das angezeigte Programmfragment gültig. Der Cast (void) verhindert, dass er in Kontexten verwendet wird, in denen ein Wert erforderlich ist. Er kann jedoch als linker Operand eines Kommaoperators verwendet werden, bei dem die Version do { ... } while (0) dies nicht kann. Wenn Sie der Meinung sind, dass Sie Debug-Code in solche Ausdrücke einbetten können, ist dies möglicherweise besser. Wenn Sie möchten, dass der Debug-Ausdruck als vollständige Anweisung fungiert, ist die Version do { ... } while (0) 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 auch Warnungen vom Compiler 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) kaputt.


Code in GitHub

Wenn Sie neugierig sind, können Sie diesen Code in GitHub in meinem SOQ (Stack Overflow Questions) -Repository als Dateien debug.c, debug.h und mddebug.c im src/libsoq ansehen = Unterverzeichnis.

388

Ich benutze so etwas:

#ifdef DEBUG
 #define D if(1) 
#else
 #define D if(0) 
#endif

Dann benutze ich nur D als Präfix:

D printf("x=%0.3f\n",x);

Der Compiler sieht den Debug-Code, es gibt kein Komma-Problem und funktioniert überall. Es funktioniert auch, wenn printf nicht ausreicht, beispielsweise wenn Sie ein Array sichern oder einen Diagnosewert berechnen müssen, der für das Programm selbst nicht erforderlich ist.

BEARBEITEN: Ok, es könnte ein Problem auftreten, wenn sich else in der Nähe befindet, das von dieser eingefügten if abgefangen werden kann. Diese Version geht darüber hinaus:

#ifdef DEBUG
 #define D 
#else
 #define D for(;0;)
#endif
25
mbq

Bei einer tragbaren Implementierung (ISO C90) können Sie doppelte Klammern wie folgt verwenden.

#include <stdio.h>
#include <stdarg.h>

#ifndef NDEBUG
#  define debug_print(msg) stderr_printf msg
#else
#  define debug_print(msg) (void)0
#endif

void
stderr_printf(const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vfprintf(stderr, fmt, ap);
  va_end(ap);
}

int
main(int argc, char *argv[])
{
  debug_print(("argv[0] is %s, argc is %d\n", argv[0], argc));
  return 0;
}

oder (hackish, würde es nicht empfehlen)

#include <stdio.h>

#define _ ,
#ifndef NDEBUG
#  define debug_print(msg) fprintf(stderr, msg)
#else
#  define debug_print(msg) (void)0
#endif

int
main(int argc, char *argv[])
{
  debug_print("argv[0] is %s, argc is %d"_ argv[0] _ argc);
  return 0;
}
10
Marcin Koziuk

Hier ist die Version, die ich verwende:

#ifdef NDEBUG
#define Dprintf(FORMAT, ...) ((void)0)
#define Dputs(MSG) ((void)0)
#else
#define Dprintf(FORMAT, ...) \
    fprintf(stderr, "%s() in %s, line %i: " FORMAT "\n", \
        __func__, __FILE__, __LINE__, __VA_ARGS__)
#define Dputs(MSG) Dprintf("%s", MSG)
#endif
9
Christoph

Ich würde so etwas machen 

#ifdef DEBUG
#define debug_print(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__)
#else
#define debug_print(fmt, ...) do {} while (0)
#endif

Ich denke das ist sauberer. 

9
LB40
#define debug_print(FMT, ARGS...) do { \
    if (DEBUG) \
        fprintf(stderr, "%s:%d " FMT "\n", __FUNCTION__, __LINE__, ## ARGS); \
    } while (0)
6
eyalm

Gemäß http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , Sollte vor ## ein __VA_ARGS__ stehen.

Andernfalls kompiliert ein Makro #define dbg_print(format, ...) printf(format, __VA_ARGS__) das folgende Beispiel nicht: dbg_print("hello world");.

5
Chobits Tai

Ich habe jahrelang darüber nachgedacht und schließlich eine Lösung gefunden. Ich wusste jedoch nicht, dass es hier bereits andere Lösungen gibt. Erstens sehe ich im Unterschied zu Lefflers Antwort sein Argument nicht, dass Debug-Ausdrucke immer kompiliert werden sollten. Ich würde es vorziehen, nicht benötigten Code in meinem Projekt auszuführen, wenn er nicht benötigt wird, wenn ich ihn testen muss und er möglicherweise nicht optimiert wird.

Nicht jedes Mal zu kompilieren klingt möglicherweise schlechter als in der Praxis. Sie werden mit Debug-Ausdrucken fertig, die manchmal nicht kompiliert werden, aber es ist nicht so schwierig, sie zu kompilieren und zu testen, bevor Sie ein Projekt fertigstellen. Wenn Sie mit diesem System drei Debug-Ebenen verwenden, setzen Sie es einfach auf Debug-Ebene drei, beheben Sie Ihre Kompilierungsfehler und suchen Sie nach anderen, bevor Sie Ihren Code fertigstellen. (Da das Kompilieren von Debug-Anweisungen natürlich keine Garantie dafür ist, dass sie weiterhin wie beabsichtigt funktionieren.)

Meine Lösung bietet auch detaillierte Debug-Informationen. und wenn Sie es auf die höchste Ebene setzen, werden sie alle kompiliert. Wenn Sie in letzter Zeit eine hohe Debug-Detailstufe verwendet haben, konnten sie alle zu diesem Zeitpunkt kompilieren. Letzte 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 ist möglicherweise einfacher. In Ihrem Code sind nur zwei Anweisungen erforderlich. Ich programmiere aber auch das Makro CLOSE - obwohl es nichts bringt. Es könnte sein, dass ich an eine Datei sende.

Gegen die Kosten ist der zusätzliche Schritt des Testens, um zu sehen, dass sie vor der Auslieferung kompiliert werden, dass

  1. Sie müssen darauf vertrauen, dass sie optimiert werden. Dies sollte zugegebenermaßen geschehen, wenn Sie über ein ausreichendes Optimierungsniveau verfügen.
  2. Darüber hinaus wird dies wahrscheinlich nicht der Fall sein, wenn Sie ein Release mit deaktivierter Optimierung für Testzwecke kompilieren (was zugegebenermaßen selten vorkommt). und sie werden mit ziemlicher Sicherheit während des Debuggens überhaupt nicht ausgeführt - wodurch Dutzende oder Hunderte von "if (DEBUG)" - Anweisungen zur Laufzeit ausgeführt werden; Dies verlangsamt die Ausführung (was mein hauptsächlicher Einwand ist) und, was weniger wichtig ist, erhöht die Größe Ihrer ausführbaren Datei oder DLL. und damit Ausführungs- und Kompilierzeiten. Jonathan teilt mir jedoch mit, dass man seine Methode anwenden kann, um überhaupt keine Aussagen zusammenzustellen.

Zweige sind in modernen Vorabrufprozessoren relativ teuer. Vielleicht keine große Sache, wenn Ihre App nicht zeitkritisch ist. Aber wenn die Leistung ein Problem ist, dann ist dies eine Sache, die groß genug ist, dass ich mich lieber für etwas schneller ausgeführten Debug-Code entscheiden würde (und möglicherweise, wie bereits erwähnt, in seltenen Fällen für eine schnellere Veröffentlichung).

Was ich also wollte, ist ein Debug-Druckmakro, das nicht kompiliert wird, wenn es nicht gedruckt werden soll, sondern wenn es gedruckt wird. Ich wollte auch Ebenen des Debuggens, so dass z. Wenn ich wollte, dass leistungsentscheidende Teile des Codes nicht zu bestimmten Zeiten, sondern zu bestimmten Zeiten gedruckt werden, könnte ich eine Debug-Stufe festlegen und zusätzliche Debug-Drucke aktivieren Der Druck wurde sogar kompiliert oder nicht. Ich habe es so erreicht:

DebugLog.h:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  Level 3 is the most information.
// Levels 2 and 1 have progressively more.  Thus, you can write: 
//     DEBUGLOG_LOG(1, "a number=%d", 7);
// and it will be seen if DEBUG is anything other than undefined or zero.  If you write
//     DEBUGLOG_LOG(3, "another number=%d", 15);
// it will only be seen if DEBUG is 3.  When not being displayed, these routines compile
// to NOTHING.  I reject the argument that debug code needs to always be compiled so as to 
// keep it current.  I would rather have a leaner and faster app, and just not be lazy, and 
// maintain debugs as needed.  I don't know if this works with the C preprocessor or not, 
// but the rest of the code is fully C compliant also if it is.

#define DEBUG 1

#ifdef DEBUG
#define DEBUGLOG_INIT(filename) debuglog_init(filename)
#else
#define debuglog_init(...)
#endif

#ifdef DEBUG
#define DEBUGLOG_CLOSE debuglog_close
#else
#define debuglog_close(...)
#endif

#define DEBUGLOG_LOG(level, fmt, ...) DEBUGLOG_LOG ## level (fmt, ##__VA_ARGS__)

#if DEBUG == 0
#define DEBUGLOG_LOG0(...)
#endif

#if DEBUG >= 1
#define DEBUGLOG_LOG1(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG1(...)
#endif

#if DEBUG >= 2
#define DEBUGLOG_LOG2(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG2(...)
#endif

#if DEBUG == 3
#define DEBUGLOG_LOG3(fmt, ...) debuglog_log (fmt, ##__VA_ARGS__)
#else
#define DEBUGLOG_LOG3(...)
#endif

void debuglog_init(char *filename);
void debuglog_close(void);
void debuglog_log(char* format, ...);

DebugLog.cpp:

// FILE: DebugLog.h
// REMARKS: This is a generic pair of files useful for debugging.  It provides three levels of 
// debug logging, currently; in addition to disabling it.  See DebugLog.h's remarks for more 
// info.

#include <stdio.h>
#include <stdarg.h>

#include "DebugLog.h"

FILE *hndl;
char *savedFilename;

void debuglog_init(char *filename)
{
    savedFilename = filename;
    hndl = fopen(savedFilename, "wt");
    fclose(hndl);
}

void debuglog_close(void)
{
    //fclose(hndl);
}

void debuglog_log(char* format, ...)
{
    hndl = fopen(savedFilename,"at");
    va_list argptr;
    va_start(argptr, format);
    vfprintf(hndl, format, argptr);
    va_end(argptr);
    fputc('\n',hndl);
    fclose(hndl);
}

Verwenden der Makros

Um es zu benutzen, mache einfach:

DEBUGLOG_INIT("afile.log");

So schreiben Sie in die Protokolldatei:

DEBUGLOG_LOG(1, "the value is: %d", anint);

Um es zu schließen, machst du:

DEBUGLOG_CLOSE();

obwohl dies derzeit technisch gesehen noch nicht einmal notwendig ist, da es nichts bewirkt. Ich verwende derzeit jedoch immer noch CLOSE, falls ich meine Meinung über die Funktionsweise ändere und die Datei zwischen den Protokollierungsanweisungen geöffnet lassen möchte.

Wenn Sie dann den Debug-Druck aktivieren möchten, bearbeiten Sie einfach die erste #Definition in der Header-Datei, z.

#define DEBUG 1

Führen Sie Folgendes aus, um zu verhindern, dass Protokollanweisungen kompiliert werden

#define DEBUG 0

Wenn Sie Informationen von einem häufig ausgeführten Teil des Codes benötigen (d. H. Einen hohen Detaillierungsgrad), möchten Sie möglicherweise Folgendes schreiben:

 DEBUGLOG_LOG(3, "the value is: %d", anint);

Wenn Sie DEBUG als 3 definieren, werden die Protokollierungsstufen 1, 2 und 3 kompiliert. Wenn Sie den Wert auf 2 setzen, erhalten Sie die Protokollierungsstufen 1 und 2. Wenn Sie den Wert auf 1 setzen, erhalten Sie nur Anweisungen der Protokollierungsstufe 1.

In Bezug auf die do-while-Schleife wird die Schleife nicht benötigt, da diese anstelle einer if-Anweisung entweder eine einzelne Funktion oder nichts ergibt. OK, ich bezichtige mich dafür, dass ich C anstelle von C++ verwende. IO (und QString :: arg () von Qt ist eine sicherere Methode zum Formatieren von Variablen, auch wenn es in Qt ist und die Formatierungsdokumentation ist nicht so organisiert, wie sie sein könnte - aber ich habe immer noch Fälle gefunden, in denen es besser ist), aber Sie können den gewünschten Code in die CPP-Datei einfügen Sie müssen es instanziieren und auf dem neuesten Stand halten oder ein neues () erstellen und speichern. Auf diese Weise fügen Sie einfach die Anweisungen #include, init und optional close in Ihre Quelle ein und können mit der Verwendung beginnen würde aber eine feine klasse machen, wenn du so geneigt bist.

Ich hatte zuvor viele Lösungen gesehen, aber keine entsprach meinen Kriterien so gut wie diese.

  1. Es kann beliebig erweitert werden.
  2. Es kompiliert zu nichts, wenn nicht gedruckt wird.
  3. Es zentralisiert IO an einem einfach zu bearbeitenden Ort.
  4. Es ist flexibel und verwendet die printf-Formatierung.
  5. Wieder verlangsamt es Debug-Läufe nicht, während immer kompilierende Debug-Ausdrucke immer im Debug-Modus ausgeführt werden. Wenn Sie Informatik betreiben und die Informationsverarbeitung nicht einfacher zu schreiben ist, können Sie einen CPU-verbrauchenden Simulator ausführen, um z. wo der Debugger es mit einem Index außerhalb des Bereichs für einen Vektor stoppt. Diese laufen bereits im Debug-Modus extra langsam. Die obligatorische Ausführung von Hunderten von Debug-Ausdrucken wird solche Abläufe notwendigerweise noch weiter verlangsamen. Für mich sind solche Läufe keine Seltenheit.

Nicht sonderlich bedeutend, aber zusätzlich:

  1. Es ist kein Hack erforderlich, um ohne Argumente zu drucken (z. B. DEBUGLOG_LOG(3, "got here!");). auf diese Weise können Sie z. Qt ist sicherer .arg () Formatierung. Es funktioniert auf MSVC und damit wahrscheinlich gcc. Es verwendet ## In den #define, Was nicht dem Standard entspricht, wie Leffler betont, aber weitgehend unterstützt wird. (Sie können es neu codieren, um bei Bedarf nicht ## Zu verwenden, aber Sie müssen einen Hack wie den von ihm bereitgestellten verwenden.)

Warnung: Wenn Sie vergessen, das Argument für die Protokollierungsstufe anzugeben, behauptet MSVC, dass der Bezeichner nicht definiert ist.

Möglicherweise möchten Sie einen anderen Präprozessorsymbolnamen als DEBUG verwenden, da einige Quellen dieses Symbol auch definieren (z. B. Progs mit ./configure - Befehlen zur Vorbereitung der Erstellung). Es schien mir natürlich, als ich es entwickelte. Ich habe es in einer Anwendung entwickelt, in der die DLL wird von etwas anderem verwendet, und es ist praktischer, Protokolldrucke in eine Datei zu senden, aber das Ändern in vprintf () würde auch gut funktionieren.

Ich hoffe, dies erspart vielen von Ihnen Kummer darüber, wie Sie das Debug-Logging 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 und 2015 und daher wahrscheinlich auf gcc; und wahrscheinlich auch 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 sehr geholfen hat, meine Nachricht besser für StackOverflow zu formatieren.

1
CodeLurker

Mein Favorit unter den folgenden ist var_dump.

var_dump("%d", count);

erzeugt eine Ausgabe wie:

patch.c:150:main(): count = 0

Dank an @ "Jonathan Leffler". Alle sind C89-glücklich:

Code

#define DEBUG 1
#include <stdarg.h>
#include <stdio.h>
void debug_vprintf(const char *fmt, ...)
{
    va_list args;
    va_start(args, fmt);
    vfprintf(stderr, fmt, args);
    va_end(args);
}

/* Call as: (DOUBLE PARENTHESES ARE MANDATORY) */
/* var_debug(("outfd = %d, somefailed = %d\n", outfd, somefailed)); */
#define var_debug(x) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf x; }} while (0)

/* var_dump("%s" variable_name); */
#define var_dump(fmt, var) do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): ", \
    __FILE__,  __LINE__, __func__); debug_vprintf ("%s = " fmt, #var, var); }} while (0)

#define DEBUG_HERE do { if (DEBUG) { debug_vprintf ("%s:%d:%s(): HERE\n", \
    __FILE__,  __LINE__, __func__); }} while (0)
1
Tom Hale

Ich glaube, diese Variation des Themas gibt Debug-Kategorien ohne die Notwendigkeit, einen separaten Makronamen pro Kategorie zu haben.

Ich habe diese Variante in einem Arduino-Projekt verwendet, in dem der Programmspeicher auf 32 KB und der dynamische Speicher auf 2 KB begrenzt sind. Das Hinzufügen von Debug-Anweisungen und Trace-Debug-Zeichenfolgen verbraucht schnell Speicherplatz. Daher ist es wichtig, den zur Kompilierungszeit enthaltenen Debug-Trace bei jeder Codeerstellung auf das erforderliche Minimum zu beschränken.

debug.h

#ifndef DEBUG_H
#define DEBUG_H

#define PRINT(DEBUG_CATEGORY, VALUE)  do { if (DEBUG_CATEGORY & DEBUG_MASK) Serial.print(VALUE);} while (0);

#endif

aufruf der CPP-Datei

#define DEBUG_MASK 0x06
#include "Debug.h"

...
PRINT(4, "Time out error,\t");
...
0
user358795

Bei der Verwendung von gcc mag ich:

#define DBGI(expr) ({int g2rE3=expr; fprintf(stderr, "%s:%d:%s(): ""%s->%i\n", __FILE__,  __LINE__, __func__, #expr, g2rE3); g2rE3;})

Weil es in Code eingefügt werden kann.

Angenommen, Sie versuchen zu debuggen

printf("%i\n", (1*2*3*4*5*6));

720

Dann können Sie es ändern in:

printf("%i\n", DBGI(1*2*3*4*5*6));

hello.c:86:main(): 1*2*3*4*5*6->720
720

Und Sie können eine Analyse erhalten, welcher Ausdruck zu welchem ​​Wert bewertet wurde. 

Es ist gegen das Problem der Doppelbewertung geschützt, aber das Fehlen von Gensyms lässt es für Namenskollisionen offen.

Es nistet jedoch:

DBGI(printf("%i\n", DBGI(1*2*3*4*5*6)));

hello.c:86:main(): 1*2*3*4*5*6->720
720
hello.c:86:main(): printf("%i\n", DBGI(1*2*3*4*5*6))->4

Ich denke also, solange Sie g2rE3 nicht als Variablennamen verwenden, werden Sie in Ordnung sein.

Ich habe es sicherlich (und verbündete Versionen für Strings und Versionen für Debug-Levels usw.) für unschätzbar befunden.

Das ist was ich benutze:

#if DBG
#include <stdio.h>
#define DBGPRINT printf
#else
#define DBGPRINT(...) /**/  
#endif

Es hat den Vorteil, dass printf auch ohne zusätzliche Argumente richtig gehandhabt werden kann. Falls DBG == 0 ist, hat selbst der dümmste Compiler nichts, worauf er achten muss, sodass kein Code generiert wird.

0
5tenzel