web-dev-qa-db-fra.com

Macros DEBUG en C++

Je viens de rencontrer une macro DEBUG en C que j'aime beaucoup

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

Je suppose qu'un analogue C++ serait: -

#ifdef DEBUG_BUILD
#  define DEBUG(x) cerr << x
#else
#  define DEBUG(x) do {} while (0)
#endif
  1. Le deuxième extrait de code est-il analogue à celui en C?
  2. Avez-vous des macros de débogage C++ préférées?

EDIT: Par "Macros de débogage", je veux dire "des macros pouvant être utiles lors de l’exécution d’un programme en mode débogage".

49
user277465

Le deuxième extrait de code est-il analogue à celui en C?

Plus ou moins. Il est plus puissant, car vous pouvez inclure des valeurs séparées par <<- dans l'argument. Ainsi, avec un seul argument, vous obtenez quelque chose qui nécessiterait un nombre variable d'arguments de macro en C. Par contre, il y a une faible chance que en abuser en incluant un point-virgule dans l'argument. Ou même rencontrer des erreurs dues à un point-virgule oublié après l'appel. Donc, j'inclurais ceci dans un bloc do:

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

Avez-vous des macros de débogage C++ préférées?

J'aime celle ci-dessus et l'utilise assez souvent. Mon no-op se lit généralement

#define DEBUG(x)

ce qui a le même effet pour optimiser les compilateurs. Bien que le commentaire de @Tony D ci-dessous soit correct, cela peut laisser certaines erreurs de syntaxe non détectées.

J'inclus parfois aussi une vérification à l'exécution, fournissant ainsi une sorte d'indicateur de débogage. Comme @Tony D me l’a rappelé, le fait d’avoir un endl dedans est aussi souvent utile.

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

Parfois, je veux aussi imprimer l'expression:

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

Dans certaines macros, j'aime bien inclure __FILE__, __LINE__ ou __func__, mais il s'agit le plus souvent d'assertions et non de simples macros de débogage.

38
MvG

Voici mon préféré

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

C'est très pratique et permet un code propre (et surtout, rapide en mode de lancement !!).

Beaucoup de #ifdef DEBUG_BUILD blocs partout (pour filtrer les blocs de code liés au débogage) sont assez laids, mais pas si mal quand vous enroulez quelques lignes avec un D().

Comment utiliser:

D(cerr << "oopsie";)

Si c'est encore trop moche/bizarre/long pour vous,

#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

(I suggère n'utilise pas using namespace std; bien que peut-être using std::cout; using std::cerr; puisse être une bonne idée)

Notez que vous voudrez peut-être faire plus de choses que simplement imprimer sur stderr lorsque vous songez à "déboguer". Faites preuve de créativité et construisez des constructions offrant un aperçu des interactions les plus complexes au sein de votre programme, tout en vous permettant de passer très rapidement à la construction d’une version extrêmement efficace, sans encombrement d’instrumentation de débogage.

Par exemple, dans l'un de mes projets récents, j'avais un énorme bloc réservé au débogage qui commençait par FILE* file = fopen("debug_graph.dot"); avant de vider un graphe compatible graphviz en format point pour visualiser les grands arbres dans mes infrastructures de données. . Ce qui est encore plus cool, c’est que le client OS X Graphviz lise automatiquement le fichier à partir du disque lorsqu’il change, de sorte que le graphique est actualisé à chaque exécution du programme!

J'aime aussi particulièrement "étendre" les classes/structures avec des membres et des fonctions uniquement débogués. Cela ouvre la possibilité d'implémenter les fonctionnalités et les états qui sont là pour vous aider à localiser les bogues. Comme tout ce qui est encapsulé dans des macros de débogage, il est supprimé en changeant un paramètre de construction. Une routine géante qui vérifie minutieusement chaque cas de coin à chaque mise à jour d'état? Pas de problème. Slap a D() autour de lui. Une fois que cela fonctionne, supprimez -DDEBUG du script de compilation, c’est-à-dire build pour version, et le tout est parti, prêt à être réactivé à tout moment pour vos tests unitaires ou ce que vous avez.

Un grand exemple, assez complet, pour illustrer l’utilisation (peut-être un peu excessive) de ce concept:

#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

Notez que pour les gros blocs de code, j’utilise juste le bloc régulier #ifdef conditionnel car cela améliore quelque peu la lisibilité, comme pour les gros blocs, l’utilisation de macros extrêmement courtes est plus un obstacle!

La raison pour laquelle la macro N(x) doit exister est de spécifier quoi ajouter lorsque le test unitaire est désactivé .

Dans cette partie:

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

Ce serait bien si nous pouvions dire quelque chose comme

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

Mais nous ne pouvons pas, car la virgule fait partie de la syntaxe du préprocesseur. L'omission de la virgule produit une syntaxe C++ non valide.

Si vous aviez du code supplémentaire pour quand pas compiler pour déboguer, vous pouvez utiliser ce type de macro correspondante à débogage inverse.

Maintenant, ce code n’est peut-être pas un exemple de "très bon code", mais il illustre certaines des choses que vous pouvez accomplir avec une application intelligente de macros, qui, si vous restez disciplinés, ne sont pas nécessairement mal.

Je suis tombé sur cette gemme tout à l'heure après m'être interrogé sur la chose do{} while(0), et vous voulez vraiment toute cette fantaisie dans ces macros!

J'espère que mon exemple pourra vous aider à comprendre au moins certaines des choses intelligentes qui peuvent être faites pour améliorer votre code C++. Il est très utile d’instruire un code pendant que vous l’écrivez plutôt que de revenir le faire quand vous ne comprenez pas ce qui se passe. Mais il faut toujours trouver un équilibre entre le rendre robuste et le faire à temps.

J'aime penser à des contrôles de cohérence supplémentaires de la construction du débogage en tant qu'outil différent dans la boîte à outils, similaire aux tests unitaires. À mon avis, ils pourraient être encore plus puissants, car plutôt que de mettre votre logique de contrôle de cohérence dans des tests unitaires et de les isoler de la mise en œuvre, s'ils sont inclus dans la mise en œuvre et peuvent être simulés à volonté, des tests complets ne sont pas aussi nécessaires. parce que vous pouvez simplement activer les contrôles et exécuter les choses comme d'habitude, à la rigueur.

32
Steven Lu

Pour la question 1] La réponse est oui. Il ne fera qu'imprimer le message dans le flux d'erreur standard.

