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?
c
c-preprocessor
jfarrell
la source
la source
Réponses:
Si vous utilisez un compilateur C99 ou version ultérieure
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:
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: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:
Et puis, dans le code, écrivez:
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:
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)
debug.h - version 3.6 (2008-02-11)
Variante à argument unique pour C99 ou version ultérieure
Kyle Brandt a demandé:
Il y a un hack simple et démodé:
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:
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: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?
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 environsdo { ... } while (0)
, vous aurez:Supposons maintenant que vous écriviez:
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):
La prochaine tentative de macro pourrait être:
Et le même fragment de code produit maintenant:
Et
else
c'est maintenant une erreur de syntaxe. Lado { ... } while(0)
boucle évite ces deux problèmes.Il existe une autre façon d'écrire la macro qui pourrait fonctionner:
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ù lado { ... } 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, lado { ... } while (0)
version est meilleure. Notez que si le corps de la macro impliquait des points-virgules (grosso modo), vous ne pouvez utiliser que lado { ... } 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
etmddebug.c
dans le sous-répertoire src / libsoq .la source
#define debug(...) \ do { if (DEBUG) \ printk("DRIVER_NAME:"); \ printk(__VA_ARGS__); \ printk("\n"); \ } while (0)
__FILE__, __LINE__, __func__, __VA_ARGS__
, il ne compilera pas si vous avez aucun paramètre de printf, à savoir si vous appelez simplementdebug_print("Some msg\n");
Vous pouvez résoudre ce problème en utilisantfprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__);
le ## __ VA_ARGS__ permet de passer aucun paramètre à la fonction.#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 utilisezdebug_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».-D input=4,macros=9,rules=2
pour 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.J'utilise quelque chose comme ça:
Que j'utilise simplement D comme préfixe:
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
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 à proximité qui peut être intercepté par cette injectionif
. Voici une version qui va plus loin:la source
for(;0;)
, cela pourrait générer un problème lorsque vous écrivez quelque chose commeD continue;
ouD break;
.Pour une implémentation portable (ISO C90), vous pouvez utiliser des parenthèses doubles, comme ceci;
ou (hackish, je ne le recommanderais pas)
la source
Voici la version que j'utilise:
la source
Je ferais quelque chose comme
Je pense que c'est plus propre.
la source
assert()
à partir du stdlib fonctionne de la même manière et je réutilise normalement laNDEBUG
macro pour mon propre code de débogage ...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");
.la source
la source
Voici ce que j'utilise:
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é.
la source
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
la source
Donc, quand j'utilise gcc, j'aime:
Parce qu'il peut être inséré dans le code.
Supposons que vous essayez de déboguer
Ensuite, vous pouvez le changer en:
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:
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.
la source
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
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:
DebugLog.cpp:
Utilisation des macros
Pour l'utiliser, faites simplement:
Pour écrire dans le fichier journal, faites simplement:
Pour le fermer, vous faites:
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
Pour que les instructions de journalisation soient compilées sur rien, faites
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:
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.
Pas terriblement significatif, mais en plus:
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#define
s, 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
./configure
commandes 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.
la source
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).((void)0)
- c'est facile.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
appel du fichier .cpp
la source