web-dev-qa-db-fra.com

Macro C #define pour l'impression de débogage

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

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

Comment cela est-il accompli avec une macro?

179
jfarrell

Si vous utilisez un compilateur C99 ou ultérieur

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

Cela suppose que vous utilisez C99 (la notation de liste d'arguments variables n'est pas prise en charge dans les versions précédentes). L'idiome do { ... } while (0) 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 à 0.

Si vous voulez travailler avec #ifdef DEBUG, changez 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 formatage (ce qui est probablement une bonne idée), vous pouvez également introduire des éléments tels que __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 repose sur la concaténation de chaînes pour créer une chaîne de format plus grand que celui programmé.

Si vous utilisez un compilateur C89

Si vous êtes coincé avec C89 et qu’il n’ya pas d’extension de compilateur utile, il n’existe pas de méthode particulièrement simple. La technique que j'avais l'habitude d'utiliser était:

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

Et puis, dans le code, écrivez:

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

Les doubles parenthèses sont cruciales - et sont la raison pour laquelle vous avez une notation amusante dans l’extension macro. Comme auparavant, le compilateur vérifie toujours la validité syntaxique du code (ce qui est bon), mais l'optimiseur appelle la fonction d'impression uniquement si la macro DEBUG est évaluée à une valeur autre que zéro.

Cela nécessite une fonction de support - dbg_printf () dans l'exemple - pour gérer des tâches telles que 'stderr'. Cela nécessite que vous sachiez é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 dans C99, bien sûr, mais la technique __VA_ARGS__ est plus précise car elle utilise la notation de fonction régulière, pas le hack à double parenthèse.

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

[ Rehausser les 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 un code à long terme - un code qui durera une décennie ou deux.

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

C'est ce que j'ai vécu - j'ai utilisé essentiellement la technique décrite dans d'autres réponses, dans laquelle la génération sans mise au point ne voit pas les déclarations analogues à printf pendant plusieurs années (plus d'une décennie). Mais je suis tombé sur le conseil de TPOP (voir mon commentaire précédent), puis j'ai activé un code de débogage après un certain nombre d'années et j'ai rencontré des problèmes de changement de contexte qui rompait le débogage. Plusieurs fois, le fait de toujours valider l’impression m’a évité de nouveaux problèmes.

J'utilise NDEBUG pour contrôler uniquement les assertions, et une macro distincte (généralement DEBUG) pour contrôler si le traçage de débogage est intégré au programme. Même lorsque le suivi de débogage est intégré, je ne veux souvent pas que la sortie de débogage apparaisse de manière inconditionnelle. J'ai donc un mécanisme pour contrôler l'affichage de la sortie (niveaux de débogage, et au lieu d'appeler fprintf() directement, j'appelle une impression de débogage. fonction qui imprime uniquement de manière conditionnelle pour que la même version du code puisse être imprimée ou non en fonction des options du programme). J'ai également une version "multi-sous-systèmes" du code pour les programmes plus volumineux, de sorte que différentes sections du programme produisent différentes quantités de trace - sous le contrôle de l'exécution.

Je préconise que pour toutes les générations, le compilateur devrait voir les déclarations de diagnostic; cependant, le compilateur ne générera aucun code pour les instructions de trace de débogage à moins que le débogage ne soit activé. En gros, cela signifie que tout le code est vérifié par le compilateur à chaque compilation, 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, faire ceci pour que debug_print fonctionne toujours même s'il n'y a pas d'argument? Par exemple:

    debug_print("Foo");

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

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

La solution GCC uniquement indiquée ci-dessous fournit également une assistance à cet égard.

Cependant, vous pouvez le faire avec le système straight C99 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 requiert 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() ne réussirait pas à compiler). La question de savoir si la perte de contrôle est un problème est discutable.

Technique spécifique à GCC pour un seul argument

Certains compilateurs peuvent proposer des extensions pour d'autres moyens de gérer des listes d'arguments de longueur variable dans des macros. Plus précisément, comme indiqué pour la première fois dans les commentaires par Hugo Ideler , GCC vous permet d’omettre la virgule qui apparaîtrait normalement après le dernier argument "corrigé" de la macro. Il vous permet également d’utiliser ##__VA_ARGS__ dans le texte de remplacement de la macro, ce 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 de nécessiter l'argument de format tout en acceptant des arguments facultatifs après le format.

Cette technique est également supportée par Clang pour la compatibilité GCC.


Pourquoi la boucle do-while?

Quel est le but du do while ici?

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 adapter le corps de la macro à votre convenance. Si vous utilisez une instruction if sans la do { ... } while (0) environnante, vous aurez:

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

Maintenant, supposons que vous écrivez:

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ées pour souligner le sens réel):

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

La prochaine tentative de la 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 le else est maintenant une erreur de syntaxe. La boucle do { ... } while(0) é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. La conversion (void) empêche son utilisation dans des contextes dans lesquels une valeur est requise - mais il peut être utilisé comme opérande gauche d'un opérateur de virgule dans lequel la version do { ... } while (0) ne peut pas. Si vous pensez pouvoir intégrer le 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 version do { ... } while (0) est préférable. Notez que si le corps de la macro implique des points-virgules (en gros), vous ne pouvez utiliser que la notation do { ... } while(0). Ça marche toujours; le mécanisme de déclaration d'expression peut être plus difficile à appliquer. Vous pouvez également recevoir des avertissements du compilateur avec la forme d'expression que vous préférez éviter. cela dépendra du compilateur et des drapeaux que vous utilisez.


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


Code dans GitHub

Si vous êtes curieux, vous pouvez consulter ce code dans GitHub dans mon référentiel SOQ (Questions de débordement de pile) sous la forme des fichiers debug.c, debug.h et mddebug.c dans le src/libsoq = sous-répertoire.

388
Jonathan Leffler

J'utilise quelque chose comme ça:

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

Alors je viens d'utiliser D comme préfixe:

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

Le compilateur voit le code de débogage, il n’ya pas de problème de virgule et cela fonctionne partout. Cela fonctionne également lorsque printf ne 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 else quelque part près qui peut être intercepté par cette if injectée. Ceci est une version qui va au-dessus:

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

Pour une implémentation portable (ISO C90), vous pouvez utiliser des doubles parenthèses, 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, 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;
}
10
Marcin Koziuk

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
Christoph

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. 

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

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

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

5
Chobits Tai

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

J'ai utilisé cette variante dans un projet Arduino où l'espace de programme est limité à 32 Ko et la mémoire dynamique limitée à 2 Ko. L'ajout d'instructions de débogage et de suivi des chaînes de débogage utilise rapidement de l'espace. Il est donc essentiel de pouvoir limiter la trace de débogage incluse lors 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

appelant le fichier .cpp

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

...
PRINT(4, "Time out error,\t");
...
0
user358795

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

#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 pour:

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 groupes électrogènes laisse la porte ouverte aux conflits de noms.

Cependant, il 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

Donc, je pense que tant que vous évitez d’utiliser g2rE3 comme nom de variable, tout ira bien.

Certes, je l'ai trouvé (et les versions alliées pour les chaînes, et les versions pour les niveaux de débogage, etc.) inestimable.

0

C'est 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 à redire, aucun code n'est généré.

0
5tenzel