Pour la question 2] Il y en a beaucoup. Mon préféré est

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

ce qui permettra d'inclure un nombre arbitraire de variables à inclure dans le message de débogage.

9
NeonGlow

J'aime utiliser des macros avec __LINE__, __FILE__ comme arguments à afficherdans le code d'où provient l'impression - il n'est pas rare d'imprimer le même nom de variable à plusieurs endroits. Par conséquent, fprintf(stderr, "x=%d", x); n'aura pas beaucoup d'importance si vous ajoutez ensuite un autre les mêmes dix lignes plus bas. 

J'ai également utilisé des macros qui surchargent certaines fonctions et enregistrent d'où elles ont été appelées (par exemple, des allocations de mémoire), afin que plus tard, je puisse déterminer laquelle des deux a fui. Pour l’allocation de mémoire, c’est un peu plus difficile en C++, car vous avez tendance à utiliser new/delete, et ils ne peuvent pas être remplacés facilement, mais d’autres ressources, telles que les opérations de verrouillage/déverrouillage, peuvent être très utiles pour suivre cette voie [bien sûr, si vous avez un wrapper de verrouillage qui utilise construction/destruction comme un bon programmeur C++, vous l'ajouterez au constructeur pour ajouter un fichier/une ligne à la structure interne une fois que vous aurez acquis le verrou, et vous pourrez voir où il se trouve ailleurs tu ne peux pas l'acquérir quelque part]. 

8
Mats Petersson

C'est la macro de journal que j'utilise actuellement:

#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

Usage:

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

Sortie:

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

Ceci est ma version, en utilisant une fonction de modèle variadique print:

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 version I fait du debug_print une fonction de modèle variadique qui accepte un niveau de débogage qui me permet de sélectionner le type de sortie que je veux générer au moment de l'exécution:

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

Notez que la fonction print bloque Visual Studio 2013 Preview (je n'ai pas testé la télécommande). J'ai remarqué qu'il est plus rapide (sous Windows, où la sortie de la console est lente) que ma solution précédente, qui utilisait une classe enfant ostream qui surchargeait operator<<.

Vous pouvez également utiliser une variable stringstream dans print si vous souhaitez uniquement appeler la fonction de sortie réelle une seule fois (ou écrire votre propre variable _printf;) de typesafe

4
rubenvb

… Et en additif à toutes les réponses:

Personnellement, je n'utilise jamais de macros telles que DEBUG pour distinguer le code de version, mais plutôt NDEBUG qui est doit être défini pour les versions de version afin d'éliminer les appels assert() (oui, j'utilise abondamment assert()). Et si ce dernier n'est pas défini, il s'agit d'une version de débogage. Facile! Il n’ya donc aucune raison d’introduire une autre macro de débogage! (et gérez les cas possibles lorsque DEBUG et NDEBUG ne sont pas définis).

4
zaufi

J'utilise le code ci-dessous pour la journalisation. Il y a quelques avantages:

  1. Je peux les activer/désactiver au moment de l'exécution.
  2. Je peux compiler des instructions à un niveau de journalisation particulier. Par exemple, pour le moment, j’ai compilé inconditionnellement dans la macro KIMI_PRIVATE parce que je débogue quelque chose dans la version de publication, mais étant donné que de nombreux éléments de sauce potentiellement secrets sont consignés (lol), je les compile en dehors des versions .

Ce modèle m'a très bien servi au fil des ans. Remarque: bien qu'il existe une fonction globale logMessage, le code met généralement le journal en file d'attente vers un thread de journalisation.

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

J'utilise les micro suivants,

#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

UTILISATION: 

LOGE("ERROR.");
LOGE2("ERROR1","ERROR2");
0
nilesh suryawanshi