#define macro pour déboguer l'impression en C?


209

Essayer de créer une macro qui peut être utilisée pour imprimer des messages de débogage lorsque DEBUG est défini, comme le pseudo-code suivant:

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

Comment cela se fait-il avec une macro?


Le compilateur (gcc) optimisera-t-il les instructions comme si (DEBUG) {...} out, si dans le code de production la macro DEBUG est définie sur 0? Je comprends qu'il y a de bonnes raisons de laisser les instructions de débogage visibles au compilateur, mais un mauvais sentiment persiste. -Pat
Pat

Réponses:


410

Si vous utilisez un compilateur C99 ou version ultérieure

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

Il suppose que vous utilisez C99 (la notation de liste d'arguments variables n'est pas prise en charge dans les versions antérieures). L' do { ... } while (0)idiome garantit que le code agit comme une instruction (appel de fonction). L'utilisation inconditionnelle du code garantit que le compilateur vérifie toujours que votre code de débogage est valide - mais l'optimiseur supprimera le code lorsque DEBUG est égal à 0.

Si vous souhaitez travailler avec #ifdef DEBUG, modifiez la condition de test:

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

Et puis utilisez DEBUG_TEST où j'ai utilisé DEBUG.

Si vous insistez sur un littéral de chaîne pour la chaîne de format (probablement une bonne idée de toute façon), vous pouvez également introduire des choses comme __FILE__, __LINE__et __func__dans la sortie, ce qui peut améliorer les diagnostics:

#define debug_print(fmt, ...) \
        do { if (DEBUG) fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, \
                                __LINE__, __func__, __VA_ARGS__); } while (0)

Cela s'appuie sur la concaténation de chaînes pour créer une chaîne de format plus grand que ce que le programmeur écrit.

Si vous utilisez un compilateur C89

Si vous êtes bloqué avec C89 et sans extension de compilateur utile, il n'y a pas de moyen particulièrement propre de le gérer. La technique que j'ai utilisée était:

#define TRACE(x) do { if (DEBUG) dbg_printf x; } while (0)

Et puis, dans le code, écrivez:

TRACE(("message %d\n", var));

Les parenthèses doubles sont cruciales - et c'est pourquoi vous avez la notation amusante dans l'expansion de macro. Comme précédemment, le compilateur vérifie toujours la validité syntaxique du code (ce qui est bien) mais l'optimiseur n'appelle la fonction d'impression que si la macro DEBUG est évaluée à non nulle.

Cela nécessite une fonction de support - dbg_printf () dans l'exemple - pour gérer des choses comme 'stderr'. Cela vous oblige à savoir comment écrire des fonctions varargs, mais ce n'est pas 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);
}

Vous pouvez également utiliser cette technique en C99, bien sûr, mais la __VA_ARGS__technique est plus nette car elle utilise la notation de fonction régulière, pas le hack entre parenthèses.

Pourquoi est-il crucial que le compilateur voit toujours le code de débogage?

[ Répétition des commentaires faits à une autre réponse. ]

Une idée centrale derrière les implémentations C99 et C89 ci-dessus est que le compilateur proprement dit voit toujours les instructions de débogage de type printf. Ceci est important pour le code à long terme - code qui durera une décennie ou deux.

Supposons qu'un morceau de code soit principalement dormant (stable) depuis un certain nombre d'années, mais qu'il doit maintenant être modifié. Vous réactivez la trace de débogage - mais il est frustrant d'avoir à déboguer le code de débogage (traçage) car il fait référence à des variables qui ont été renommées ou retapées, pendant les années de maintenance stable. Si le compilateur (postprocesseur) voit toujours l'instruction print, il s'assure que les modifications environnantes n'ont pas invalidé les diagnostics. Si le compilateur ne voit pas l'instruction d'impression, il ne peut pas vous protéger contre votre propre imprudence (ou la négligence de vos collègues ou collaborateurs). Voir « La pratique de la programmation » de Kernighan et Pike, en particulier le chapitre 8 (voir également Wikipedia sur TPOP ).

C'est l'expérience `` été là, fait ça '' - j'ai utilisé essentiellement la technique décrite dans d'autres réponses où la construction non déboguée ne voit pas les instructions de type printf depuis un certain nombre d'années (plus d'une décennie). Mais je suis tombé sur les conseils de TPOP (voir mon commentaire précédent), puis j'ai activé le code de débogage après un certain nombre d'années, et j'ai rencontré des problèmes de changement de contexte pour rompre le débogage. Plusieurs fois, avoir l'impression toujours validée m'a évité des problèmes ultérieurs.

