#define macro per la stampa di debug in C?


209

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

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

Come si ottiene ciò con una macro?


Il compilatore (gcc) ottimizzerà le istruzioni come se (DEBUG) {...} out, se nel codice di produzione la macro DEBUG è impostata su 0? Comprendo che ci sono buoni motivi per lasciare le dichiarazioni di debug visibili al compilatore, ma rimane una brutta sensazione. -Pat
Pat

Risposte:


410

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_printfunziona 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 whilequi?

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 ifdichiarazione 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 elseora è 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.he mddebug.cnella / libsoq src sub-directory.


1
Penso che l'approccio GCC ## - tratto da gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html sarebbe degno di nota nella sezione "Variante C99 a argomento singolo".
Hugo Ideler,

2
Anni dopo, e questa risposta è ancora la più utile di tutte le Internet, su come alias printk! vfprintf non funziona nello spazio del kernel poiché stdio non è disponibile. Grazie! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
Kevin Inf

6
Nel tuo esempio con le parole chiave __FILE__, __LINE__, __func__, __VA_ARGS__, non verrà compilato se non si hanno parametri printf, ad esempio se si chiama semplicemente debug_print("Some msg\n"); È possibile risolvere questo problema utilizzando fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); Il ## __ VA_ARGS__ consente di non passare parametri alla funzione.
mc_electron,

1
@LogicTom: la differenza è tra #define debug_print(fmt, ...)e #define debug_print(...). Il primo richiede almeno un argomento, il formato stringa ( fmt) e zero o più altri argomenti; il secondo richiede zero o più argomenti in totale. Se lo usi debug_print()con il primo, ricevi un errore dal preprocessore sull'uso improprio della macro, mentre il secondo no. Tuttavia, ricevi ancora errori di compilazione perché il testo di sostituzione non è valido C. Quindi, in realtà non fa molta differenza, quindi l'uso del termine "controllo limitato".
Jonathan Leffler,

1
La variante mostrata sopra, @ St.Antario, utilizza un singolo livello di debug attivo nell'intera applicazione e di solito utilizzo le opzioni della riga di comando per consentire l'impostazione del livello di debug quando il programma viene eseguito. Ho anche una variante che riconosce diversi sottosistemi diversi, a ciascuno dei quali viene assegnato un nome e il proprio livello di debug, in modo da poter utilizzare -D input=4,macros=9,rules=2per impostare il livello di debug del sistema di input su 4, il sistema di macro su 9 (sottoposto a scrutinio intenso ) e il sistema di regole a 2. Vi sono infinite variazioni sul tema; usa quello che ti piace.
Jonathan Leffler,

28

Uso qualcosa del genere:

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

Quindi uso solo D come prefisso:

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

Il compilatore vede il codice di debug, non c'è nessun problema di virgola e funziona ovunque. Funziona anche quando printfnon è sufficiente, ad esempio quando è necessario scaricare un array o calcolare un valore di diagnosi ridondante per il programma stesso.

EDIT: Ok, potrebbe generare un problema quando c'è un elseposto vicino che può essere intercettato da questo iniettato if. Questa è una versione che la supera:

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

3
Per quanto riguarda for(;0;), potrebbe generare un problema quando si scrive qualcosa di simile D continue;o D break;.
ACcreator

1
Mi ha preso; sembra però molto improbabile che possa accadere per caso.
mbq,

11

Per un'implementazione portatile (ISO C90), è possibile utilizzare le doppie parentesi, come questa;

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

o (hackish, non lo consiglierei)

#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: per far pensare al preprocessore c'è solo un argomento, lasciando che _ si espanda in una fase successiva.
Marcin Koziuk,

10

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

9

Vorrei fare qualcosa del genere

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


Non mi piace l'idea di usare una macro all'interno di un test come bandiera. Potresti spiegare perché la stampa di debug dovrebbe essere sempre controllata?
LB40,

1
@Jonathan: se il codice viene eseguito solo in modalità debug, perché dovresti preoccuparti se viene compilato in modalità non debug? assert()dallo stdlib funziona allo stesso modo e normalmente riutilizzo la NDEBUGmacro per il mio codice di debug ...
Christoph,

usando DEBUG nel test, se qualcuno esegue un DEBUG incontrollato non controllato, il codice non viene più compilato. giusto ?
LB40,

