254 votes

C #define macro pour l’impression de débogage

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

Comment cela est réalisé avec une macro ?

484voto

Jonathan Leffler Points 299946

Si vous utilisez un compilateur C99

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

Il suppose que vous êtes à l'aide de C99 (la liste d'arguments variable notation n'est pas pris en charge dans les versions antérieures). L' do { ... } while (0) idiome s'assure que le code agit comme un énoncé (appel de fonction). L'utilisation inconditionnelle du code garantit que le compilateur vérifie toujours que votre déboguer le code est valide, mais l'optimiseur de supprimer le code lorsque le DÉBOGAGE est de 0.

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

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

Et puis utiliser DEBUG_TEST où j'ai utilisé de DÉBOGAGE.

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 présenter les choses comme __FILE__, __LINE__ et __func__ à 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îne de créer un plus grand format de chaîne de caractères que le programmeur écrit.

Si vous utilisez un compilateur C89

Si vous êtes coincé avec C89 et pas de compilateur extension, il n'est pas particulièrement propre façon de le gérer. La technique que j'ai utilisé était:

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

Et puis, dans le code, écrire:

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

Le double parenthèses sont indispensables et sont la raison pour laquelle vous avez le plus drôle de la notation de la macro-expansion. Comme avant, le compilateur vérifie toujours le code pour la validité syntaxique (qui est bonne), mais l'optimiseur seulement invoque la fonction d'impression si le DÉBOGAGE macro renvoie la valeur non-nulle.

Cela va nécessiter une fonction d'appui — dbg_printf() dans l'exemple — pour gérer des choses comme "stderr'. Il exige de vous que vous savez comment écrire des varargs fonctions, mais ce n'est pas dur:

#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 l' __VA_ARGS__ technique est plus lisible car il utilise régulièrement la fonction de notation, pas le double de parenthèses hack.

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

[Ressasser les commentaires faits à une autre réponse.]

Une idée centrale derrière la C99 et C89 implémentations ci-dessus est que le compilateur approprié voit toujours le débogage printf déclarations. Ceci est important pour le long terme, code — code qui va durer une décennie ou deux.

Supposons qu'un morceau de code a été la plupart du temps en sommeil (stable) pour un certain nombre d'années, mais maintenant il doit être changé. Vous re-activer le débogage trace, mais il est frustrant de devoir de débogage débogage (suivi de) code car il fait référence à des variables qui ont été renommés ou retapé, pendant les années de maintien stable. Si le compilateur (post-pré-processeur) voit toujours l'impression de déclaration, il s'assure que les changements n'ont pas invalidé les diagnostics. Si le compilateur ne pas voir le rapport d'impression, il ne peut pas vous protéger contre votre propre négligence (ou de la négligence de vos collègues ou de collaborateurs). Voir"la Pratique de La Programmation"par Kernighan et le Brochet, en particulier le Chapitre 8.

C'est "été là, fait que" l'expérience — j'ai utilisé essentiellement la technique décrite dans d'autres réponses, où le non-debug ne voit pas le printf déclarations pour 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 a fait de permettre à certains de débogage de code après un certain nombre d'années, et a couru dans des problèmes de changement de contexte briser le débogage. Plusieurs fois, avoir l'impression de toujours validé m'a sauvé de problèmes ultérieurs.

J'utilise NDEBUG de contrôler les assertions seulement, et une autre macro (généralement de DÉBOGAGE) pour contrôler si le traçage de débogage est intégrée dans le programme. Même lorsque le traçage de débogage est intégré, j'ai souvent ne veulent pas sortie de débogage pour apparaître de manière inconditionnelle, j'ai donc un mécanisme pour contrôler si la sortie s'affiche (les niveaux de débogage, et au lieu d'appeler fprintf() directement, je l'appelle un de débogage de la fonction d'impression que conditionnellement imprime donc de la même génération de code permet d'imprimer ou de ne pas imprimer basé sur les options du programme). J'ai aussi un "multi-sous-système" version du code pour de gros programmes, afin que je puisse avoir différentes sections du programme de production de différentes quantités d'oligo - en cours d'exécution, de contrôle.