J'utilise NDEBUG pour contrôler les assertions uniquement, et une macro distincte (généralement DEBUG) pour contrôler si le suivi du débogage est intégré au programme. Même lorsque le suivi de débogage est intégré, je ne souhaite souvent pas que la sortie de débogage apparaisse inconditionnellement, j'ai donc un mécanisme pour contrôler si la sortie apparaît (niveaux de débogage et au lieu d'appeler fprintf()directement, j'appelle une fonction d'impression de débogage qui n'imprime que de manière conditionnelle donc la même construction du code peut imprimer ou ne pas imprimer en fonction des options du programme). J'ai également une version «multi-sous-système» du code pour les programmes plus gros, de sorte que je puisse avoir différentes sections du programme produisant différentes quantités de trace - sous contrôle d'exécution.

Je préconise que pour toutes les versions, le compilateur devrait voir les instructions de diagnostic; cependant, le compilateur ne génère aucun code pour les instructions de trace de débogage, sauf si le débogage est activé. Fondamentalement, cela signifie que tout votre code est vérifié par le compilateur à chaque fois que vous compilez - que ce soit pour la publication ou le débogage. C'est une bonne chose!

debug.h - version 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 - version 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 à argument unique pour C99 ou version ultérieure

Kyle Brandt a demandé:

Quoi qu'il en soit, cela fonctionne donc debug_printtoujours même s'il n'y a pas d'arguments? Par exemple:

    debug_print("Foo");

Il y a un hack simple et démodé:

debug_print("%s\n", "Foo");

La solution GCC uniquement illustrée ci-dessous fournit également un support pour cela.

Cependant, vous pouvez le faire avec le système C99 droit en utilisant:

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

Par rapport à la première version, vous perdez la vérification limitée qui nécessite l'argument 'fmt', ce qui signifie que quelqu'un pourrait essayer d'appeler 'debug_print ()' sans argument (mais la virgule de fin dans la liste d'arguments à fprintf()échouerait à compiler) . Que la perte de vérification soit un problème est discutable.

Technique spécifique à GCC pour un seul argument

Certains compilateurs peuvent proposer des extensions pour d'autres façons de gérer les listes d'arguments de longueur variable dans les macros. Plus précisément, comme indiqué pour la première fois dans les commentaires de Hugo Ideler , GCC vous permet d'omettre la virgule qui apparaîtrait normalement après le dernier argument «fixe» de la macro. Il vous permet également d'utiliser ##__VA_ARGS__dans le texte de remplacement de macro, qui supprime la virgule précédant la notation si, mais seulement si, le jeton précédent est une virgule:

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

Cette solution conserve l'avantage d'exiger l'argument format tout en acceptant les arguments facultatifs après le format.

Cette technique est également prise en charge par Clang pour la compatibilité GCC.


Pourquoi la boucle do-while?

Quel est le but de l' do whileici?

Vous voulez pouvoir utiliser la macro pour qu'elle ressemble à un appel de fonction, ce qui signifie qu'elle sera suivie d'un point-virgule. Par conséquent, vous devez empaqueter le corps de macro en fonction. Si vous utilisez une ifdéclaration sans les environs do { ... } while (0), vous aurez:

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

Supposons maintenant que vous écriviez:

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

Malheureusement, cette indentation ne reflète pas le contrôle réel du flux, car le préprocesseur produit un code équivalent à celui-ci (indenté et accolades ajoutés pour souligner la signification réelle):

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

La prochaine tentative de macro pourrait être:

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

Et le même fragment de code produit maintenant:

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);

Et elsec'est maintenant une erreur de syntaxe. La do { ... } while(0)boucle évite ces deux problèmes.

Il existe une autre façon d'écrire la macro qui pourrait fonctionner:

/* BAD - BAD - BAD */
#define debug_print(...) \
            ((void)((DEBUG) ? fprintf(stderr, __VA_ARGS__) : 0))

