Copy of https://perso.isima.fr/loic/unixc/tpc-devel.php
tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
Institut d'informatique ISIMA
  • twitter
  • linkedin
  • viadeo

[C] Développement

 Cette page commence à dater. Son contenu n'est peut-être plus à jour. Contactez-moi si c'est le cas!

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) :

Pour mesurer votre degré de connaissances, répondez aux questions suivantes :

déboGueur et valGrind

Elle s'invente pas mais elle se lit dans la sortie de valgrind en cas de problème --track-origins=yes

Cela se fait en ligne de commande... ou par les menus une fois que ddd est affiché

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

Le double clic par interface graphique ou la commande break no_numero

  • 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

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

  1. 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

  1. Lancez-le, de l'aide est affichée.
  2. 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 ?

  1. Lancez le programme en mode graphique avec le jeu d'essai par défaut.
  2. 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 :

En moyenne, vous ne mettez pas longtemps à trouver ce qui ne va pas.

  1. 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 :

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 :

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.
$ 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 …