Domanda Macro DEBUG in C ++


Ho appena incontrato una macro DEBUG in C che mi piace molto

#ifdef DEBUG_BUILD
#  define DEBUG(x) fprintf(stderr, x)
#else
#  define DEBUG(x) do {} while (0)
#endif

Sto indovinando un analogo C ++ sarebbe: -

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  1. Il secondo snippet di codice è analogo a quello in C?
  2. Hai qualche macro di debug C ++ preferita?

MODIFICARE : Con "Debug Macro" intendo "macro che potrebbero tornare utili durante l'esecuzione di un programma in modalità di debug".


44
2018-01-10 04:53


origine


risposte:


Il secondo snippet di codice è analogo a quello in C?

Più o meno. È più potente, come puoi includere <<-separati valori nell'argomento, quindi con un singolo argomento si ottiene qualcosa che richiederebbe un numero variabile di argomenti macro in C. D'altra parte, c'è una sottile possibilità che le persone ne abusino includendo un punto e virgola nell'argomento. O ancora errori di encouter a causa di un punto e virgola dimenticato dopo la chiamata. Quindi includerei questo in un blocco do:

#define DEBUG(x) do { std::cerr << x; } while (0)

Hai qualche macro di debug C ++ preferita?

Mi piace quello sopra, e lo uso abbastanza spesso. Il mio no-op di solito legge solo

#define DEBUG(x)

che ha lo stesso effetto per l'ottimizzazione dei compilatori. Sebbene il commento di @Tony D qui sotto sia corret: questo può lasciare inosservati alcuni errori di sintassi.

A volte includo anche un controllo run-time, fornendo in tal modo qualche forma di flag di debug. Come mi ha ricordato @Tony D, anche avere un endl in è spesso utile.

#define DEBUG(x) do { \
  if (debugging_enabled) { std::cerr << x << std::endl; } \
} while (0)

A volte voglio anche stampare l'espressione:

#define DEBUG2(x) do { std::cerr << #x << ": " << x << std::endl; } while (0)

In alcune macro, mi piace includere __FILE__, __LINE__ o __func__, ma queste sono asserzioni più frequenti e non semplici macro di debug.


35
2018-01-10 10:56



Ecco il mio preferito

#ifdef DEBUG 
#define D(x) x
#else 
#define D(x)
#endif

È super pratico e rende il codice pulito (e, soprattutto, veloce in modalità di rilascio !!).

Molti #ifdef DEBUG_BUILD blocchi dappertutto (per filtrare i blocchi di codice relativi al debug) è piuttosto brutto, ma non così male quando si avvolgono alcune righe con un D().

Come usare:

D(cerr << "oopsie";)

Se è ancora troppo brutto / strano / a lungo per te,

#ifdef DEBUG
#define DEBUG_STDERR(x) (std::cerr << (x))
#define DEBUG_STDOUT(x) (std::cout << (x))
//... etc
#else 
#define DEBUG_STDERR(x)
#define DEBUG_STDOUT(x)
//... etc
#endif

(IO suggerire non usare using namespace std; anche se forse using std::cout; using std::cerr; potrebbe essere una buona idea)

Nota che potresti voler fare più cose piuttosto che stampare su stderr quando si pensa al "debug". Diventa creativo e costruisci costrutti che offrono informazioni sulle interazioni più complesse all'interno del tuo programma, consentendo al tempo stesso di passare rapidamente alla creazione di una versione super efficiente non ingombrante dalla strumentazione di debug.

Ad esempio, in uno dei miei progetti recenti ho avuto un enorme blocco di debug che è iniziato con FILE* file = fopen("debug_graph.dot"); e proceduto a scaricare un graphviz grafico compatibile in formato punto per visualizzare alberi di grandi dimensioni all'interno dei miei dati. Ciò che è ancora più interessante è che il client graphviz OS X legge automaticamente il file dal disco quando cambia, quindi il grafico si aggiorna ogni volta che viene eseguito il programma!