4
È frustrante abilitare il debug e quindi eseguire il debug del codice di debug perché si riferisce a variabili che sono state rinominate o riscritte, ecc. Se il compilatore (post-pre-processore) vede sempre l'istruzione print, garantisce che eventuali modifiche circostanti abbiano non invalidata 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 - plan9.bell-labs.com/cm/cs/tpop .
Jonathan Leffler,

1
@Christoph: beh, in un certo senso ... Uso NDEBUG per controllare solo le asserzioni e una macro separata (di solito DEBUG) per controllare la traccia di debug. 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 direttamente fprintf (), chiamo una funzione di stampa di debug che stampa solo in modo condizionale, quindi la stessa build del il codice può essere stampato o meno in base alle opzioni del programma). Sto sostenendo che per tutte le build, il compilatore dovrebbe vedere le dichiarazioni diagnostiche; tuttavia, non genererà codice se non è abilitato il debug.
Jonathan Leffler,

8

Secondo http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , dovrebbe esserci un ##precedente __VA_ARGS__.

In caso contrario, una macro #define dbg_print(format, ...) printf(format, __VA_ARGS__)non verrà compilato il seguente esempio: dbg_print("hello world");.


1
Benvenuto in Stack Overflow. Hai ragione che GCC ha l'estensione non standard a cui fai riferimento. La risposta attualmente accettata menziona infatti questo, incluso esattamente l'URL di riferimento che dai.
Jonathan Leffler,

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

Quale versione di C supporta tale notazione? E, se ha funzionato, il token incollando tutti gli argomenti in questo modo significa che hai solo un set molto limitato di opzioni per la stringa di formato, non è vero?
Jonathan Leffler,

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

1
OK - concordato: è documentato come una vecchia estensione GNU (sezione 5.17 del manuale GCC 4.4.1). Ma dovresti probabilmente documentare che funzionerà solo con GCC - o forse l'abbiamo fatto tra noi in questi commenti.
Jonathan Leffler,

1
La mia intenzione era di mostrare un altro stile di utilizzo di args e principalmente di dimostrare l'uso di FUNCTION e LINE
eyalm,

2

Questo è quello che uso:

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

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


È meglio che il compilatore controlli sempre il codice di debug.
Jonathan Leffler

1

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

var_dump("%d", count);

produce output come:

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

Ringraziamo @ "Jonathan Leffler". Tutti sono felici di C89:

Codice

#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

Quindi, quando utilizzo gcc, mi piace:

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

Perché può essere inserito nel codice.

Supponiamo di provare a eseguire il debug

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

720

Quindi puoi cambiarlo in:

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

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

E puoi ottenere un'analisi di quale espressione è stata valutata su cosa.

È protetto contro il problema della doppia valutazione, ma l'assenza di palestre lo rende aperto alle collisioni di nomi.

Tuttavia nidifica:

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

Quindi penso che fino a quando eviterai di usare g2rE3 come nome di una variabile, starai bene.

Certamente l'ho trovato (e le versioni alleate per le stringhe e le versioni per i livelli di debug ecc.) Inestimabili.


1

Ho stufato su come farlo per anni e finalmente ho trovato una soluzione. Tuttavia, non sapevo che esistessero già altre soluzioni qui. Innanzitutto, a differenza della risposta di Leffler , non vedo la sua tesi secondo cui le stampe di debug dovrebbero essere sempre compilate. Preferirei non avere tonnellate di codice non necessario in esecuzione nel mio progetto, quando non necessario, nei casi in cui ho bisogno di testare e potrebbero non essere ottimizzati.

Non compilare ogni volta potrebbe sembrare peggio di quanto non sia nella pratica. Ti ritrovi con stampe di debug che non vengono compilate a volte, ma non è così difficile compilarle e testarle prima di finalizzare un progetto. Con questo sistema, se stai utilizzando tre livelli di debug, inseriscilo nel messaggio di debug a livello tre, correggi gli errori di compilazione e controlla eventuali altri prima di finalizzare il tuo codice. (Dal momento che, naturalmente, la compilazione delle dichiarazioni di debug non garantisce che funzionino ancora come previsto.)

La mia soluzione prevede anche livelli di dettaglio di debug; e se lo imposti al livello più alto, vengono compilati tutti. Se di recente hai utilizzato un livello di dettaglio di debug elevato, sono stati tutti in grado di compilare in quel momento. Gli aggiornamenti finali dovrebbero essere abbastanza facili. Non ho mai avuto bisogno di più di tre livelli, ma Jonathan dice che ne ha usati nove. Questo metodo (come quello di Leffler) può essere esteso a qualsiasi numero di livelli. L'uso del mio metodo potrebbe essere più semplice; che richiedono solo due istruzioni quando utilizzato nel codice. Tuttavia, sto anche codificando la macro CLOSE, sebbene non faccia nulla. Potrebbe essere se stavo inviando a un file.

