C # definisce la macro per la stampa di debug

Cercando di creare una macro che può essere utilizzata per i messaggi di debug di stampa quando DEBUG è definito, come il seguente pseudo codice:

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

Come viene realizzato con una macro?

Se usi un compilatore C99

 #define debug_print(fmt, ...) \ do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0) 

Si presuppone che si stia utilizzando C99 (la notazione elenco argomento variabile non è supportata nelle versioni precedenti). L’ do { ... } while (0) garantisce che il codice funzioni come un’istruzione (chiamata di funzione). L’uso incondizionato del codice garantisce che il compilatore verifichi sempre che il codice di debug sia valido, ma l’ottimizzatore rimuoverà il codice quando DEBUG è 0.

Se vuoi lavorare con #ifdef DEBUG, modifica la condizione di test:

 #ifdef DEBUG #define DEBUG_TEST 1 #else #define DEBUG_TEST 0 #endif 

E quindi utilizzare 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 elementi 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) 

Ciò si basa sulla concatenazione di stringhe per creare una stringa di formato più grande rispetto alle scritture del programmatore.

Se si utilizza un compilatore C89

Se sei bloccato con C89 e nessuna estensione del compilatore utile, allora non c’è 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 – ed è per questo che hai la notazione divertente nell’espansione macro. Come in precedenza, il compilatore verifica sempre il codice per la validità sintattica (che è buona), ma l’ottimizzatore invoca solo la funzione di stampa se la macro DEBUG restituisce un valore diverso da zero.

Ciò richiede una funzione di supporto – dbg_printf () nell’esempio – per gestire cose come “stderr”. Ti richiede di sapere come scrivere le funzioni varargs, ma non è difficile:

 #include  #include  void dbg_printf(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } 

È anche ansible utilizzare questa tecnica in C99, ovviamente, ma la tecnica __VA_ARGS__ è più __VA_ARGS__ perché utilizza la notazione regolare delle funzioni, non la __VA_ARGS__ parentesi doppie.

Perché è fondamentale che il compilatore veda sempre il codice di debug?

[ Rifletti i commenti fatti a un’altra risposta. ]

Un’idea centrale dietro entrambe le implementazioni C99 e C89 sopra è che il compilatore proprio vede sempre il debug di dichiarazioni printf-like. Questo è importante per codice a lungo termine – codice che durerà un decennio o due.

Supponiamo che un pezzo di codice sia rimasto per lo più dormiente (stabile) per un certo numero di anni, ma ora deve essere cambiato. Ritriggers la traccia di debug, ma è frustrante dover eseguire il debug del codice di debugging (tracciamento) perché fa riferimento a variabili che sono state rinominate o ridigitate, durante gli anni di manutenzione stabile. Se il compilatore (post pre-processore) vede sempre l’istruzione di stampa, garantisce che eventuali modifiche circostanti non abbiano invalidato la diagnostica. Se il compilatore non vede la dichiarazione di stampa, non può proteggerti contro la tua incuria (o la negligenza dei tuoi colleghi o collaboratori). Vedi ” La pratica della programmazione ” di Kernighan e Pike, in particolare il capitolo 8 (vedi anche Wikipedia su TPOP ).

Questo è ‘stato lì, fatto che’ esperienza – ho usato essenzialmente la tecnica descritta in altre risposte in cui la build non di debug non vede le dichiarazioni tipo printf per un certo numero di anni (più di un decennio). Ma ho trovato il consiglio in TPOP (vedi il mio commento precedente), e poi ho abilitato qualche codice di debug dopo un certo numero di anni, e ho incontrato problemi di contesto modificato che interrompevano il debugging. Diverse volte, avere la stampa sempre valida mi ha salvato dai problemi successivi.

Io uso NDEBUG per controllare solo le asserzioni e una macro separata (di solito DEBUG) per controllare se la traccia di debug è incorporata nel programma. Anche quando la traccia di debug è incorporata, 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 chiamare fprintf () direttamente, chiamo una funzione di stampa di debug che stampa condizionatamente solo così la stessa build del codice può stampare o non stampare in base alle opzioni del programma). Ho anche una versione “multi-sottosistema” del codice per i programmi più grandi, in modo che io possa avere diverse sezioni del programma che producono quantità diverse di traccia – sotto 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 di debug a meno che il debug non sia abilitato. Fondamentalmente, significa che tutto il tuo 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  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 C99 a un solo argomento