Mi piace anche in particolare "estendere" classi / struct con membri e funzioni debug-only. Questo apre la possibilità di implementare funzionalità e stato che è lì per aiutarti a rintracciare i bug e, proprio come tutto il resto che è avvolto nelle macro di debug, viene rimosso cambiando un parametro di build. Una gigantesca routine che controlla scrupolosamente ogni caso d'angolo su ogni aggiornamento di stato? Non è un problema. Slap a D()intorno ad esso. Una volta che vedi funziona, rimuovi -DDEBUG dallo script di compilazione, ad esempio il build per il rilascio, ed è sparito, pronto per essere riattivato in un momento preciso per i test dell'unità o per quello che hai.

Un grande esempio, un po 'completo, per illustrare (un uso forse un po' troppo zelante) di questo concetto:

#ifdef DEBUG
#  define D(x) x
#else
#  define D(x)
#endif // DEBUG

#ifdef UNITTEST
#  include <UnitTest++/UnitTest++.h>
#  define U(x) x // same concept as D(x) macro.
#  define N(x)
#else
#  define U(x)
#  define N(x) x // N(x) macro performs the opposite of U(x)
#endif

struct Component; // fwd decls
typedef std::list<Component> compList;

// represents a node in the graph. Components group GNs
// into manageable chunks (which turn into matrices which is why we want
// graph component partitioning: to minimize matrix size)
struct GraphNode {
    U(Component* comp;) // this guy only exists in unit test build
    std::vector<int> adj; // neighbor list: These are indices
    // into the node_list buffer (used to be GN*)
    uint64_t h_i; // heap index value
    U(int helper;) // dangling variable for search algo to use (comp node idx)
    // todo: use a more space-efficient neighbor container?
    U(GraphNode(uint64_t i, Component* c, int first_edge):)
    N(GraphNode(uint64_t i, int first_edge):)
        h_i(i) {
        U(comp = c;)
        U(helper = -1;)
        adj.push_back(first_edge);
    }
    U(GraphNode(uint64_t i, Component* c):)
    N(GraphNode(uint64_t i):)
        h_i(i)
    {
        U(comp=c;)
        U(helper=-1;)
    }
    inline void add(int n) {
        adj.push_back(n);
    }
};

// A component is a ugraph component which represents a set of rows that
// can potentially be assembled into one wall.
struct Component {
#ifdef UNITTEST // is an actual real struct only when testing
    int one_node; // any node! idx in node_list (used to be GN*)
    Component* actual_component;
    compList::iterator graph_components_iterator_for_myself; // must be init'd
    // actual component refers to how merging causes a tree of comps to be
    // made. This allows the determination of which component a particular
    // given node belongs to a log-time operation rather than a linear one.

    D(int count;) // how many nodes I (should) have

    Component(): one_node(-1), actual_component(NULL) {
        D(count = 0;)
    }
#endif
};

#ifdef DEBUG
// a global pointer to the node list that makes it a little
// easier to reference it
std::vector<GraphNode> *node_list_ptr;

#  ifdef UNITTEST
std::ostream& operator<<(std::ostream& os, const Component& c) {
    os << "<s=" << c.count << ": 1_n=" << node_list_ptr->at(c.one_node).h_i;
    if (c.actual_component) {
        os << " ref=[" << *c.actual_component << "]";
    }
    os << ">";
    return os;
}
#  endif
#endif

Si noti che per grandi blocchi di codice, io uso solo un blocco regolare #ifdef condizionali perché ciò migliora in qualche modo la leggibilità, poiché per i blocchi di grandi dimensioni l'uso di macro estremamente brevi è più di un ostacolo!

Il motivo per cui il N(x) la macro deve esistere è specificare cosa fare Inserisci quando è il test unitario Disabilitato.

In questa parte:

U(GraphNode(uint64_t i, Component* c, int first_edge):)
N(GraphNode(uint64_t i, int first_edge):)

Sarebbe bello se potessimo dire qualcosa del genere

GraphNode(uint64_t i, U(Component* c,) int first_edge):

Ma non possiamo, perché la virgola è una parte della sintassi del preprocessore. L'omissione della virgola produce una sintassi C ++ non valida.

