DEBUG macros en C++

je viens de rencontrer une macro de débogage en C que j'aime vraiment

#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 code est-il analogue à celui du C?
  2. avez-vous des macros de débogage c++ préférées?

EDIT : Par "macros de débogage", je veux dire"macros qui peuvent être utiles lors de l'exécution d'un programme en mode de débogage".

46
demandé sur a3f 2013-01-10 08:53:52

9 réponses

le second extrait de code est-il analogue à celui de C?

plus ou moins. Il est plus puissant, comme vous pouvez inclure << -valeurs séparées dans l'argument, donc avec un seul argument vous obtenez quelque chose qui nécessiterait un nombre variable de macro arguments en C. d'un autre côté, il y a une faible chance que les gens l'abuseront en incluant un point-virgule dans l'argument. Ou encore encouter des erreurs dues à un point-virgule oublié après l'appel. Donc j'aurais incluez 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 celui ci-dessus, et de l'utiliser assez souvent. Mon Non-op lit habituellement

#define DEBUG(x)

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

j'ai parfois inclure un temps de vérifier aussi bien, offrant ainsi une certaine forme de debug. Comme @Tony D ME l'a rappelé, avoir une endl là-dedans est souvent utile aussi bien.

#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 inclure __FILE__ , __LINE__ ou __func__ , mais ce sont plus souvent des assertions et pas de simples macros de débogage.

35
répondu MvG 2013-06-03 06:16:09

Voici mon préféré

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

c'est très pratique et rend propre (et surtout, rapide en mode de libération!!) code.

Beaucoup de #ifdef DEBUG_BUILD blocs de tous sur la place (à filtre déboguer des blocs de code) est assez moche, mais pas si mal quand vous enroulez quelques lignes avec un D() .

comment utiliser:

D(cerr << "oopsie";)

Si c'est encore trop laid/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èrent ne pas utiliser using namespace std; mais peut-être using std::cout; using std::cerr; pourrait être une bonne idée)

notez que vous pourriez vouloir faire plus de choses que simplement imprimer sur stderr quand vous pensez à "déboguer". Être créatif, et vous pouvez construire des constructions qui offrent 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'un version super efficace, sans problème avec l'instrumentation de débogage.

par exemple, dans un de mes projets récents, j'ai eu un bloc de débogage énorme seulement qui a commencé avec FILE* file = fopen("debug_graph.dot"); et a procédé à décharger un graphviz graphique compatible au format de point pour visualiser de grands arbres dans mes infrastructures de données. Ce qui est encore plus cool, c'est que le client graphviz OS X lira automatiquement le fichier à partir du disque lorsqu'il change, de sorte que le graphique se rafraîchit à chaque fois que le programme est lancé!

j'aime aussi particulièrement" étendre " les classes/structures avec des membres et des fonctions de débogage seulement. Cela ouvre la possibilité de mettre en œuvre la fonctionnalité et l'état qui est là pour vous aider à traquer les bogues, et tout comme tout le reste qui est enveloppé dans des macros de débogage, est supprimé en changeant un paramètre de construction. Une routine géante qui vérifie minutieusement chaque affaire de coin sur chaque mise à jour de l'état? Pas un problème. Mettez un D() autour. Une fois que vous voyez cela fonctionne, supprimer -DDEBUG du script de construction, c'est-à-dire construire pour la publication, et il est parti, prêt à être réactivé à tout moment pour votre unité-test ou ce que vous avez.

un grand exemple, quelque peu complet, pour illustrer (peut-être un peu trop zélé) l'utilisation 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

noter que pour les grands blocs de code, je n'utilise que le bloc régulier #ifdef conditionals parce que cela améliore un peu la lisibilité, comme pour les grands l'utilisation de macros extrêmement courtes est plus un obstacle!

la raison pour laquelle la macro N(x) doit exister est de préciser ce que ajouter lorsque le test de l'unité est désactivé .

dans la présente 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, parce que la virgule fait partie du préprocesseur syntaxe. Omettre la virgule produit une syntaxe C++ invalide.

si vous aviez un code supplémentaire pour quand pas compilant pour le débogage, vous pourriez utiliser ce type de macro inverse-débogage correspondante.

maintenant ce code pourrait ne pas être un exemple de "vraiment bon code", mais il illustre certaines des choses que vous pouvez accomplir avec l'application intelligente de macros, qui si vous restez discipliné, ne sont pas nécessairement mal.

je suis tombé sur ce joyau tout à l'heure après s'interroger sur le do{} while(0) choses", et vous avez vraiment ne voulez tous que fanciness dans ces macros ainsi!