Cela laisse le fragment de programme affiché comme valide. Le (void)transtypage l'empêche d'être utilisé dans des contextes où une valeur est requise - mais il pourrait être utilisé comme l'opérande gauche d'un opérateur virgule où la do { ... } while (0)version ne peut pas. Si vous pensez que vous devriez pouvoir incorporer du code de débogage dans de telles expressions, vous préférerez peut-être cela. Si vous préférez que l'impression de débogage agisse comme une instruction complète, la do { ... } while (0)version est meilleure. Notez que si le corps de la macro impliquait des points-virgules (grosso modo), vous ne pouvez utiliser que la do { ... } while(0)notation. Cela fonctionne toujours; le mécanisme de déclaration d'expression peut être plus difficile à appliquer. Vous pouvez également obtenir des avertissements du compilateur avec la forme d'expression que vous préférez éviter; cela dépendra du compilateur et des indicateurs que vous utilisez.


TPOP était précédemment sur http://plan9.bell-labs.com/cm/cs/tpop et http://cm.bell-labs.com/cm/cs/tpop mais les deux le sont maintenant (2015-08-10) cassé.


Code dans GitHub

Si vous êtes curieux, vous pouvez consulter ce code dans GitHub dans mon référentiel SOQ (Stack Overflow Questions) sous forme de fichiers debug.c, debug.het mddebug.cdans le sous-répertoire src / libsoq .


1
Je pense que l'approche GCC ## - de gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html mériterait d'être mentionnée sous la rubrique "Variante à argument unique C99".
Hugo Ideler

2
Des années plus tard, et cette réponse est toujours la plus utile de tous les internets, sur la façon d'alias printk! vfprintf ne fonctionne pas dans l'espace noyau car stdio n'est pas disponible. Je vous remercie! #define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
kevinf

6
Dans votre exemple , avec les mots - clés __FILE__, __LINE__, __func__, __VA_ARGS__, il ne compilera pas si vous avez aucun paramètre de printf, à savoir si vous appelez simplement debug_print("Some msg\n"); Vous pouvez résoudre ce problème en utilisant fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__); le ## __ VA_ARGS__ permet de passer aucun paramètre à la fonction.
mc_electron

1
@LogicTom: la différence est entre #define debug_print(fmt, ...)et #define debug_print(...). Le premier d'entre eux nécessite au moins un argument, la chaîne de format ( fmt) et zéro ou plusieurs autres arguments; le second ne nécessite aucun ou plusieurs arguments au total. Si vous utilisez debug_print()avec le premier, vous obtenez une erreur du préprocesseur concernant une mauvaise utilisation de la macro, contrairement au second. Cependant, vous obtenez toujours des erreurs de compilation parce que le texte de remplacement n'est pas valide C. Donc, ce n'est vraiment pas beaucoup de différence - d'où l'utilisation du terme «vérification limitée».
Jonathan Leffler

1
La variante illustrée ci-dessus, @ St.Antario, utilise un seul niveau de débogage actif sur l'ensemble de l'application, et j'utilise généralement des options de ligne de commande pour permettre au niveau de débogage d'être défini lors de l'exécution du programme. J'ai également une variante qui reconnaît plusieurs sous-systèmes différents, chacun ayant un nom et son propre niveau de débogage, afin que je puisse utiliser -D input=4,macros=9,rules=2pour définir le niveau de débogage du système d'entrée à 4, le système de macros à 9 (en cours d'examen approfondi) ) et le système de règles à 2. Il existe des variations infinies sur le thème; utilisez ce qui vous convient.
Jonathan Leffler

28

J'utilise quelque chose comme ça:

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

Que j'utilise simplement D comme préfixe:

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

Le compilateur voit le code de débogage, il n'y a pas de problème de virgule et cela fonctionne partout. Cela fonctionne également lorsque cela printfne suffit pas, par exemple lorsque vous devez vider un tableau ou calculer une valeur de diagnostic redondante pour le programme lui-même.

EDIT: Ok, cela pourrait générer un problème quand il y a elsequelque part à proximité qui peut être intercepté par cette injection if. Voici une version qui va plus loin:

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

3
Quant à for(;0;), cela pourrait générer un problème lorsque vous écrivez quelque chose comme D continue;ou D break;.
ACcreator

1
J'ai moi; il semble cependant très peu probable que cela se produise lors d'un accident.
mbq

11

Pour une implémentation portable (ISO C90), vous pouvez utiliser des parenthèses doubles, comme ceci;

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

ou (hackish, je ne le recommanderais pas)

