Faire un don ! | | style | statistiques | contactez-nous | plan | lettre d'information

Journal : printf debugging considered harmful

Posté par Krunch (Jabber id, page perso, ) le 05 septembre 2006

Après avoir vainement cherché un « printf debugging considered harmful » j'en suis arrivé à la conclusion qu'il n'existait pas et j'ai donc décidé de l'écrire moi même.

J'appelle « printf debugging » le fait d'ajouter du code temporaire dans le seul but de débugger du code existant. En C, le code ajouté étant généralement un appel à printf(3) permettant par exemple de retrouver quelle partie du code a été exécutée avant le segfault dont on cherche la cause. Cette technique est particulièrement populaire chez les programmeurs débutants qui n'ont pas envie d'apprendre à se servir d'un debugger « parce qu'ils s'en passent très bien » (moi aussi j'ai dit ça à une époque). Dans ce document je tente de montrer que le printf debugging pose un certains nombre de problèmes que n'ont pas d'autres méthodes.

$ while true; do vi && make && ./plop ; done

Le premier problème le plus évident qu'on rencontre quand on pratique le printf debugging est qu'il est nécessaire de recompiler et réexécuter le code concerné plusieurs fois. En effet, on trouve rarement le bug en rajoutant un seul printf, d'où plusieurs cycles d'édition, compilation, exécution qui font perdre un temps considérable.

Le deuxième problème qui apparaît lorsque l'on a ajouté des printf un peu partout est de les retrouver tous pour les enlever une fois le bug corrigé. Ca semble trivial mais ça prend aussi du temps et il n'est pas rare d'en oublier l'un ou l'autre caché au fin fond d'une branche d'exécution qui sera prise trop rarement pour être remarqué rapidement.

Une alternative permettant d'éviter ce problème est d'utiliser un système de logging permanent (dés)activable plus ou moins dynamiquement (via une variable d'environnement, un argument ou une option de compilation par exemple) associé à un système d'assertions. De plus ça incite à écrire des messages compréhensible plutôt qu'un « toto » qui n'aura plus de signification pour personne une fois la session de debugging finie. Il existe de nombreux systèmes de logging plus ou moins complexes mais pour commencer autant s'en tenir à un simple macro qui affiche ou non son argument selon que l'on veuille afficher les logs ou non [1].

« C'est Heinsenberg qui est sur l'autoroute... » [2]

Un autre problème plus rare mais bien plus pervers est que l'ajout de code peut modifier ou masquer le bug. C'est ce qu'on appelle un heinsenbug : il devient inobservable quand on essaie d'en déterminer la nature mais réapparaît aussitôt le printf retiré. Ce genre de bug à tendance à se retrouver dans les programmes concurrents mais peut aussi être causé par de subtiles problèmes dans la gestion de la mémoire et sans doute d'autres choses. L'utilisation d'un debugger classique permet de retrouver un certains nombre de ces heinsenbugs mais pas tous.

D'autres problèmes qui peuvent sembler plus triviaux sont liés au printf debugging. Il est par exemple impossible de debugger printf lui même avec cette méthode et les systèmes de cache la rendent souvent inutile si on n'en tient pas compte.

En conclusion, le printf debugging peut être utile dans certains cas mais on rencontre vite ses limites qui peuvent être dépassées avec un usage judicieux d'un système de logging, d'assertions et d'un debugger.

À lire aussi : « Debugging 101 » [3]. Cet article déconseille l'utilisation générale de debuggers « classiques » en faveur du prinf debugging mais je persiste à penser que l'utilisation d'un système de logging et d'un debuggers plus évolués [4] reste souvent préférable. Et je suis tout à fait d'accord avec l'utilisation du « design by contract » tel qu'expliqué (ainsi qu'avec la plupart du reste de l'article).

[1] Personnellement en C99 pour des petits projets j'utilise ceci :

#ifndef NDEBUG
#  define debug(...)  fprintf(stderr, __VA_ARGS__)
#else
#  define debug(...)
#endif
Quand on développe, on compile normalement et quand on passe en « production », on définit le macro NDEBUG qui éliminera aussi les assert(3). Le macro debug() s'utilise de la même manière que printf(3) mais on le laissera dans le code final.

[2] Heinsenberg est sur l'autoroute au volant de sa voiture quand il se fait interpeller par un agent de police. « Vous savez à quelle vitesse vous rouliez ? » « Non, mais je sais où je suis » Si vous n'avez pas compris, vous devriez chercher de la documentation sur le principe d'incertitude d'Heisenberg. Je déconseille cette blague dans les soirées comportant moins de 50% de physiciens/chimistes/ingénieurs.

[3] http://www.hacknot.info/hacknot/action/showEntry?eid=85

[4] L'« Omniscient Debugger » par exemple permet de retourner en arrière dans l'exécution du code.
http://www.lambdacs.com/debugger/debugger.html
http://video.google.com/videoplay?docid=3897010229726822034

> Lire le journal (117 commentaires, moyenne: 3,4).  

Vous avez demandé le commentaire #751373.

printf, pour débutant ?

Posté par Laurent J (page perso, ) le 06/09/2006 à 12:17. (lien). Évalué à 4.

Bon, certains commentaires l'ont déjà signalé, un debugger fait pas tout. Et je vais en rajouter.

1) gdb : j'ai pas vu de debugger aussi inutilisable que gdb, aussi lourd, et aussi lent. Surtout sur des gros programmes (même si c'est sur une toute petite partie que tu veux debogger).