A fronte del costo, il passaggio aggiuntivo di testarli per verificare che verranno compilati prima della consegna è quello

  1. Devi fidarti di loro per ottenere l'ottimizzazione, il che, di certo, DOVREBBE accadere se si dispone di un livello di ottimizzazione sufficiente.
  2. Inoltre, probabilmente non lo faranno se si compila una compilation di rilascio con l'ottimizzazione disattivata a scopo di test (che è certamente raro); e quasi certamente non lo faranno affatto durante il debug - eseguendo in tal modo dozzine o centinaia di istruzioni "if (DEBUG)" in fase di esecuzione; rallentando così l'esecuzione (che è la mia obiezione principale) e, cosa ancora più importante, aumentando le dimensioni del file eseguibile o della dll; e quindi i tempi di esecuzione e compilazione. Jonathan, tuttavia, mi informa che il suo metodo può essere fatto anche per non compilare dichiarazioni.

Le filiali sono in realtà relativamente piuttosto costose nei moderni processori pre-fetching. Forse non è un grosso problema se la tua app non è critica in termini di tempo; ma se le prestazioni sono un problema, allora sì, un affare abbastanza grande che preferirei optare per un codice di debug con esecuzione leggermente più veloce (e possibilmente rilascio 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 debug, in modo che, ad esempio, se volevo che parti del codice cruciali per le prestazioni non venissero stampate in alcune occasioni, ma per stampare in altri, avrei potuto impostare un livello di debug e fare in modo che le stampe di debug extra iniziassero. si è imbattuto in un modo per implementare i livelli di debug che hanno determinato se la stampa è stata persino 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 <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);
}

Usando le 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();

sebbene attualmente questo non sia nemmeno necessario, tecnicamente parlando, in quanto non fa nulla. Sto ancora usando il CLOSE in questo momento, tuttavia, nel caso in cui cambi idea su come funziona e voglio lasciare il file aperto tra le istruzioni di registrazione.

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

#define DEBUG 1

Per fare in modo che le istruzioni di registrazione non vengano compilate in nulla, fare

#define DEBUG 0

Se hai bisogno di informazioni da un pezzo di codice eseguito frequentemente (cioè un livello elevato di dettagli), potresti voler scrivere:

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

Se si definisce DEBUG come 3, registrare i livelli 1, 2 e 3. Se lo si imposta su 2, si ottengono i livelli di registrazione 1 e 2. Se lo si imposta su 1, si ottengono solo le istruzioni del livello di registrazione 1.

Per quanto riguarda il ciclo do-while, poiché questo valuta una singola funzione o nulla, anziché un'istruzione if, il ciclo non è necessario. OK, castigami per aver usato C invece di C ++ IO (e QString :: arg () di Qt è un modo più sicuro di formattare le variabili anche in Qt - è piuttosto fluido, ma richiede più codice e la documentazione di formattazione non è 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 classe, ma allora dovresti istanziarlo e tenerlo aggiornato, oppure fare un nuovo () e archiviarlo. In questo modo, rilascia semplicemente #include, init e facoltativamente chiudi le istruzioni nella tua fonte e sei pronto per iniziare a usarlo. Sarebbe una classe eccellente, tuttavia, se sei così propenso.

In precedenza avevo visto molte soluzioni, ma nessuna era adatta ai miei criteri, oltre a questo.

  1. Può essere esteso per fare tutti i livelli che desideri.
  2. Non si compila in nulla se non in stampa.
  3. Centralizza IO in un posto facile da modificare.
  4. È flessibile, utilizzando la formattazione di printf.
  5. Ancora una volta, non rallenta le esecuzioni di debug, mentre le stampe di debug sempre in compilazione vengono sempre eseguite in modalità debug. Se stai facendo l'informatica e non è più facile scrivere l'elaborazione delle informazioni, potresti trovarti a eseguire un simulatore che consuma CPU, per vedere ad esempio dove il debugger lo interrompe con un indice fuori portata per un vettore. Questi funzionano già molto lentamente in modalità debug. L'esecuzione obbligatoria di centinaia di stampe di debug rallenterà necessariamente ulteriormente tali cicli. Per me, tali piste non sono rare.

Non tremendamente significativo, ma in aggiunta:

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

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

