# Makro für Debug-Druck in C definieren?


209

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?


Optimiert der Compiler (gcc) Anweisungen wie if (DEBUG) {...} out, wenn im Produktionscode das DEBUG-Makro auf 0 gesetzt ist? Ich verstehe, dass es gute Gründe gibt, die Debug-Anweisungen für den Compiler sichtbar zu lassen, aber es bleibt ein schlechtes Gefühl. -Pat
Pat

Antworten:


410

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_printimmer 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 whilehier?

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 ifAnweisung 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 elseist 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.hund mddebug.cim src / libsoq Unterverzeichnis.


1
Ich denke, der GCC ## - Ansatz von gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html wäre es wert, unter der Überschrift "Einzelargument C99-Variante" erwähnt zu werden.
Hugo Ideler

2
Jahre später, und diese Antwort ist immer noch die nützlichste unter allen Internets, wie man alias printk! vfprintf funktioniert nicht im Kernelraum, da stdio nicht verfügbar ist. Danke dir! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
Kevinf

6
In Ihrem Beispiel mit den Schlüsselwörtern __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 von fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); ## __ VA_ARGS__ beheben, indem Sie keine Parameter an die Funktion übergeben.
mc_electron

1
@LogicTom: Der Unterschied ist zwischen #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 Sie debug_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".
Jonathan Leffler

1
Die oben gezeigte Variante @ St.Antario verwendet eine einzelne aktive Debugging-Ebene für die gesamte Anwendung, und ich verwende normalerweise Befehlszeilenoptionen, damit die Debugging-Ebene festgelegt werden kann, wenn das Programm ausgeführt wird. Ich habe auch eine Variante, die mehrere verschiedene Subsysteme erkennt, von denen jedes einen Namen und eine eigene Debugging-Ebene erhält, so dass ich -D input=4,macros=9,rules=2die 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.
Jonathan Leffler

28

Ich benutze so etwas:

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

Dann benutze ich einfach D als Präfix:

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

Der Compiler sieht den Debug-Code, es gibt kein Komma-Problem und er funktioniert überall. Es funktioniert auch, wenn printfes 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 elseirgendwo in der Nähe gibt, das durch diese Injektion abgefangen werden kann if. Dies ist eine Version, die darüber geht:

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

3
Was for(;0;)könnte es ein Problem erzeugen , wenn Sie so etwas wie schreiben D continue;oder D break;.
ACcreator

1
Hast mich; Es ist jedoch sehr unwahrscheinlich, dass es zu einem Unfall kommt.
mbq

11

Für eine tragbare Implementierung (ISO C90) können Sie wie folgt doppelte Klammern 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 (hackisch, 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;
}

3
@LB: Um den Präprozessor zum Nachdenken zu bringen, gibt es nur ein Argument, während _ zu einem späteren Zeitpunkt erweitert werden kann.
Marcin Koziuk

10

Hier ist die Version, die ich benutze:

#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

Ich würde so etwas tun

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

Ich finde das sauberer.


Die Idee, ein Makro in einem Test als Flag zu verwenden, gefällt mir nicht wirklich. Können Sie erklären, warum der Debug-Druck immer überprüft werden sollte?
LB40

1
@ Jonathan: Wenn der Code immer nur im Debug-Modus ausgeführt wird, warum sollte es Sie interessieren, wenn er im Nicht-Debug-Modus kompiliert wird? assert()von der stdlib funktioniert genauso und ich verwende das NDEBUGMakro normalerweise nur für meinen eigenen Debugging-Code ...
Christoph

Wenn Sie DEBUG im Test verwenden und jemand einen unkontrollierten undef DEBUG ausführt, wird Ihr Code nicht mehr kompiliert. richtig ?
LB40

4
Es ist frustrierend, das Debuggen zu aktivieren und dann den Debugging-Code zu debuggen, da er sich auf Variablen bezieht, die umbenannt oder neu eingegeben wurden usw. Wenn der Compiler (Post-Pre-Prozessor) die print-Anweisung immer sieht, stellt er sicher, dass alle umgebenden Änderungen vorhanden sind Die Diagnose wurde nicht ungültig. 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 - plan9.bell-labs.com/cm/cs/tpop .
Jonathan Leffler