#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: pour faire `penser 'au préprocesseur, il n'y a qu'un seul argument, tout en permettant à _ d'être développé à un stade ultérieur.
Marcin Koziuk

10

Voici la version que j'utilise:

#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

Je ferais quelque chose comme

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

Je pense que c'est plus propre.


Je n'aime pas vraiment l'idée d'utiliser une macro dans un test comme indicateur. Pourriez-vous expliquer pourquoi l'impression de débogage doit toujours être vérifiée?
LB40

1
@Jonathan: Si le code n'est exécuté qu'en mode débogage, pourquoi devriez-vous vous soucier s'il compile en mode non débogage? assert()à partir du stdlib fonctionne de la même manière et je réutilise normalement la NDEBUGmacro pour mon propre code de débogage ...
Christoph

en utilisant DEBUG dans le test, si quelqu'un fait un DEBUG non contrôlé non contrôlé, votre code ne compile plus. droite ?
LB40

4
Il est frustrant d'activer le débogage et de devoir ensuite déboguer le code de débogage car il fait référence à des variables qui ont été renommées ou retapées, etc. Si le compilateur (post-pré-processeur) voit toujours l'instruction print, il s'assure que toutes les modifications environnantes ont pas invalidé les diagnostics. Si le compilateur ne voit pas l'instruction d'impression, il ne peut pas vous protéger contre votre propre imprudence (ou la négligence de vos collègues ou collaborateurs). Voir «La pratique de la programmation» par Kernighan et Pike - plan9.bell-labs.com/cm/cs/tpop .
Jonathan Leffler

1
@Christoph: enfin, en quelque sorte ... J'utilise NDEBUG pour contrôler uniquement les assertions, et une macro distincte (généralement DEBUG) pour contrôler le suivi du débogage. Souvent, je ne veux pas que la sortie de débogage apparaisse sans condition, j'ai donc un mécanisme pour contrôler si la sortie apparaît (niveaux de débogage, et au lieu d'appeler directement fprintf (), j'appelle une fonction d'impression de débogage qui imprime uniquement de manière conditionnelle, donc la même construction de la le code peut imprimer ou ne pas imprimer en fonction des options du programme). Je préconise que pour toutes les versions, le compilateur devrait voir les instructions de diagnostic; cependant, il ne générera pas de code à moins que le débogage ne soit activé.
Jonathan Leffler

8

Selon http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html , il devrait y avoir un ##avant __VA_ARGS__.

Dans le cas contraire, une macro #define dbg_print(format, ...) printf(format, __VA_ARGS__)ne compilera pas l'exemple suivant: dbg_print("hello world");.


1
Bienvenue dans Stack Overflow. Vous avez raison de dire que GCC a l'extension non standard à laquelle vous faites référence. La réponse actuellement acceptée en fait mention, y compris exactement l'URL de référence que vous donnez.
Jonathan Leffler

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

Quelle version de C prend en charge cette notation? Et, si cela a fonctionné, le jeton collant tous les arguments comme ça signifie que vous n'avez qu'un ensemble très limité d'options pour la chaîne de format, n'est-ce pas?
Jonathan Leffler

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

1
OK - d'accord: il est documenté comme une ancienne extension GNU (section 5.17 du manuel GCC 4.4.1). Mais vous devriez probablement documenter que cela ne fonctionnera qu'avec GCC - ou peut-être que nous l'avons fait entre nous dans ces commentaires.
Jonathan Leffler

1
Mon intention était de montrer un autre style d'utilisation des args et principalement de démontrer l'utilisation de FUNCTION et LINE
eyalm

2

Voici ce que j'utilise:

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

Il a l'avantage de gérer correctement printf, même sans arguments supplémentaires. Dans le cas où DBG == 0, même le compilateur le plus stupide n'a rien à mâcher, donc aucun code n'est généré.


Il est préférable que le compilateur vérifie toujours le code de débogage.
Jonathan Leffler

1

Mon préféré de ce qui suit est var_dump, qui lorsqu'il est appelé:

var_dump("%d", count);

produit une sortie comme:

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

Nous remercions @ "Jonathan Leffler". Tous sont satisfaits du C89:

Code

#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

Donc, quand j'utilise gcc, j'aime:

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

Parce qu'il peut être inséré dans le code.

Supposons que vous essayez de déboguer

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

720

Ensuite, vous pouvez le changer en:

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

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

Et vous pouvez obtenir une analyse de quelle expression a été évaluée à quoi.

Il est protégé contre le problème de la double évaluation, mais l'absence de gensymes le laisse ouvert aux collisions de noms.

Cependant, il se niche:

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

Je pense donc que tant que vous éviterez d'utiliser g2rE3 comme nom de variable, tout ira bien.

Je l'ai certainement trouvé (et les versions alliées pour les chaînes et les versions pour les niveaux de débogage, etc.) inestimables.


1

Je réfléchis à la façon de procéder depuis des années et j'ai finalement trouvé une solution. Cependant, je ne savais pas qu'il y avait déjà d'autres solutions ici. Tout d'abord, à la différence de la réponse de Leffler , je ne vois pas son argument selon lequel les impressions de débogage devraient toujours être compilées. Je préfère ne pas avoir des tonnes de code inutile à exécuter dans mon projet, lorsqu'il n'est pas nécessaire, dans les cas où je dois tester et qu'ils pourraient ne pas être optimisés.

Ne pas compiler à chaque fois peut sembler pire que dans la pratique. Vous vous retrouvez avec des impressions de débogage qui ne se compilent pas parfois, mais ce n'est pas si difficile de les compiler et de les tester avant de finaliser un projet. Avec ce système, si vous utilisez trois niveaux de débogage, placez-le simplement sur le message de débogage de niveau trois, corrigez vos erreurs de compilation et vérifiez les autres avant de finaliser votre code. (Bien sûr, la compilation des instructions de débogage ne garantit pas qu'elles fonctionnent toujours comme prévu.)