Kyle Brandt ha chiesto:

Comunque, per fare questo, 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"); 

La soluzione solo GCC fornisce anche il supporto per questo.

Tuttavia, puoi farlo con il sistema C99 dritto utilizzando:

 #define debug_print(...) \ do { if (DEBUG) fprintf(stderr, __VA_ARGS__); } while (0) 

Rispetto alla prima versione, si perde il controllo limitato che richiede l’argomento ‘fmt’, il che significa che qualcuno potrebbe chiamare ‘debug_print ()’ senza argomenti. È discutibile se la perdita di controllo sia un problema.

Tecnica specifica per GCC

Alcuni compilatori possono offrire estensioni per altri modi di gestire elenchi di argomenti di lunghezza variabile nelle macro. In particolare, come notato per la prima volta nei commenti di Hugo Ideler , GCC consente di omettere la virgola che normalmente compare dopo l’ultimo argomento ‘fisso’ della macro. Consente inoltre di utilizzare ##__VA_ARGS__ nel testo di sostituzione macro, che elimina la virgola precedente alla 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 accettando gli argomenti opzionali dopo il formato.

Questa tecnica è supportata anche da Clang per la compatibilità GCC.


Perché il ciclo del do-while?

Qual è lo scopo del do while qui?

Vuoi essere in grado di utilizzare la macro in modo che assomigli ad una chiamata di funzione, il che significa che sarà seguita da un punto e virgola. Pertanto, è necessario impacchettare il corpo della macro per adattarlo. Se si usa un’istruzione if senza l’ambiente circostante do { ... } while (0) , si avrà:

 /* BAD - BAD - BAD */ #define debug_print(...) \ if (DEBUG) fprintf(stderr, __VA_ARGS__) 

Ora, supponiamo di scrivere:

 if (x > y) debug_print("x (%d) > y (%d)\n", x, y); else do_something_useful(x, y); 

Sfortunatamente, tale indentazione non riflette il controllo effettivo del stream, perché il preprocessore produce un codice equivalente a questo (indentato e parentesi aggiunte per enfatizzare il significato effettivo):

 if (x > y) { if (DEBUG) fprintf(stderr, "x (%d) > y (%d)\n", x, y); else do_something_useful(x, y); } 

Il prossimo tentativo della 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 il else è ora un errore di syntax. Il ciclo do { ... } while(0) 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 cast (void) impedisce che venga utilizzato in contesti in cui è richiesto un valore, ma potrebbe essere utilizzato come operando di sinistra di un operatore virgola in cui la versione do { ... } while (0) non può. Se pensi che dovresti essere in grado di incorporare il codice di debug in tali espressioni, potresti preferirlo. Se si preferisce richiedere la stampa di debug per agire come una dichiarazione completa, quindi la versione do { ... } while (0) è migliore. Si noti che se il corpo della macro coinvolge un punto e virgola (in parole povere), allora si può usare solo la notazione do { ... } while(0) . 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 dalle bandiere che usi.


In precedenza, TPOP era su http://plan9.bell-labs.com/cm/cs/tpop e http://cm.bell-labs.com/cm/cs/tpop, ma entrambi sono ora (2015-08-10) rotto.


Codice in GitHub

Se sei curioso, puoi consultare questo codice in GitHub nel mio repository SOQ (Stack Overflow Questions) come file debug.c , debug.h e mddebug.c nella mddebug.c src / libsoq .

Io uso qualcosa come questo:

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

Quindi uso semplicemente D come prefisso:

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

Il compilatore vede il codice di debug, non c’è alcun problema con virgola e funziona ovunque. Funziona anche quando printf non è sufficiente, ad esempio quando è necessario scaricare un array o calcolare un valore diagnostico ridondante per il programma stesso.

EDIT: Ok, potrebbe generare un problema quando c’è da qualche parte vicino che può essere intercettato da questo iniettato if . Questa è una versione che la segue:

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

Per un’implementazione portatile (ISO C90), potresti usare doppie parentesi, come questa;

 #include  #include  #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; } 

o (hackish, non lo consiglierei)

 #include  #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; } 

Farei qualcosa di simile

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

Penso che questo sia più pulito.