1
@Christoph: Nun, irgendwie ... Ich verwende NDEBUG nur zur Steuerung von Zusicherungen und ein separates Makro (normalerweise DEBUG) zur Steuerung der Debug-Ablaufverfolgung. Ich möchte 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 den gleichen Build des druckt Code kann je nach Programmoptionen gedruckt oder nicht gedruckt werden. Ich befürworte, dass der Compiler für alle Builds die diagnostischen Anweisungen sehen sollte; Es wird jedoch kein Code generiert, es sei denn, das Debuggen ist aktiviert.
Jonathan Leffler

8

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");.


1
Willkommen bei Stack Overflow. Sie haben Recht, dass GCC die nicht standardmäßige Erweiterung hat, auf die Sie verweisen. In der aktuell akzeptierten Antwort wird dies tatsächlich erwähnt, einschließlich genau der von Ihnen angegebenen Referenz-URL.
Jonathan Leffler

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

Welche Version von C unterstützt diese Notation? Und wenn es funktioniert hat, bedeutet das Einfügen aller Argumente, dass Sie nur einen sehr begrenzten Satz von Optionen für die Formatzeichenfolge haben, nicht wahr?
Jonathan Leffler

@ Jonathan: gcc (Debian 4.3.3-13) 4.3.3
eyalm

1
OK - vereinbart: Es ist als alte GNU-Erweiterung dokumentiert (Abschnitt 5.17 des GCC 4.4.1-Handbuchs). Aber Sie sollten wahrscheinlich dokumentieren, dass es nur mit GCC funktioniert - oder vielleicht haben wir das in diesen Kommentaren zwischen uns getan.
Jonathan Leffler

1
Meine Absicht war es, einen anderen Stil der Verwendung von
Argumenten

2

Das benutze ich:

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

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.


Es ist besser, wenn der Compiler immer den Debug-Code überprüft.
Jonathan Leffler

1

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

#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

Wenn ich gcc benutze, 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 ​​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:

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 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.


1

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

  1. Sie müssen darauf vertrauen, dass sie optimiert werden, was zugegebenermaßen passieren sollte, wenn Sie über eine ausreichende Optimierungsstufe verfügen.
  2. Darüber hinaus werden sie dies wahrscheinlich nicht tun, wenn Sie eine Release-Kompilierung mit deaktivierter Optimierung zu Testzwecken durchführen (was zugegebenermaßen selten ist). und sie werden es mit ziemlicher Sicherheit während des Debuggens überhaupt nicht tun - wodurch Dutzende oder Hunderte von "if (DEBUG)" - Anweisungen zur Laufzeit ausgeführt werden; Dadurch wird die Ausführung verlangsamt (was mein grundsätzlicher Einwand ist) und weniger wichtig, um die Größe Ihrer ausführbaren Datei oder DLL zu erhöhen. und damit Ausführungs- und Kompilierungszeiten. Jonathan teilt mir jedoch mit, dass seine Methode angewendet werden kann, um auch überhaupt keine Aussagen zu kompilieren.

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:

// 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, machen Sie einfach:

DEBUGLOG_INIT("afile.log");

Um in die Protokolldatei zu schreiben, gehen Sie einfach wie folgt vor:

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

Um es zu schließen, tun Sie:

DEBUGLOG_CLOSE();

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

#define DEBUG 1

Führen Sie Folgendes aus, damit Protokollierungsanweisungen zu nichts kompiliert werden

#define DEBUG 0