Ma solution fournit également des niveaux de détails de débogage; et si vous le définissez au plus haut niveau, ils compilent tous. Si vous avez récemment utilisé un niveau de détail de débogage élevé, ils ont tous pu être compilés à ce moment-là. Les mises à jour finales devraient être assez faciles. Je n'ai jamais eu besoin de plus de trois niveaux, mais Jonathan dit qu'il en a utilisé neuf. Cette méthode (comme celle de Leffler) peut être étendue à n'importe quel nombre de niveaux. L'utilisation de ma méthode peut être plus simple; ne nécessitant que deux instructions lorsqu'il est utilisé dans votre code. Je suis cependant en train de coder la macro FERMER aussi - bien qu'elle ne fasse rien. Cela pourrait se produire si j'envoyais vers un fichier.

Contre le coût, l'étape supplémentaire de les tester pour voir qu'ils vont compiler avant la livraison, c'est que

  1. Vous devez leur faire confiance pour être optimisé, ce qui DEVRAIT certainement se produire si vous avez un niveau d'optimisation suffisant.
  2. De plus, ils ne le seront probablement pas si vous effectuez une compilation avec une optimisation désactivée à des fins de test (ce qui est certes rare); et ils ne le seront certainement pas du tout pendant le débogage - exécutant ainsi des dizaines ou des centaines d'instructions "if (DEBUG)" au moment de l'exécution; ralentissant ainsi l'exécution (ce qui est mon objection principale) et moins important, augmentant la taille de votre exécutable ou de votre DLL; et donc les temps d'exécution et de compilation. Jonathan, cependant, m'informe que sa méthode peut également être utilisée pour ne pas compiler du tout les déclarations.

Les branches sont en fait relativement assez coûteuses dans les processeurs de prélecture modernes. Peut-être pas un gros problème si votre application n'est pas critique; mais si les performances sont un problème, alors, oui, un problème assez important pour que je préfère opter pour un code de débogage un peu plus rapide (et peut-être une version plus rapide, dans de rares cas, comme indiqué).

Donc, ce que je voulais, c'est une macro d'impression de débogage qui ne compile pas si elle ne doit pas être imprimée, mais le fait si c'est le cas. Je voulais également des niveaux de débogage, de sorte que, par exemple, si je voulais que des parties cruciales des performances du code ne s'impriment pas à certains moments, mais s'impriment à d'autres moments, je pouvais définir un niveau de débogage et avoir des impressions de débogage supplémentaires. est tombé sur un moyen d'implémenter des niveaux de débogage qui ont déterminé si l'impression était même compilée ou non. Je l'ai réalisé de cette façon:

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

Utilisation des macros

Pour l'utiliser, faites simplement:

DEBUGLOG_INIT("afile.log");