avec un peu de chance mon exemple peut fournir un aperçu d'au moins quelques-unes des choses intelligentes qui peuvent être faites pour améliorer votre code C++. Il est vraiment précieux pour le code d'instrument pendant que vous l'écrivez plutôt que de revenir pour le faire lorsque vous ne pas comprendre ce qui se passe. Mais c'est toujours un équilibre qu'il faut trouver entre la robustesse et le respect des délais.

j'aime à penser à des vérifications supplémentaires de bon sens de debug build comme un outil différent dans la boîte à outils, similaire aux tests unitaires. À mon avis, ils pourraient être encore plus puissants, parce que plutôt que de mettre votre logique de contrôle de la santé mentale dans les tests unitaires et de les isoler de la mise en œuvre, s'ils sont inclus dans la mise en œuvre et peuvent être inventés à volonté, alors 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, dans un pincement.

29
répondu Steven Lu 2018-05-02 14:48:51

pour la question 1] la réponse est oui. Il suffit d'imprimer le message dans le flux d'erreurs standard.

pour la question 2] Il y en a beaucoup. Ma préférence est

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

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

8
répondu NeonGlow 2013-01-10 05:22:36

j'aime utiliser des macros avec __LINE__ , __FILE__ comme arguments pour montrer dans le code l'impression est de - il n'est pas rare d'imprimer le même nom de variable à plusieurs endroits, donc fprintf(stderr, "x=%d", x); ne signifiera pas beaucoup si vous ajoutez alors un autre les mêmes dix lignes plus loin.

j'ai également utilisé des macros qui outrepassent certaines fonctions et stockent où il a été appelé à partir( par exemple allocations de mémoire), de sorte que plus tard, je peux comprendre lequel est celui qui a fuité. Pour l'allocation de la mémoire, c'est un peu plus difficile en C++, car vous avez tendance à utiliser new/delete, et ils ne peuvent pas être facilement remplacés, mais d'autres ressources comme les opérations de verrouillage/déverrouillage peuvent être très utiles pour tracer de cette façon [bien sûr, si vous avez un wrapper de verrouillage qui utilise construction/destruction comme un bon programmeur C++, vous l'ajouteriez au constructeur pour ajouter file/line à la structure interne une fois que vous avez acquis la serrure, et vous pouvez voir où il a lieu ailleurs lorsque vous ne pouvez pas acquérir quelque part].

8
répondu Mats Petersson 2013-06-02 08:37:24

c'est la macro log 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

Utilisation:

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

sortie:

xxxx/proj.ios_mac/Classes/IntroScene.cpp][gotoNextScene][Line 58] >>> test...
6
répondu firestoke 2015-07-01 04:46:52

C'est ma version, à l'aide d'un variadic template print de la fonction:

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 quel type de sortie je veux sortir à l'exécution:

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

Note la fonction print crashes Visual Studio 2013 Preview (Je n'ai pas testé la RC). J'ai remarqué qu'il est plus rapide (sur Windows, où la sortie de la console est lent) que ma solution précédente qui utilisait une classe d'enfant ostream qui a surchargé operator<< .

vous pouvez également utiliser un temporaire stringstream à l'intérieur de print si vous voulez seulement appeler la fonction de sortie réelle une fois (ou écrire votre propre sécurité de type printf ; -))

4
répondu rubenvb 2013-09-30 15:48:36

... et comme addendum à toutes les réponses:

personnellement, je n'utilise jamais de macros comme DEBUG pour distinguer le débogage du code de publication, au lieu de cela j'utilise NDEBUG qui est doit être défini pour les constructions de publication pour éliminer assert() appels (Oui, j'utilise assert() intensivement). Et si cette dernière n'est pas définie, alors c'est une construction de débogage. Facile! Donc, en fait, il n'y a aucune raison d'introduire une macro de débogage de plus! (et gérer les cas où DEBUG et NDEBUG ne sont pas définis).

4
répondu zaufi 2015-06-04 13:51:07

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

  1. je peux les allumer/éteindre à l'exécution.
  2. je peux compiler des déclarations à un niveau logarithmique particulier. Par exemple, pour le moment, j'ai compilé inconditionnellement dans la macro KIMI_PRIVATE parce que je suis en train de déboguer quelque chose dans la compilation de la version mais puisqu'il y a beaucoup de choses potentiellement secrètes de sauce qui sont enregistrées (lol), je les compile dans les compilations de la version.

Ce modèle m'a été très utile au fil des ans. Note: bien qu'il existe une fonction globale logMessage , le code fait habituellement la queue du log 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
répondu cheez 2013-06-03 04:42:52

j'utilise un 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

utiliser:

LOGE("ERROR.");
LOGE2("ERROR1","ERROR2");
0
répondu nilesh suryawanshi 2017-02-01 14:54:39