Wenn Sie Informationen von einem häufig ausgeführten Code benötigen (dh 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 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.

  1. Es kann erweitert werden, um so viele Ebenen zu erstellen, wie Sie möchten.
  2. Es wird zu nichts kompiliert, wenn nicht gedruckt wird.
  3. Es zentralisiert E / A an einem einfach zu bearbeitenden Ort.
  4. Es ist flexibel und verwendet die printf-Formatierung.
  5. Auch hier werden Debug-Läufe nicht verlangsamt, während immer kompilierte Debug-Ausdrucke immer im Debug-Modus ausgeführt werden. Wenn Sie Informatik betreiben und die Informationsverarbeitung nicht einfacher zu schreiben ist, führen Sie möglicherweise einen CPU-verbrauchenden Simulator aus, um beispielsweise festzustellen, wo der Debugger ihn mit einem Index außerhalb des Bereichs für einen Vektor stoppt. Diese laufen im Debug-Modus bereits extra langsam. Die obligatorische Ausführung von Hunderten von Debug-Drucken wird solche Läufe notwendigerweise noch weiter verlangsamen. Für mich sind solche Läufe keine Seltenheit.

Nicht besonders wichtig, aber zusätzlich:

  1. Es erfordert keinen Hack, um ohne Argumente zu drucken (z. B. 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 #defines 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.


2
Sie sagen "Dutzende oder Hunderte von 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).
Jonathan Leffler

Jonathan Leffler: Danke, dass Sie auf meine falsche Formulierung hingewiesen haben. Ich lasse meine Gedanken schneller rasen als meine Finger und bin so froh, dass dies beendet ist. Ich habe meine Einwände mit "... 1) überarbeitet. Sie müssen darauf vertrauen, dass sie optimiert werden. Dies sollte zugegebenermaßen passieren, wenn Sie über eine ausreichende Optimierungsstufe verfügen. 2) Darüber hinaus werden sie dies nicht tun, wenn Sie ein Release mit Optimierung kompilieren lassen Zu Testzwecken deaktiviert, und während des Debuggens werden sie wahrscheinlich überhaupt nicht ausgeführt. Dadurch werden zur Laufzeit Dutzende oder Hunderte von 'if (DEBUG)' - Anweisungen ausgeführt. Dadurch werden die Größe Ihrer ausführbaren Datei oder der DLL sowie die Ausführungszeiten erhöht. "
CodeLurker

Damit Sie das andere wichtige tun können, was ich tue, müssten Sie Debug-Levels haben. Obwohl ich oft nicht viele davon aktivieren muss, profitieren einige Anwendungen wirklich davon, dass sie mit einem einfachen "#define DEBUG 3" einen hohen Detaillierungsgrad für eine zeitkritische Schleife erhalten und dann zu zurückkehren können viel weniger ausführliche Informationen mit "#define DEBUG 1". Ich habe nie mehr als drei Level benötigt und daher kompilieren mindestens ungefähr 1/3 meiner Debugs bereits bei der Veröffentlichung. Wenn ich kürzlich Level 3 verwendet habe, tun es wahrscheinlich ALLE.
CodeLurker

YMMV. Das moderne System, das ich gezeigt habe, unterstützt die dynamische (Laufzeit-) Einstellung von Debug-Ebenen, sodass Sie programmgesteuert entscheiden können, wie viel Debug zur Laufzeit erzeugt wird. Normalerweise habe ich die Stufen 1 bis 9 verwendet, obwohl es keine Obergrenze gibt (oder Untergrenze; ​​die Standardstufe ist 0, die normalerweise deaktiviert ist, kann aber bei Bedarf während der aktiven Entwicklung explizit angefordert werden - sie ist nicht für Langzeitarbeit geeignet). Ich habe eine Standardstufe von 3 gewählt. Dinge können abgestimmt werden. Das gibt mir viel Kontrolle. Wenn Sie den Debug-Code wirklich nicht testen möchten, wenn er inaktiv ist, ändern Sie die Alternative in ((void)0)- es ist einfach.
Jonathan Leffler

1
Ahh. Es hätte geholfen, das Ganze gelesen zu haben. Es ist ein ziemlich langer Beitrag. Ich denke, das hat bisher die wesentlichen Punkte. Es stellt sich heraus, dass Ihre wie meine zum Kompilieren oder Nichtkompilieren aller Debug-Drucke verwendet werden können und Ebenen unterstützen können. Allerdings können Sie Levels kompilieren, die Sie nicht verwenden - zu einem Preis während des Debuggens.
CodeLurker

0

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

#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");
...
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.