Pour écrire dans le fichier journal, faites simplement:

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

Pour le fermer, vous faites:

DEBUGLOG_CLOSE();

même si actuellement, ce n'est même pas nécessaire, techniquement parlant, car cela ne fait rien. J'utilise toujours CLOSE en ce moment, cependant, au cas où je changerais d'avis sur son fonctionnement et que je voudrais laisser le fichier ouvert entre les instructions de journalisation.

Ensuite, lorsque vous souhaitez activer l'impression de débogage, modifiez simplement le premier #define dans le fichier d'en-tête pour dire, par exemple

#define DEBUG 1

Pour que les instructions de journalisation soient compilées sur rien, faites

#define DEBUG 0

Si vous avez besoin d'informations à partir d'un morceau de code fréquemment exécuté (c'est-à-dire un niveau de détail élevé), vous pouvez écrire:

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

Si vous définissez DEBUG sur 3, les niveaux de journalisation 1, 2 et 3 sont compilés. Si vous le définissez sur 2, vous obtenez les niveaux de journalisation 1 et 2. Si vous le définissez sur 1, vous obtenez uniquement les instructions de niveau de journalisation 1.

En ce qui concerne la boucle do-while, car elle est évaluée à une seule fonction ou à rien, au lieu d'une instruction if, la boucle n'est pas nécessaire. OK, indiquez-moi pour utiliser C au lieu de C ++ IO (et QString :: arg () de Qt est également un moyen plus sûr de formater les variables dans Qt - c'est assez simple, mais prend plus de code et la documentation de formatage n'est pas aussi organisée comme cela pourrait être - mais j'ai quand même trouvé des cas où c'est préférable), mais vous pouvez mettre le code dans le fichier .cpp que vous voulez. Il peut également s'agir d'une classe, mais vous devrez alors l'instancier et la suivre, ou faire un nouveau () et le stocker. De cette façon, vous déposez simplement les instructions #include, init et éventuellement close dans votre source, et vous êtes prêt à commencer à l'utiliser. Cela ferait un bon cours, cependant, si vous êtes si enclin.

J'avais déjà vu beaucoup de solutions, mais aucune ne correspondait à mes critères ainsi qu'à celui-ci.

  1. Il peut être étendu pour faire autant de niveaux que vous le souhaitez.
  2. Il compile à rien sinon l'impression.
  3. Il centralise les E / S en un seul endroit facile à modifier.
  4. C'est flexible, en utilisant le formatage printf.
  5. Encore une fois, cela ne ralentit pas les exécutions de débogage, tandis que les impressions de débogage toujours compilables sont toujours exécutées en mode débogage. Si vous faites de l'informatique, et pas plus facile d'écrire du traitement de l'information, vous pouvez vous retrouver à exécuter un simulateur consommant beaucoup de CPU, pour voir par exemple où le débogueur l'arrête avec un index hors de portée pour un vecteur. Ceux-ci fonctionnent déjà très lentement en mode débogage. L'exécution obligatoire de centaines d'impressions de débogage ralentira nécessairement encore ces exécutions. Pour moi, de telles courses ne sont pas rares.

Pas terriblement significatif, mais en plus:

  1. Il ne nécessite aucun hack pour imprimer sans arguments (par exemple DEBUGLOG_LOG(3, "got here!");); vous permettant ainsi d'utiliser, par exemple, le formatage .arg () plus sûr de Qt. Cela fonctionne sur MSVC, et donc probablement sur gcc. Il utilise ##dans le #defines, qui n'est pas standard, comme le souligne Leffler, mais est largement pris en charge. (Vous pouvez le recoder pour ne pas l'utiliser ##si nécessaire, mais vous devrez utiliser un hack tel qu'il le fournit.)

Avertissement: si vous oubliez de fournir l'argument de niveau de journalisation, MSVC prétend que l'identificateur n'est pas défini.

Vous voudrez peut-être utiliser un nom de symbole de préprocesseur autre que DEBUG, car une source définit également ce symbole (par exemple, progs utilisant des ./configurecommandes pour se préparer à la construction). Cela m'a paru naturel lorsque je l'ai développé. Je l'ai développé dans une application où la DLL est utilisée par autre chose, et il est plus conventuel d'envoyer des impressions de journal vers un fichier; mais le changer en vprintf () fonctionnerait aussi très bien.

J'espère que cela vous épargnera beaucoup de peine à trouver la meilleure façon de faire la journalisation du débogage; ou vous en montre un que vous pourriez préférer. Cela fait des décennies que j'essaie sans conviction de comprendre celui-ci. Fonctionne dans MSVC 2012 et 2015, et donc probablement sur gcc; ainsi que probablement travailler sur beaucoup d'autres, mais je ne l'ai pas testé sur eux.

Je veux aussi en faire une version en streaming un jour.

Remarque: Merci à Leffler, qui m'a cordialement aidé à mieux formater mon message pour StackOverflow.


2
Vous dites "exécuter des dizaines ou des centaines d' if (DEBUG)instructions au moment de l'exécution, qui ne sont pas optimisées" - ce qui penche sur les moulins à vent . L'intérêt du système que j'ai décrit est que le code est vérifié par le compilateur (important et automatique - pas de build spécial requis) mais le code de débogage n'est pas généré du tout car il est optimisé (il n'y a donc aucun impact sur l'exécution sur la taille ou les performances du code car le code n'est pas présent au moment de l'exécution).
Jonathan Leffler

Jonathan Leffler: Merci d'avoir souligné ma mauvaise formulation. Je laissai mes pensées courir plus vite que mes doigts, étant si heureux d'avoir fini. J'ai révisé mes objections avec "... 1) vous devez leur faire confiance pour être optimisé, ce qui devrait se produire si vous avez un niveau d'optimisation suffisant. 2) De plus, ils ne le seront pas si vous faites une compilation avec optimisation désactivées à des fins de test; et elles ne le seront probablement pas du tout pendant le débogage - exécutant ainsi des dizaines ou des centaines d'instructions "if (DEBUG)" au moment de l'exécution - augmentant ainsi la taille de votre exécutable ou de votre DLL, et les temps d'exécution. "
CodeLurker