Potresti voler usare un nome di simbolo del preprocessore diverso da DEBUG, poiché alcune fonti definiscono anche quel simbolo (es. Progs che usano i ./configurecomandi per prepararsi alla costruzione). Mi è sembrato naturale quando l'ho sviluppato. L'ho sviluppato in un'applicazione in cui la DLL viene utilizzata da qualcos'altro ed è più opportuno inviare stampe di log in un file; ma cambiarlo in vprintf () funzionerebbe anche bene.

Spero che questo risparmierà a molti di voi il dispiacere nel capire il modo migliore per eseguire il log di debug; o ti mostra quello che potresti preferire. Ho tentato con tutto il cuore di capirlo per decenni. Funziona in MSVC 2012 e 2015, e quindi probabilmente su gcc; oltre a lavorare probabilmente su molti altri, ma non l'ho provato su di essi.

Intendo fare anche una versione in streaming di questo un giorno.

Nota: grazie a Leffler, che mi ha aiutato cordialmente a formattare meglio il mio messaggio per StackOverflow.


2
Dici "eseguendo dozzine o centinaia di if (DEBUG)istruzioni in fase di esecuzione, che non vengono ottimizzate", che si inclina nei mulini a vento . L'intero punto del sistema che ho descritto è che il codice è controllato dal compilatore (importante e automatico - non è richiesta alcuna build speciale) ma il codice di debug non viene generato affatto perché è ottimizzato (quindi non ha alcun impatto sul runtime su dimensione o prestazione del codice perché il codice non è presente in fase di esecuzione).
Jonathan Leffler,

Jonathan Leffler: Grazie per aver sottolineato le mie parole sbagliate. Ho lasciato correre i miei pensieri più velocemente delle mie dita, essendo così felice di averlo finito. Ho rivisto le mie obiezioni con "... 1) devi fidarti di loro per essere ottimizzato, cosa che dovrebbe succedere se hai un livello di ottimizzazione sufficiente. 2) Inoltre, non lo faranno se fai una compilazione di una release con l'ottimizzazione disattivato a scopo di test; e probabilmente non lo faranno affatto durante il debug - eseguendo in tal modo dozzine o centinaia di istruzioni "if (DEBUG)" in fase di esecuzione - aumentando così le dimensioni del file eseguibile o della dll e i tempi di esecuzione ".
CodeLurker

Affinché il tuo possa fare l'altra cosa importante che sta facendo la mia, dovresti avere livelli di debug. Anche se spesso non ho bisogno di accenderne molte, alcune applicazioni traggono davvero vantaggio dalla possibilità di ottenere un livello elevato di dettagli su un ciclo critico in termini di tempo con un semplice "#define DEBUG 3", quindi tornare a informazioni molto meno dettagliate con "#define DEBUG 1". Non ho mai avuto bisogno di più di tre livelli e, quindi, almeno all'incirca 1/3 dei miei debug si compilano già al momento del rilascio. Se ho usato il livello 3 di recente, probabilmente TUTTI lo fanno.
CodeLurker,

YMMV. Il moderno sistema che ho mostrato supporta l'impostazione dinamica (runtime) dei livelli di debug, quindi puoi decidere a livello di programmazione quanta parte del debug viene prodotta in fase di runtime. Di solito ho usato i livelli 1-9, anche se non esiste un limite superiore (o limite inferiore; il livello predefinito è 0 che di solito è disattivato, ma può essere esplicitamente richiesto durante lo sviluppo attivo se appropriato - non è appropriato per il lavoro a lungo termine). Ho scelto un livello predefinito di 3; le cose possono essere sintonizzate. Questo mi dà molto controllo. Se davvero non vuoi testare il codice di debug quando è inattivo, cambia l'alternativa a ((void)0): è facile.
Jonathan Leffler

1
Ahh. Avrebbe aiutato ad avere letto tutto. È un post piuttosto lungo. Penso che finora abbia ottenuto i punti essenziali. Si scopre che il tuo, come il mio, può essere utilizzato per compilare o non compilare tutte le stampe di debug e può supportare i livelli; anche se è vero, il tuo può compilare i livelli che non stai utilizzando - a un costo durante il debug.
CodeLurker

0

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 32 KB e la memoria dinamica è limitata a 2 KB. L'aggiunta di istruzioni di debug e la traccia delle stringhe di debug occupano rapidamente spazio. Quindi è essenziale essere in grado di limitare la traccia di debug 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

chiamando il file .cpp

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

...
PRINT(4, "Time out error,\t");
...
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.