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_print
toujours 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 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 empaqueter le corps de macro en fonction. Si vous utilisez une if
dé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 else
c'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.h
et mddebug.c
dans le
sous-répertoire src / libsoq .