Pour que le vôtre fasse l'autre chose importante que fait le mien, vous devez avoir des niveaux de débogage. Bien que je n'aie souvent pas besoin de beaucoup d'entre elles activées, quelques applications bénéficient vraiment de pouvoir obtenir un grand niveau de détails sur une boucle critique avec un simple "#define DEBUG 3", puis revenir à des informations beaucoup moins verbeuses avec "#define DEBUG 1". Je n'ai jamais eu besoin de plus de trois niveaux, et donc, au moins environ 1/3 de mes debugs se compilent déjà à la sortie. Si j'ai utilisé le niveau 3 récemment, ils le font probablement TOUS.
CodeLurker

YMMV. Le système moderne que j'ai montré prend en charge la définition dynamique (à l'exécution) des niveaux de débogage, de sorte que vous pouvez décider par programmation de la quantité de débogage produite lors de l'exécution. J'utilisais généralement les niveaux 1 à 9, bien qu'il n'y ait pas de limite supérieure (ou inférieure); le niveau par défaut est 0, ce qui est généralement désactivé, mais peut être explicitement demandé pendant le développement actif si approprié - il n'est pas approprié pour un travail à long terme). J'ai choisi un niveau par défaut de 3; les choses peuvent être réglées. Cela me donne beaucoup de contrôle. Si vous ne voulez vraiment pas tester le code de débogage lorsqu'il est inactif, changez l'alternative en ((void)0)- c'est facile.
Jonathan Leffler

1
Ahh. Cela aurait aidé à lire le tout. C'est un poste assez long. Je pense que cela a jusqu'à présent les points essentiels. Il s'avère que le vôtre, comme le mien, peut être utilisé pour compiler ou non toutes les impressions de débogage, et peut prendre en charge les niveaux; bien que, certes, le vôtre puisse compiler des niveaux que vous n'utilisez pas - à un coût lors du débogage.
CodeLurker

0

Je crois que cette variation du thème donne des catégories de débogage sans avoir besoin d'avoir un nom de macro séparé par catégorie.

J'ai utilisé cette variation dans un projet Arduino où l'espace du programme est limité à 32K et la mémoire dynamique est limitée à 2K. L'ajout d'instructions de débogage et de chaînes de débogage de trace utilise rapidement de l'espace. Il est donc essentiel de pouvoir limiter la trace de débogage incluse au moment de la compilation au minimum nécessaire à chaque génération du code.

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

appel du fichier .cpp

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

...
PRINT(4, "Time out error,\t");
...
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.