Se avessi qualche codice aggiuntivo per quando non compilando per il debug, è possibile utilizzare questo tipo di macro inverse-debug corrispondente.

Ora questo codice potrebbe non essere un esempio di "codice veramente buono" ma illustra alcune delle cose che è possibile realizzare con l'applicazione intelligente di macro, che se si rimane disciplinati, non sono necessariamente il male.

Mi sono imbattuto questa gemma solo ora dopo aver pensato al do{} while(0) roba, e tu vuoi davvero tutta quella follia in questi macros!

Spero che il mio esempio possa fornire alcune informazioni su almeno alcune delle cose intelligenti che possono essere fatte per migliorare il vostro codice C ++. È davvero prezioso per il codice dello strumento mentre lo scrivi piuttosto che tornare a farlo quando non capisci cosa sta succedendo. Ma è sempre un equilibrio che devi trovare tra renderlo solido e farlo in tempo.

Mi piace pensare a ulteriori controlli di sanità di debug build come uno strumento diverso nella toolbox, simile ai test di unità. Secondo me, potrebbero essere ancora più potenti, perché piuttosto che mettere la logica del controllo di integrità nei test unitari e isolarli dall'implementazione, se sono inclusi nell'implementazione e possono essere evocati a piacere, allora i test completi non sono necessari perché puoi semplicemente abilitare i controlli ed eseguire le cose come al solito, in un pizzico.


29
2018-01-10 05:18



Per la domanda 1] La risposta è sì. Stamperà semplicemente il messaggio al flusso di errore standard.

Per la domanda 2] Ce ne sono molti. Il mio Fav è

#define LOG_ERR(...) fprintf(stderr, __VA_ARGS__)

che permetterà di includere un numero arbitrario di variabili da includere nel messaggio di debug.


8
2018-01-10 05:22



Mi piace usare le macro con __LINE__, __FILE__ come argomenti da mostrare dove nel codice da cui proviene la stampa - non è raro stampare lo stesso nome di variabile in più punti, quindi fprintf(stderr, "x=%d", x); non significa molto se poi aggiungi un altro le stesse dieci righe più in basso.

Ho anche usato macro che sovrascrivono alcune funzioni e memorizzano da dove è stato chiamato (ad esempio allocazioni di memoria), in modo che più avanti, posso capire quale trapelato. Per l'allocazione della memoria, è un po 'più difficile in C ++, dato che tendi ad usare new / delete, e non possono essere facilmente sostituiti, ma altre risorse come le operazioni di lock / unlock possono essere molto utili per tracciare in questo modo [ovviamente, se hai un wrapper con blocco che usa costruzione / distruzione come un buon programmatore C ++, lo aggiungerei al costruttore per aggiungere file / linea alla struttura interna una volta che hai acquisito il blocco, e puoi vedere dove si svolge altrove quando non puoi acquistarlo da qualche parte].


8
2018-01-10 09:26



Questa è la macro di registro che sto usando attualmente:

#ifndef DEBUG 
#define DEBUG 1 // set debug mode
#endif

#if DEBUG
#define log(...) {\
    char str[100];\
    sprintf(str, __VA_ARGS__);\
    std::cout << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << str << std::endl;\
    }
#else
#define log(...)
#endif

Uso:

log(">>> test...");

Produzione:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...

6
2017-07-01 04:44



... e come addendum a tutte le risposte:

Personalmente non uso mai macro come DEBUG al debug distinto dal codice di rilascio, invece io uso NDEBUG che è deve essere definito per il rilascio costruisce per eliminare assert() chiama (sì, io uso assert() ampiamente). E se quest'ultimo non è definito, allora è una build di debug. Facile! Quindi, in realtà non c'è motivo di introdurre un'altra macro di debug! (e gestisci possibili casi in cui DEBUG e NDEBUG entrambi non sono definiti).


4
2018-06-04 13:51