Ecco la versione che uso:

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

Secondo http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , ci dovrebbe essere un ## prima di __VA_ARGS__ .

Altrimenti, una macro #define dbg_print(format, ...) printf(format, __VA_ARGS__) non compilerà il seguente esempio: dbg_print("hello world"); .

Questo è quello che uso:

 #if DBG #include  #define DBGPRINT printf #else #define DBGPRINT(...) /**/ #endif 

Ha il vantaggio di gestire correttamente printf, anche senza argomenti aggiuntivi. Nel caso di DBG == 0, anche il compilatore più stupido non ottiene nulla da masticare, quindi non viene generato alcun codice.

Il mio preferito di sotto è var_dump , che quando viene chiamato come:

var_dump("%d", count);

produce output come:

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

Credito a @ “Jonathan Leffler”. Tutti sono felici C89:

Codice

 #define DEBUG 1 #include  #include  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) 

Mi sono soffermato su come farlo per anni, e ho appena trovato questa soluzione prima di vedere questa domanda. Innanzitutto, a differenza della risposta di Leffler , non vedo la sua argomentazione che le stampe di debug debbano sempre essere compilate. Sì, si finisce con le stampe di debug che non vengono compilate a volte, ma non è così difficile compilarle e testarle prima di finalizzare un progetto. Diciamo che non lo fai. Il peggio che succede è che devi correggere qualche manciata su un grande progetto. Se il tuo progetto non è HUUUUGE, è più facile di così.

La mia soluzione fornisce livelli di dettaglio di debug; e se lo si imposta al livello più alto, vengono compilati tutti. Se recentemente hai utilizzato un elevato livello di dettagli di debug, probabilmente tutti sono già stati compilati. Non ho mai avuto bisogno di più di tre livelli, ma Jonathan dice che ne ha usati nove. Il suo metodo può anche avere livelli di dettagli di stampa di debug. Il mio può essere facilmente esteso per supportare qualsiasi numero di livelli, e il suo può supportare qualsiasi numero di livelli. L’utilizzo del mio metodo potrebbe essere più semplice; richiede solo due istruzioni quando vengono utilizzate nel codice (anche se sto usando anche la terza).

Contro il costo il passo extra di testarli per vedere che si compileranno prima della consegna, è quello

  1. Devi fidarti di loro per essere ottimizzato, il che dovrebbe DOVREBBE accadere se hai un livello di ottimizzazione sufficiente.
  2. Inoltre, probabilmente non lo faranno se compilate una versione con ottimizzazione distriggersta a scopo di test (cosa rara); e quasi certamente non lo faranno affatto durante il debug, eseguendo quindi dozzine o centinaia di istruzioni “if (DEBUG)” in fase di esecuzione; rallentando quindi l’esecuzione (che è la mia obiezione principale) e, cosa meno importante, aumentando la dimensione del file eseguibile o dll; e quindi tempi di esecuzione e compilazione. Jonathan, tuttavia, mi informa che il suo metodo può essere fatto anche per non compilare dichiarazioni.

I rami sono in realtà relativamente piuttosto costosi nei moderni processori di pre-fetching. Forse non è un grosso problema se la tua app non è di tipo time-critical; ma se le prestazioni sono un problema, allora sì, un affare abbastanza grande che preferirei optare per un codice di debug un po ‘più veloce (e probabilmente una versione più veloce, in rari casi, come notato).

Quindi, quello che volevo è una macro di stampa di debug che non viene compilata se non deve essere stampata, ma lo fa se lo è. Volevo anche livelli di debugging, in modo che, ad esempio, se volevo che le parti cruciali del codice delle prestazioni non venissero stampate in alcuni momentjs, ma per stamparle in altri, potrei impostare un livello di debug e fare in modo che le stampe extra di debug inizino. ha trovato un modo per implementare i livelli di debug che determinano se la stampa è stata compilata o meno. L’ho raggiunto in questo modo:

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  #include  #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); } 

Utilizzando i macro

Per usarlo, basta fare:

 DEBUGLOG_INIT("afile.log"); 

Per scrivere nel file di registro, basta fare:

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

Per chiuderlo, fai:

 DEBUGLOG_CLOSE(); 