Et je ne parle pas de toutes ces commandes qu'il faut apprendre, des front-ends limités ou non ergonomique, et qui par nature, souffrent des mêmes défauts de gdb lui même..

Si il y a bien quelque chose à féliciter à MS, c'est le deboggeur de Visual Studio. Tout est quasi instantannée, interface nickelle. Vivement le jour où on pourra virer gdb sous linux et avoir un déboggeur aussi efficace que celui de VS.

2)

Le deuxième problème qui apparaît lorsque l'on a ajouté des printf un peu partout est de les retrouver tous pour les enlever une fois le bug corrigé.


Mauvais éditeur, changer éditeur. Et changer pour un éditeur qui contient une véritable fonction de recherche multi fichier.

3) Gdb est inutilisable dans un programme qui travaille sur un volume de donnée conséquent. Exemple : j'ai réalisé un validateur xml, basé sur relaxng. J'avais des bugs lors du parsing du schema de docbook, qui contient des milliers de patterns. Trés franchement, il est inutile de tenter d'utiliser le deboggeur : pas envie de repasser 3500 fois dans la même méthode pour arriver jusqu'au 3500ieme pattern qui provoque le bug.
La seule solution : le printf pour se générer un log. C'est ce que je fais sur des traitements "moyens".

Sinon j'utilise comme tu dis "un système de logging permanent (dés)activable plus ou moins dynamiquement ". En particulier dans mes devs Mozilla. Mozilla possède un système de log trés sympa (PrLog), que tu peux activer ou désactiver via une variable d'environnement (avec plusieurs niveaux d'activation). (et n'est pas compilé pour la production d'une version optimisée de l'application).

