Se si utilizza un compilatore C99 o successivo
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)
Presuppone che tu stia utilizzando C99 (la notazione dell'elenco di argomenti variabili non è supportata nelle versioni precedenti). Il do { ... } while (0)
linguaggio assicura che il codice si comporti come un'istruzione (chiamata di funzione). L'uso incondizionato del codice garantisce che il compilatore controlli sempre che il codice di debug sia valido, ma l'ottimizzatore rimuoverà il codice quando DEBUG è 0.
Se vuoi lavorare con #ifdef DEBUG, modifica le condizioni del test:
#ifdef DEBUG
#define DEBUG_TEST 1
#else
#define DEBUG_TEST 0
#endif
E quindi usa DEBUG_TEST dove ho usato DEBUG.
Se insisti su una stringa letterale per la stringa di formato (probabilmente una buona idea comunque), puoi anche introdurre cose come __FILE__
, __LINE__
e __func__
nell'output, che possono migliorare la diagnostica:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, __VA_ARGS__); } while (0)
Questo si basa sulla concatenazione di stringhe per creare una stringa di formato più grande di quella scritta dal programmatore.
Se si utilizza un compilatore C89
Se sei bloccato con C89 e nessuna estensione del compilatore utile, allora non esiste un modo particolarmente pulito per gestirlo. La tecnica che usavo era:
#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)
E poi, nel codice, scrivi:
TRACE(("message %d\n", var));
Le doppie parentesi sono cruciali - e sono per questo che hai la notazione divertente nell'espansione macro. Come in precedenza, il compilatore controlla sempre la validità sintattica del codice (il che è buono), ma l'ottimizzatore invoca la funzione di stampa solo se la macro DEBUG è diversa da zero.
Ciò richiede una funzione di supporto - dbg_printf () nell'esempio - per gestire cose come 'stderr'. Richiede di sapere come scrivere le funzioni di varargs, ma non è difficile:
#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);
}
Puoi anche usare questa tecnica in C99, ovviamente, ma la __VA_ARGS__
tecnica è più ordinata perché usa una notazione di funzione regolare, non l'hack tra parentesi.
Perché è fondamentale che il compilatore visualizzi sempre il codice di debug?
[ Riforma dei commenti fatti per un'altra risposta. ]
Un'idea centrale dietro entrambe le implementazioni C99 e C89 sopra è che il compilatore vero e proprio vede sempre le istruzioni di debug simili a printf. Questo è importante per il codice a lungo termine - codice che durerà un decennio o due.
Supponiamo che un pezzo di codice sia rimasto in gran parte inattivo (stabile) per un certo numero di anni, ma ora debba essere modificato. Riattivare la traccia di debug - ma è frustrante dover eseguire il debug del codice di debug (traccia) perché si riferisce a variabili che sono state rinominate o ridigitate, durante gli anni di manutenzione stabile. Se il compilatore (post pre-processore) vede sempre l'istruzione print, garantisce che eventuali modifiche circostanti non abbiano invalidato la diagnostica. Se il compilatore non vede la dichiarazione stampata, non può proteggerti dalla tua disattenzione (o dalla disattenzione dei tuoi colleghi o collaboratori). Vedi " La pratica della programmazione " di Kernighan e Pike, in particolare il capitolo 8 (vedi anche Wikipedia su TPOP ).
Questa è "stata lì, fatto quell'esperienza" - ho usato essenzialmente la tecnica descritta in altre risposte in cui la build non di debug non vede le dichiarazioni simili a printf per un certo numero di anni (più di un decennio). Ma mi sono imbattuto nel consiglio in TPOP (vedi il mio commento precedente), e dopo alcuni anni ho abilitato del codice di debug e ho riscontrato problemi di cambiamento del contesto che ha interrotto il debug. Più volte, la convalida della stampa mi ha salvato da problemi successivi.
Uso NDEBUG per controllare solo le asserzioni e una macro separata (di solito DEBUG) per controllare se la traccia di debug è integrata nel programma. Anche quando la traccia di debug è integrata, spesso non voglio che l'output di debug appaia incondizionatamente, quindi ho un meccanismo per controllare se l'output appare (livelli di debug e invece di chiamarefprintf()
direttamente, chiamo una funzione di stampa di debug che stampa solo in modo condizionale così la stessa build del codice può stampare o non stampare in base alle opzioni del programma). Ho anche una versione del "sottosistema multiplo" del codice per programmi più grandi, in modo da poter avere diverse sezioni del programma che producono diverse quantità di traccia, sotto il controllo di runtime.
Sto sostenendo che per tutte le build, il compilatore dovrebbe vedere le dichiarazioni diagnostiche; tuttavia, il compilatore non genererà alcun codice per le istruzioni di traccia del debug a meno che non sia abilitato il debug. Fondamentalmente, significa che tutto il codice viene controllato dal compilatore ogni volta che lo compili, sia per il rilascio che per il debug. Questa è una buona cosa!
debug.h - versione 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 - versione 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 */
Variante a argomento singolo per C99 o successive
Kyle Brandt ha chiesto:
Ad ogni modo fare così debug_print
funziona ancora anche se non ci sono argomenti? Per esempio:
debug_print("Foo");
C'è un semplice trucco vecchio stile:
debug_print("%s\n", "Foo");
Anche la soluzione solo GCC mostrata di seguito fornisce supporto.
Tuttavia, puoi farlo con il sistema dritto C99 usando:
#define debug_print(...) \
do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0)
Rispetto alla prima versione, perdi il controllo limitato che richiede l'argomento 'fmt', il che significa che qualcuno potrebbe provare a chiamare 'debug_print ()' senza argomenti (ma la virgola finale nell'elenco degli argomenti fprintf()
non riuscirà a compilare) . È discutibile se la perdita di controllo sia un problema.
Tecnica specifica GCC per un singolo argomento
Alcuni compilatori possono offrire estensioni per altri modi di gestire elenchi di argomenti a lunghezza variabile nelle macro. Nello specifico, come notato per la prima volta nei commenti di Hugo Ideler , GCC consente di omettere la macro che normalmente apparirebbe dopo l'ultimo argomento "risolto". Consente inoltre di utilizzare ##__VA_ARGS__
nel testo di sostituzione della macro, che elimina la virgola che precede la notazione se, ma solo se, il token precedente è una virgola:
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, ##__VA_ARGS__); } while (0)
Questa soluzione conserva il vantaggio di richiedere l'argomento format pur accettando argomenti opzionali dopo il formato.
Questa tecnica è supportata anche da Clang per la compatibilità GCC.
Perché il ciclo do-while?
Qual è lo scopo del do while
qui?
Si desidera poter utilizzare la macro in modo che appaia come una chiamata di funzione, il che significa che sarà seguita da un punto e virgola. Pertanto, è necessario impacchettare il corpo macro per adattarlo. Se usi una if
dichiarazione senza i dintorni do { ... } while (0)
, avrai:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) fprintf(stderr, __VA_ARGS__)
Supponiamo ora di scrivere:
if (x > y)
debug_print("x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
Sfortunatamente, tale rientro non riflette l'effettivo controllo del flusso, perché il preprocessore produce un codice equivalente a questo (rientri e parentesi aggiunti per enfatizzare il significato reale):
if (x > y)
{
if (DEBUG)
fprintf(stderr, "x (%d) > y (%d)\n", x, y);
else
do_something_useful(x, y);
}
Il prossimo tentativo di macro potrebbe essere:
/* BAD - BAD - BAD */
#define debug_print(...) \
if (DEBUG) { fprintf(stderr, __VA_ARGS__); }
E lo stesso frammento di codice ora produce:
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);
E else
ora è un errore di sintassi. Il do { ... } while(0)
loop evita entrambi questi problemi.
C'è un altro modo di scrivere la macro che potrebbe funzionare:
/* BAD - BAD - BAD */
#define debug_print(...) \
((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))
Questo lascia il frammento del programma mostrato come valido. Il (void)
cast impedisce che venga utilizzato in contesti in cui è richiesto un valore, ma potrebbe essere utilizzato come l'operando di sinistra di un operatore virgola in cui la do { ... } while (0)
versione non può essere eseguita. Se pensi che dovresti essere in grado di incorporare il codice di debug in tali espressioni, potresti preferire questo. Se si preferisce richiedere che la stampa di debug funzioni come un'istruzione completa, la do { ... } while (0)
versione è migliore. Nota che se il corpo della macro ha coinvolto dei punti e virgola (approssimativamente parlando), puoi usare solo la do { ... } while(0)
notazione. Funziona sempre; il meccanismo di dichiarazione di espressione può essere più difficile da applicare. Potresti anche ricevere avvisi dal compilatore con il modulo di espressione che preferiresti evitare; dipenderà dal compilatore e dai flag che usi.
TPOP era precedentemente su http://plan9.bell-labs.com/cm/cs/tpop e http://cm.bell-labs.com/cm/cs/tpop ma entrambi lo sono ora (2015-08-10) rotto.
Codice in GitHub
Se siete curiosi, si può guardare a questo codice in GitHub nei miei SOQ (Stack Overflow domande) repository come file debug.c
, debug.h
e mddebug.c
nella
/ libsoq src
sub-directory.