jeudi 9 octobre 2014

Dernier cours de mise à niveau

Débugger mémoire


Aujourd'hui, pour notre dernier cours, je vais vous montrer pourquoi et comment utiliser un debugger mémoire. Nous utiliserons valgrind.

Pourquoi ?

Le comportement d'un programme qui fait des erreurs mémoire est imprévisible, il est donc très très difficile à débugger. Principalement parce que les erreurs ne seront visible que rarement, et que le message d'erreur sera uniquement "segmentation fault" (sous entendu "erreur de segment mémoire") avec un arrêt prématuré de l'exécution du programme. Tout cela ne vous aidera pas trouver le problème et encore moins à le corriger...
Si vous voulez produire un programme sûr et fiable, vous devez utilisez un debugger mémoire qui surveillera l'utilisation de la mémoire pour identifier les erreurs dès la première exécution du programme.

Comment ?

Valgrind va surveiller l'utilisation de la mémoire faite par votre programme lors de son exécution (ce que ne fait pas le compilateur!).

Pour cela nous allons d'abord devoir ajouter une option lors de la compilation, l'option -g :

       gcc monCode.c -o monExecutable -g

Ceci aura pour effet de garder l'information des numéros de ligne du code source dans l'exécutable.
Ensuite pour l'exécution du programme nous allons préfixer son appel par la commande valgrind :

       valgrind ./monExecutable
     
Le programme se déroule alors exactement comme normalement, sauf que toute utilisation de la mémoire sera pistée, et en cas d'erreur, un rapport sera fait dans le terminal. Ce qui fait que le programme s'exécutera un peu plus lentement. En cas d'erreur, valgrind sera capable de retrouver les numéros de ligne du code source correspondant aux instruction qui posent problème.
Voyez le texte sur cette page : http://valgrind.org/docs/manual/quick-start.html#quick-start.intro
pour comprendre comment interpréter ce rapport.

L'interprétation de ces message n'est pas chose simple au départ, il faut un peu d'exercice.
Je vous propose donc de faire un petit programme dans lequel vous ferez successivement les différents type d'erreurs mémoire que peut détecter valgrind.
À chaque fois vous compilerez le programme, vous constatez que la nature de l'analyse statique faite par le compilateur est incapable de déceler les erreurs. Puis vous l'exécuterez avec valgrind, et vous chercherez à faire le lien entre le rapport produit par valgrind et l'erreur que vous connaissez (puisque vous l'avez faites exprès.). Voici les différents cas  à tester :
  • Utilisation d'une zone mémoire non-initialisée. Faites un malloc puis affichez le contenu de la zone mémoire en question (sans jamais y avoir mi quelque chose au préalable).
  • Lecture d'une zone mémoire non allouée : utilisez un pointeur jamais alloué et affichez son contenu.
  • Même chose que la question précédente mais en écriture. 
  • Dépassement de tableau : allouez un tableau avec malloc, puis utilisez une case qui est au delà de la zone allouée. Exemple créez un tableau de 10 cases et écrivez dans la case No 12.
    Note : valgrind ne surveille pas les allocations statiques (avec les []), encore une raison supplémentaire pour ne plus les utiliser,et faire des malloc.
  • Mettez l'une des erreurs précédentes dans une boucle. Vous constaterez le coté dynamique de l'analyse proposée par valgrind.
  • Après avoir corrigé l'erreur de la question précédente, essayez de voir où est spécifié dans le rapport que vous n'avez pas fait la désallocation du tableau. (Si vous aviez mis un free, je vous félicite ;) , mais , pour l'expérience, enlevez le.). Cela s'appelle une fuite mémoire, si votre programme en fait trop il peut sérieusement ralentir la machine sur laquelle il tourne. (souvenez vous de l'expérience faites lors d'un cours précédent où nous avions occupé toute la mémoire d'un ordinateur...)

Mise en pratique en situation réelle

Voilà maintenant vous êtes en capacité de tenter un vrai débuggage avec valgrind. Prenez votre programme de jeu de pendu. Je vous rappelle que vous deviez l'avoir finalisé et avoir replacé toutes les allocations [] par des malloc (si ce n'est pas fait il très est urgent de le faire). Testez le avec valgrind, il est assez vraisemblable qu'il y ait des erreurs même si votre programme n'a jamais planté...  Débuggez le !
Pensez aussi à vérifier que vous désallouez bien la mémoire en fin de programme.

Si votre programme ne comporte aucune erreur ni aucune fuite mémoire, demandez à ce qu'un de vos camarades vous passez le sien (comportant des erreurs) et débuggez le.

Il est très important que vous vous soyez entraîné à utiliser cet outil dans un contexte simple, avant de vous retrouver dans la situation de devoir débugger un programme complexe.

Conclusion

Nous n'avons pas pu tout voir dans ce cours de mise à niveau, mais nous aurons vu quelques bases qui vous permettrons d'aborder la suite du programme d'informatique.

Voilà j'espère que vous avez apprécié ces cours. Je vous souhaite bon courage pour la suite.