Bref, chaque solution (printf, loggeurs évolués, deboggueur) est utile selon les cas.

  • [^]Re: printf, pour débutant ?

    Posté par Krunch (Jabber id, page perso, ) le 06/09/2006 à 12:24. (lien). Évalué à 6.

    pas envie de repasser 3500 fois dans la même méthode pour arriver jusqu'au 3500ieme pattern qui provoque le bug
    J'ai pas vu le code/bug concerné mais tu sais que tu peux mettre des conditions sur les breakpoints ?

    --
    Free Softwares Users Group Arlon (Sud Luxembourg, Belgique)
    pertinent, e adj. Approprié ; qui se rapporte exactement à ce dont il est question.
    • [^]Re: printf, pour débutant ?

      Posté par Nicolas Schoonbroodt (Jabber id, page perso, ) le 06/09/2006 à 12:29. (lien). Évalué à 4.

      Si tu ne sais pas lequel provoque le bug (c-à-d si c'est justement ce que tu cherche) c'est moins simple. Je ne sais pas si c'est qu'il a voulu dire, mais c'est ce que j'ai cru comprendre en lisant.

      --
      [ Répondre ] Ce commentaire est-il impertinent ou utile ?
      • [^]Re: printf, pour débutant ?

        Posté par √λιi () le 06/09/2006 à 14:05. (lien). Évalué à 6.

        Si tu ne sais pas lequel provoque le bug
        Si tu sais, printf et gdb ne sont pas exclusif :
        procesing pattern 3501 ... passed
        procesing pattern 3502 ...
        segfault
        
        puis
        (gdb) break 523 if partternnum = 3502
        
        non de d'la !

        [^]Re: printf, pour débutant ?

        Posté par Laurent J (page perso, ) le 06/09/2006 à 14:29. (lien). Évalué à 3.

        effectivement, quand on ne sait pas vraiment quelle "valeur en entrée" fait planter le truc... Le log est le seul moyen.

        Aprés effectivement, si on arrive à determiner des conditions d'apparitions du bug, on peut passer au deboggeur avec des breakpoint conditionnels. Par contre je ne savais pas que ça se faisait, les breakpoints conditionnels (dans kdevelop apparement, il ne permet pas de specifier ce genre de truc, et la ligne de commande de gdb, je ne supporte pas)

        • [^]Re: printf, pour débutant ?

          Posté par Krunch (Jabber id, page perso, ) le 06/09/2006 à 14:38. (lien). Évalué à 3.

          ddd supporte les breakpoints conditionnels apparement. C'est pas très joli mais c'est à la souris (avec possibilité d'entrer des commandes gdb à la main quand même).

          --
          Free Softwares Users Group Arlon (Sud Luxembourg, Belgique)
          pertinent, e adj. Approprié ; qui se rapporte exactement à ce dont il est question.

          [^]Re: printf, pour débutant ?

          Posté par Thomas Douillard () le 06/09/2006 à 15:06. (lien). Évalué à 5.

          C'est possible dans eclipse avec CDT.

          Après c'est parfois difficile de déterminer la condition dans des cas tordus, je suis pas sûr qu'il soit possible de mettre un appel de méthode dans la condition par exemple.

          • [^]Re: printf, pour débutant ?

            Posté par peau chat () le 06/09/2006 à 15:44. (lien). Évalué à 4.

            En fin de compte, le plus important n'est pas de savoir COMMENT debugger le code, mais de trouver une solution pour debugger le cerveau du programmeur (i.e. bien souvent son propre cerveau).

            ;)

          [^]Re: printf, pour débutant ?

          Posté par tene (page perso, ) le 10/09/2006 à 12:51. (lien). Évalué à 1.

          euh y'a pas moyen de dire à son gentil debugger de ... faire son break quand le segfault arrive?

          Y'a du remote debugging avec gdb, j'image aussi?

          • [^]Re: printf, pour débutant ?

            Posté par Matthieu Moy (page perso, ) le 11/09/2006 à 05:12. (lien). Évalué à 2.

            > euh y'a pas moyen de dire à son gentil debugger de ... faire son break quand le segfault arrive?

            Euh, c'est un peu le mode par défaut ...

            Mais bug != segfault.

            Et par ailleurs, en général, l'endroit intéressant, il est quelques instructions avant le segfault (genre « Grmbl, comment ça il est nul mon pointeur, je viens de l'initialiser ? Ah, en fait, non ! »).

            • [^]Re: printf, pour débutant ?

              Posté par Erwan (page perso, ) le 13/09/2006 à 19:29. (lien). Évalué à 3.

              Mais bug != segfault.

              C'est pour ca que ce que je fais, c'est mettre des asserts partout pour stopper le processus des que quelque chose d'anormal arrive (genre au debut d'une fonction, les parametres d'entree ne respectent pas certains criteres). Ca aide a debugger, car sinon on se retrouve a planter plus loin.

              Je met ca dans des #ifdef DEBUG, comme ca je desactive les asserts pour la version que je livre/publie. Si quelque chose va mal, il y aura peut-etre un comportement bizarre mais moins violent qu'un arret du processus pour l'utilisateur.

              • [^]Re: printf, pour débutant ?

                Posté par Mildred (Jabber id, page perso, ) le 14/09/2006 à 05:45. (lien). Évalué à 4.

                man assert :

                DESCRIPTION
                       Si  la  macro  NDEBUG  était  définie  lors de la dernière inclusion de
                       <assert.h>, la macro assert() ne génère aucun code, et  ne  fait  rien.
                       Sinon,  la  macro  assert()  affiche  un message d’erreur sur la sortie
                       standard, et termine l’exécution du  programme  en  cours  en  appelant
                       abort() si l’expression est fausse (égale à zéro).
                
                Donc pas besoin de #ifdef DEBUG

    [^]Re: printf, pour débutant ?

    Posté par norbs () le 06/09/2006 à 23:07. (lien). Évalué à 1.

    > Mauvais éditeur, changer éditeur. Et changer pour un éditeur qui
    > contient une véritable fonction de recherche multi fichier.

    Serait-ce une attaque contre Emacs ???