Date de première publication : 2014/10/3
Nous allons nous intéresser dans ce TP à la compilation séparée (commande make et fichier makefile), au profilage d’applications, à la génération d’une documentation (avec doxygen) et à la conception d’une bibliothèque. Tous ces outils vous seront utiles pour les TP de SDD, les projets et votre vie professionnelle.
QuiZZ valgrind / ddd
Vous connaissez déjà (ou en tout cas vous devriez connaître) :
- ddd la surcouche graphique de gdb, le débogueur.
- valgrind qui permet en autres de repérer les fuites mémoire et les variables non initialisées
Pour mesurer votre degré de connaissances, répondez aux questions suivantes :
- Quelle option de compilation est obligatoire pour l’utilisation de ces outils ?
déboGueur et valGrind
- Savez-vous lancer valgrind en activant l’option de recherche des variables non initialisées ?
Elle s'invente pas mais elle se lit dans la sortie de valgrind en cas de problème --track-origins=yes
- Savez-vous lancer ddd sur un exécutable ? sur un exécutable avec un fichier coredump ?
Cela se fait en ligne de commande... ou par les menus une fois que ddd est affiché
- Savez-vous déboguer un programme qui demande des paramètres en ligne de commande ?
En ligne de commande : $ ddd -args program arg1 arg2
.
Dans la boite de commande de ddd : set args arg1 arg2
ou encore run arg1 arg2
show args
pour vérifier
- Savez-vous placer un point d’arrêt ?
Le double clic par interface graphique ou la commande break no_numero
- Quelle est la différence entre next, step et continue?
- continue permet d'aller jusqu'au prochain point d'arrêt ou à la fin du programme
- next permet d'exécuter ligne de code par ligne de code sans plonger dans les fonctions si la ligne de code est une fonction
- step, au contraire va dedans
- Comment obtenir facilement la valeur d’une variable ?
Il suffit de laisser trainer la souris sur la variable ou alors de taper la commande print variable
. Pour que le survol fonctionne, il ne faut pas d'accent dans le code source.
Compilation séparée
Nous allons prendre un code "commenté" que vous ne connaissez pas... L’idée est d’abord de le compiler et de l’exécuter pour voir ce qu’il fait. Même si le fichier n’est pas énorme, nous vous demandons de lui appliquer le principe de la compilation séparée avec des fichiers entête et fichiers de code, le tout compilé et lié avec des commandes dans un fichier makefile.
Le programme utilise les arguments en ligne de commande, définit quelques macros-commandes, des structures de données et des fonctions. Il y a même utilisation d’une variable globale (elle doit être définie dans un seule fichier d’implémentation mais peut être déclarée grâce au mot-clé extern). Une fois tout cela fait, vous regardez si tout se passe bien ;-)
Prise en main
- Récupérez le fichier et compilez-le. Les erreurs de type "undefined reference" ou "référence inddéfinie" devraient être assez facile à régler.
-lX11
pour le premier et -lSDL2 -lSDL2_ttf pour le second
- Lancez-le, de l'aide est affichée.
- Corrigez l'erreur de segmentation. Passez-y 5 minutes au maximum. valgrind doit vous aider à trouver l'erreur.
La liste a-t-elle été correctement initialisée ?
- Lancez le programme en mode graphique avec le jeu d'essai par défaut.
- N'y aurait-il pas par hasard une information choquante qui est affichée ? Pour déboguer le programme, utiliser la constante symbolique (compilation conditionnelle)
DEBUG
.
Vous pouvez activer la constante de deux manières :
- Ajouter la ligne
#define DEBUG
dans le code - ou bien vous pouvez utiliser l'option
-DDEBUG
à la compilation.-D permet de définir
alors que-U
permet d'effacer ("undefine")
En moyenne, vous ne mettez pas longtemps à trouver ce qui ne va pas.
- Maintenant, il faut également vérifier une information non visuelle ... Le développeur s'est oublié...
Y a besoin d'une pampers là ...
Compilation séparée
Si l'on regarde ce qui est défini dans le fichier, on trouve de la manipulation de liste chaînée, d'histogramme et de l'affichage texte ou graphique.
Je vous propose donc le découpage suivant :
- Des fichiers liste.h et liste.c pour gérer la liste chaînée
- Des fichier histo.h et histo.c pour gérer l'histogramme
- Un fichier commun.h pour gérer toutes les déclarations communes
- Un fichier main.c pour le reste du code dont la fonction
main()
Vous pourrez compiler à la main dans un premier temps mais à la fin, il faudra un makefile fonctionnel.
Un fichier objet.o n'a en général qu'une dépendance de type fichier c. En revanche, il peut y avoir plusieurs fichiers d'entête. Pour un fichier A.o, on aura probablement A.c et A.h puis tous les fichiers inclus par A.h en cascade, jusqu'à trouver des fichiers standards qu'il ne faut pas inclure.
Pour vérifier vos dépendances, vous pouvez taper la ligne suivante :
gcc -MM A.c
Pour le fun, je vous demande de mettre en commentaire les gardiens d'un fichier comme "commun.h" et d'inclure ce fichier deux fois dans le même fichier et de voir le message d'erreur !-)
Création d'une bibliothèque
Créer une bibliothèque permet d’aller plus loin dans le processus de réutilisation de code déjà écrit. Les bibliothèques peuvent être statiques ou au contraire dynamiques. Une bibliothèque statique est liée à l’exécutable (qui grossit d’autant). Ce n’est pas le cas d’une bibliothèque dynamique qui est chargée à l’exécution du programme (il faut donc que cette bibliothèque soit disponible sur la machine).
Trouvez la bibliothèque mathématique dont le nom est de la forme libm*.so où * est un numéro de version
Exécuter la commande nm –s libm_bonne_version.so
? Que fait-elle ?
La commande nm
peut s'appliquer sur un fichier objet, sur un fichier exécutable, sur une bibliothèque statique (fichier d'extension .a
), sur une bibliothèque dynamique (fichier d'extension .so
sous linux, .dylib
sous mac, [et .dll
sous windows pour info]).
Prendre un fichier fic. c. la suite de commandes suivantes permet de le transformer en bibliothèque statique :
$ gcc –c fic.c
$ ar r libfic.a fic.o (puis vérifier par ar t libfic.a)
$ ranlib libfic.a permet de construire un index des fonctions.
Pour les bibliothèques partagées, ce n’est guère plus compliqué (mais il faut utiliser des options comme –fPIC, -shared, …).
Transformez liste.c en bibliothèque !
Pour utiliser une bibliothèque, n’oubliez pas les options –I (i majuscule), -L (l majuscule) et le LD_LIBRARY_PATH
#include "fichier1.h"
recherche le fichier1.h à partir du répertoire courant
#include <fichier2.h>
recherche le fichier2.h à partir d'une liste de répertoires standards et d'une liste prédéfinie (LD_LIBRARY_PATH)
$ LD_LIBRARY_PATH=. make
permet de compiler avec le répertoire courant comme répertoire du LD_LIBRARY_PATH temporairement.
Les répertoires "standards" sont /usr/include pour les entêtes et /usr/lib pour les bibliothèques. Créer dans votre $HOME
les répertoires include et lib pour placer la bibliothèque de liste et jouer avec les bonnes options.
Profilage d'application
Pour pouvoir analyser les performances d’un programme, il faut compiler avec l’option
–pg
(à la compilation et à l’édition des liens). Le code final est ainsi truffé
d’instructions d’analyse et de
mesure du temps (on dit qu'il est instrumenté). Une fois le code compilé et exécuté, on fait une analyse avec la commande gprof
:
$ gcc –o fichier fichier.c –pg
$ ./fichier génère un fichier gmon.out
$ gprof fichier
Compiler le fichier donné, l’exécuter et l’analyser. Que peut-on conclure sur l’utilisation des macros et des fonctions en analysant les temps donnés par la colonne total ms/call ? Recommencer cette manipulation avec l’option de compilation suivante :
–O0
(moins o majuscule zéro) qui désactive les optimisations implicites du compilateur. Si l’exécution précédente ne vous permettait pas de retrouver ce que l’on a dit en cours, là cela devrait être le cas-O2
qui demande au compilateur d’optimiser (option communément utilisée)-O3
pour encore plus d’optimisations. Il existe des niveaux supérieurs d’optimisation. Attention toutefois, car cela se fait au détriment de la justesse des calculs.
On verra l'année prochaine que valgrind aussi peut faire du profilage.
Génération de documentation
Nous allons utiliser l’utilitaire doxygen pour générer la documentation d’un code source. Les commentaires qui sont extraits sont du type : /** commentaire */ ou /* ! commentaire */ avec certaines commandes (\brief,\param,…). La documentation est disponible sur http://www.doxygen.org. Doxygen utilise un fichier de configuration qu’il est possible de générer avec l’option –g et qu’il faut modifier pour obtenir ce que l’on veut.- Générer un fichier de configuration et jeter un coup d’œil sur son contenu
- Générer la documentation au format HTML de l'exercice sur le profilage.
- Générer la documentation au format pdf (si LaTeX est installé) ou rtf.
$ doxygen -g
$ doxygen
Conclusion
Nous avons essayé de vous faire découvrir des outils utiles au développement. Nous n’avons pas évoqué l’usage judicieux d’un EDI (Environnement de Développement Intégré) qui intègre une partie de ces outils avec une interface plus intuitive ou plus directe.
Il est aussi primordial de tester ses applications : de nombreux outils ou bibliothèques existent : tests et séries de tests où chaque fonction ou partie de code est testée séparément de l’ensemble. Chaque test est répétable à l’envie et lorsque le programme évolue, on teste la non régression, c’est-à-dire le non retour des bogues déjà corrigés.
Nous disposons aussi d’outils d’analyse du code : documentation, couverture de tests (savoir si on a testé tous les cas possibles …) mais cela est une autre histoire …