anche se attualmente non è nemmeno necessario, tecnicamente parlando, poiché non fa nulla. In questo momento sto ancora utilizzando CLOSE, nel caso in cui cambi idea su come funziona e voglio lasciare il file aperto tra le dichiarazioni di registrazione.

Quindi, quando si desidera triggersre la stampa di debug, è sufficiente modificare il primo #define nel file di intestazione per dire, ad es

 #define DEBUG 1 

Per fare in modo che le dichiarazioni di registrazione vengano compilate a zero, fallo

 #define DEBUG 0 

Se hai bisogno di informazioni da una parte di codice eseguita di frequente (ovvero un livello elevato di dettagli), potresti scrivere:

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

Se si definisce DEBUG come 3, i livelli di registrazione 1, 2 e 3 vengono compilati. Se si imposta su 2, si ottengono i livelli di registrazione 1 e 2. Se si imposta su 1, si ottengono solo le dichiarazioni di livello 1 di registrazione.

Per quanto riguarda il ciclo do-while, poiché questo valuta una singola funzione o nulla, invece di un’istruzione if, il ciclo non è necessario. OK, castigatemi per usare C invece di C ++ IO (e Qt’s QString :: arg () è un modo più sicuro per formattare le variabili anche in Qt: è piuttosto scorrevole, ma richiede più codice e la documentazione di formattazione non è così organizzata come potrebbe essere – ma ho ancora trovato casi in cui è preferibile), ma puoi inserire qualsiasi codice nel file .cpp che desideri. Potrebbe anche essere una class, ma in tal caso è necessario creare un’istanza e mantenerla, oppure eseguire una nuova () e memorizzarla. In questo modo, devi semplicemente eliminare le istruzioni #include, init e opzionalmente chiuse nella tua fonte, e sei pronto per iniziare a usarlo. Sarebbe una bella lezione, tuttavia, se sei così inclinato.

In precedenza avevo visto molte soluzioni, ma nessuna si adattava ai miei criteri e anche a questo.

  1. Può essere esteso per fare tutti i livelli che vuoi.
  2. Non compila niente se non stampa.
  3. Centralizza l’IO in un posto facile da modificare.
  4. È flessibile, usando la formattazione di printf.

Inoltre,

  1. Non richiede alcun hack per stampare senza argomenti (es. ERRLOG_LOG(3, "got here!"); ); consentendo così di utilizzare, ad esempio la formattazione .arg () più sicura di Qt. Funziona su MSVC e, quindi, probabilmente su gcc. Usa ## nei #define s, che non è standard, come sottolinea Leffler, ma è ampiamente supportato. (Puoi ricodificarlo per non usare ## se necessario, ma dovrai usare un hack come lui fornisce).

Avvertenza: se si dimentica di fornire l’argomento del livello di registrazione, MSVC afferma inutilmente che l’identificatore non è definito.

È ansible che si desideri utilizzare un nome di simbolo del preprocessore diverso da DEBUG, in quanto alcune fonti definiscono anche tale simbolo (ad esempio progs che utilizza i comandi ./configure per prepararsi alla creazione). Mi è sembrato naturale quando l’ho sviluppato. L’ho sviluppato in un’applicazione in cui la DLL viene utilizzata da qualcos’altro, ed è più convessa l’invio di stampe di log su un file; ma cambiandolo in vprintf () funzionerebbe anche bene.

Spero che questo eviti molti di voi a proposito del modo migliore per eseguire il debug di registrazione; o ti mostra quello che potresti preferire. Ho cercato a malapena di capire questo per decenni. Funziona in MSVC 2012 e 2015, e quindi probabilmente su gcc; così come probabilmente lavorando su molti altri, ma non l’ho testato su di loro.

Credo che questa variazione del tema fornisca categorie di debug senza la necessità di avere un nome macro separato per categoria.

Ho usato questa variazione in un progetto Arduino in cui lo spazio del programma è limitato a 32K e la memoria dynamic è limitata a 2K. L’aggiunta di istruzioni di debug e stringhe di debug di traccia occupa rapidamente spazio. Quindi è essenziale essere in grado di limitare la traccia di debug che è inclusa in fase di compilazione al minimo necessario ogni volta che viene creato il codice.

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 

calling .cpp file

 #define DEBUG_MASK 0x06 #include "Debug.h" ... PRINT(4, "Time out error,\t"); ...