Je préconise que, pour toutes les versions, le compilateur devrait voir le diagnostic des états; cependant, le compilateur ne génère pas de code pour le débogage des instructions de suivi, sauf si le débogage est activé. Fondamentalement, cela signifie que l'ensemble de votre code est vérifié par le compilateur à chaque fois que vous compilez - que ce soit pour la libération ou de 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 */

Seul argument C99 variante

Kyle Brandt demandé:

De toute façon pour ce faire, debug_print fonctionne toujours, même si il n'y a pas d'arguments? Par exemple:

    debug_print("Foo");

Il y a un simple, à l'ancienne, hack:

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

La GCC seule solution fournit également un soutien pour cela.

Cependant, vous pouvez le faire avec la droite C99 système à l'aide de:

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

Par rapport à la première version, vous perdez le peu de vérification qui exige la 'fmt' argument, ce qui signifie que quelqu'un pourrait appeler la " debug_print () sans arguments. Si la perte de contrôle est un problème, tout est discutable.

GCC-Technique spécifique

Certains compilateurs peuvent vous proposer des extensions pour les autres moyens de la manipulation de la variable longueur des listes d'arguments dans les macros. Plus précisément, comme observé pour la première fois dans les commentaires par Hugo Ideler, GCC permet d'omettre la virgule qui doit normalement s'afficher après le dernier "fixe" argument de la macro. Il vous permet également d'utiliser ##__VA_ARGS__ dans la macro de texte de remplacement, qui supprime la virgule précédant la notation si, et seulement si, le jeton précédent, c'est une virgule:

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

Cette solution conserve les avantages de la première version.


Pourquoi la boucle while?

Quel est le but de l' do while ici?

Vous voulez être en mesure d'utiliser la macro afin qu'il ressemble à un appel de fonction, ce qui signifie qu'il sera suivi par un point-virgule. Par conséquent, vous devez package de la macro corps de la fonction. Si vous utilisez un if déclaration sans les environs do { ... } while (0),, 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, que l'indentation ne reflète pas le réel contrôle de débit, car le préprocesseur produit un code équivalent à ceci (en retrait et les accolades ajout de 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 à la macro peut ê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 l' else est maintenant une erreur de syntaxe. L' 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 d'un programme affiche comme étant valide. L' (void) cast, il l'empêche d'être utilisé dans des contextes où une valeur est requise — mais il pourrait être utilisé comme opérande de gauche d'un opérateur virgule où l' do { ... } while (0) version ne peut pas. Si vous pensez que vous devriez être en mesure d'intégrer le code de débogage dans ces expressions, vous préférerez peut-être cela. Si vous préférez exiger le débogage impression d'agir comme un énoncé complet, puis l' do { ... } while (0) version est mieux. Notez que si le corps de la macro impliqués des points-virgules (en gros), alors vous ne pouvez utiliser l' do { ... } while(0) de la notation. Il fonctionne toujours; l'expression de la déclaration de mécanisme peut être plus difficile à appliquer. Vous pouvez également obtenir des avertissements du compilateur avec l'expression de la forme que vous préférez à éviter; il dépend du compilateur et les options que vous utilisez.

32voto

mbq Points 8963

J'utilise quelque chose comme ceci:

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

Que je viens de l'utiliser D un préfixe:

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

Compilateur voit le débogage de code, il n'y a pas de virgule problème et il fonctionne partout. Aussi il fonctionne lorsque printf n'est pas suffisant, par exemple lorsque vous devez faire un dump d'un tableau ou de calculer certaines diagnostic de la valeur qui est redondant pour le programme lui-même.

EDIT: Ok, ça peut générer un problème quand il y a else quelque part près qui peut être intercepté par cette injecté if. C'est une version qui va dessus:

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

11voto

Marcin Koziuk Points 478

Pour une mise en œuvre (ISO C90) portable, vous pourriez utiliser les doubles parenthèses, comme ceci ;

ou (hackers, ne le recommanderais pas)

10voto

Christoph Points 64389

Voici la version que j’utilise :

9voto

LB40 Points 4372

Je ferais quelque chose comme

Je pense que c’est plus propre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X