Io uso il codice qui sotto per la registrazione. Ci sono alcuni vantaggi:

  1. Posso accenderli / spegnerli in runtime.
  2. Posso compilare le dichiarazioni a un particolare livello di registro. Ad esempio, al momento, ho compilato incondizionatamente il KIMI_PRIVATE macro perché sto eseguendo il debug di qualcosa nella build di rilascio ma dal momento che c'è un sacco di roba potenzialmente segreta che viene registrata (lol), la compilo da build di release.

Questo schema mi ha servito molto bene nel corso degli anni. Nota: sebbene ci sia un globale logMessage funzione, il codice di solito accoda il log a un thread di registrazione.

#define KIMI_LOG_INTERNAL(level,EXPR)           \
  if(kimi::Logger::loggingEnabled(level))       \
  {                                             \
    std::ostringstream os;                      \
    os << EXPR;                                 \
    kimi::Logger::logMessage(level ,os.str());  \
  }                                             \
  else (void) 0

#define KIMI_LOG(THELEVEL,EXPR)                 \
  KIMI_LOG_INTERNAL(kimi::Logger::LEVEL_ ## THELEVEL,EXPR)

#define KIMI_ERROR(EXPR)   KIMI_LOG(ERROR,EXPR)
#define KIMI_VERBOSE(EXPR) KIMI_LOG(VERBOSE,EXPR)
#define KIMI_TRACE(EXPR)   KIMI_LOG(TRACE,EXPR)
#define KIMI_INFO(EXPR)    KIMI_LOG(INFO,EXPR)
#define KIMI_PROFILE(EXPR) KIMI_LOG(TRACE,EXPR)

// Use KIMI_PRIVATE for sensitive tracing
//#if defined(_DEBUG)
#  define KIMI_PRIVATE(EXPR) KIMI_LOG(PRIVATE,EXPR)
// #else
// #  define KIMI_PRIVATE(EXPR) (void)0
// #endif

3
2018-06-03 04:42



Questa è la mia versione, usando un modello variadic print funzione:

template<typename... ArgTypes>
inline void print(ArgTypes... args)
{
  // trick to expand variadic argument pack without recursion
  using expand_variadic_pack = int[];
  // first zero is to prevent empty braced-init-list
  // void() is to prevent overloaded operator, messing things up
  // trick is to use the side effect of list-initializer to call a function
  // on every argument.
  // (void) is to suppress "statement has no effect" warnings
  (void)expand_variadic_pack{0, ((cout << args), void(), 0)... };
}

#ifndef MYDEBUG
#define debug_print(...)
#else
#define debug_print(...) print(__VA_ARGS__)
#endif

La versione I rende il debug_print una funzione di modello variadic che accetta un livello di debug che mi permette di selezionare quale tipo di output voglio produrre in fase di runtime:

template<typename... ArgTypes>
inline void debug_print(debug::debug level, ArgTypes... args)
{
  if(0 != (debug::level & level))
    print(args...);
}

Notare la print la funzione si blocca Visual Studio 2013 Preview (non ho testato il RC). Ho notato che è più veloce (su Windows, dove l'output della console è lento) rispetto alla mia precedente soluzione che usava un ostream classe di bambino che ha sovraccaricato operator<<.

Puoi anche usare un temporaneo stringstream dentro print se si desidera chiamare la funzione di output reale una sola volta (o scrivere il proprio carattere tipografico) printf ;-))


3
2017-09-30 15:48



Io uso i seguenti micro,

#if DEBUG
#define LOGE2(x,y) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y <<std::endl;
#define LOGI2(x,y) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGD2(x,y) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x <<":"<< y << std::endl;
#define LOGE(x) std::cout << "ERRO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGI(x) std::cout << "INFO : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#define LOGD(x) std::cout << "DEBG : " << "[" << __FILE__ << "][" << __FUNCTION__ << "][Line " << __LINE__ << "] " << x << std::endl;
#else
#define LOGE2(x,y) NULL
#define LOGI2(x,y) NULL
#define LOGD2(x,y) NULL
#define LOGE(x) NULL
#define LOGI(x) NULL
#define LOGD(x) NULL
#endif

USO:

LOGE("ERROR.");
LOGE2("ERROR1","ERROR2");

0
2018-02-01 14:54