Table des matières


<< Retour à la page principale du cours


TD Outils - Préambule

Avant de commencer les TD Outils, vous devez télécharger le code disponible pour l'UE. Si vous connaissez déjà git ou sinon simplement en copiant/collant la ligne suivante dans un terminal :

git clone https://forge.univ-lyon1.fr/Alexandre.Meyer/L2_ConceptionDevApp.git

Sinon :




TD Outils - Partie 1 : éditeur de code, débogueur et diagramme

L'objectif de cette 1ère partie du TD est d'écrire un module (fichiers .h et .cpp) de gestion d'images. L'important est la manière de coder et les outils utilisés plus que la réalisation technique. Votre mini-programme comportera les fichiers Pixel.h/Pixel.cpp (le module Pixel), Image.h/Image.cpp (le module Image), les programmes principaux et un Makefile.
Ce TD outil est à réaliser sous Linux. ATTENTION, bien lire et respecter les consignes pour le rendu du module Image.


Le choix d'un éditeur de code

Un bon éditeur de code n'est pas un éditeur de texte. En effet, l'éditeur de code doit comporter les fonctionnalités suivantes :


Nous montrons principalement des copies d'écran de CodeBlocks mais donnons des liens vers d'autres outils qui sont tous aussi valables. L'objectif est que vous trouviez l'éditeur qui vous convient et surtout que vous soyez efficace avec. Choisissez parmi ces 6.


Les autres sont plus anecdotique pour du C++


Remarques : Apprenez et utilisez les raccourcis clavier de votre éditeur de code. Il peut y avoir des questions là-dessus lors du contrôle :-) Vous devez rapidement ne plus toucher à votre souris 8-o, le gain de temps est considérable.



Présentation rapide de CodeBlocks

CodeBlocks est plus qu'un éditeur de code. C'est un “environnement de développement intégré” (IDE en anglais). CodeBlocks va gérer votre programme comme un projet. Vous pourrez par exemple déboguer directement dans l'éditeur.
Pour créer, compiler et exécuter un projet sous CodeBlocks:

  1. cd LIFAP4_TD
  2. codeblocks
  3. Cliquez sur “Create a new project”, sélectionnez “Empty project”
  4. Project title=LIFAP4_TD et Folder=/home/…/ (le chemin vers le répertoire du projet)
  5. Une fois votre projet créé, cliquez avec le bouton droit sur “LIFAP4_TD” à gauche puis sur “Add file” et choisissez 'main.cpp' ou bien “New file” et donner lui le nom de 'main.cpp' si vous n'avez pas déjà de fichier principal.
  6. Explorez le menu “Build” (Alt-b) pour apprendre les raccourcis clavier
    • F9 pour “Compile & Run” (le plus important)
    • Ctrl-F9 pour “Compile”
    • Ctrl-F10 pour “Run”
    • Alt-F2 pour aller à une erreur (si Alt-F2 est pris par le système, sinon allez dans “Settings/editor/shortcut” pour configurer vos raccourcis clavier).
    • Ctrl-espace pour compléter un nom de fonction ou un nom de variable (essayez en tapant 'pr' 'Ctrl-espace' pour voir apparaître printf).
    • Shift-Ctrl-x , Shift-Ctrl-c pour dé/commenter du code
    • Ctrl-Tab , Ctrl-Shift-Tab pour naviguer dans les onglets

Voir également ce tutoriel si besoin.

Prenez le temps de bien choisir votre éditeur.


Le module Image

Une image est en quelque sorte un tableau à 2 dimensions de largeur dimx et de hauteur dimy, dont les éléments sont des pixels. En interne, nous allons allouer un tableau 1D de taille dimx*dimy. En externe (ie. le code utilisant le module Image) nous interpréterons l'image avec deux dimensions (x,y) en passant par les fonctions getPix (un accesseur) et setPix (un mutateur). Les définitions de classes vous sont données ci-dessous en langage algorithmique.

1. Créez les fichiers Pixel.h, Pixel.cpp, Image.h et Image.cpp, et traduisez et implémentez les classes en C++. N'oubliez pas de compiler AU FUR ET A MESURE. N'attendez pas d'avoir écrit toutes les fonctions membres pour compiler. Ne modifiez pas les noms des classes, membres, paramètres, etc.

Classe Pixel

 privé :

   r,g,b : entier(0..255)   // les composantes du pixel, unsigned char en C++

 public :

   // Constructeur par défaut de la classe: initialise le pixel à la couleur noire
   Constructeur Pixel ()

   // Constructeur de la classe: initialise r,g,b avec les paramètres
   Constructeur Pixel (nr, ng, nb : donnée entier(0..255) )

   // Accesseur : récupère la composante rouge du pixel
   Fonction getRouge () -> entier(0..255)

   // Accesseur : récupère la composante verte du pixel
   Fonction getVert () -> entier(0..255)

   // Accesseur : récupère la composante bleue du pixel
   Fonction getBleu () -> entier(0..255)

   // Mutateur : modifie la composante rouge du pixel
   Procédure setRouge (nr : donnée entier(0..255))

   // Mutateur : modifie la composante verte du pixel
   Procédure setVert (ng : donnée entier(0..255))

   // Mutateur : modifie la composante bleue du pixel
   Procédure setBleu (nb : donnée entier(0..255))

FinClasse
Classe Image

 privé :

   tab : tableau de Pixel     // le tableau 1D de pixel
   dimx, dimy : entier        // les dimensions de l'image

 public :

   // Constructeur par défaut de la classe: initialise dimx et dimy à 0
   // ce constructeur n'alloue pas de pixel
   Constructeur Image ()

   // Constructeur de la classe: initialise dimx et dimy (après vérification)
   // puis alloue le tableau de pixel dans le tas (image noire)
   Constructeur Image (dimensionX, dimensionY : donnée entier);

   // Destructeur de la classe: déallocation de la mémoire du tableau de pixels
   // et mise à jour des champs dimx et dimy à 0
   Destructeur Image ()

   // Accesseur : récupère le pixel original de coordonnées (x,y) en vérifiant leur validité
   // la formule pour passer d'un tab 2D à un tab 1D est tab[y*dimx+x]
   Fonction getPix (x,y : donnée entier) -> 'Pixel' (l'original, pas une copie)

   // Mutateur : modifie le pixel de coordonnées (x,y)
   Procédure setPix (x,y : donnée entier; couleur : donnée Pixel)

   // Dessine un rectangle plein de la couleur dans l'image (en utilisant setPix, indices en paramètre compris)
   Procédure dessinerRectangle (Xmin,Ymin,Xmax,Ymax : donnée entier; couleur : donnée Pixel)

   // Efface l'image en la remplissant de la couleur en paramètre
   // (en appelant dessinerRectangle avec le bon rectangle)
   Procédure effacer (couleur : donnée Pixel)

   // Effectue une série de tests vérifiant que le module fonctionne et
   // que les données membres de l'objet sont conformes
   Procédure testRegression ()

FinClasse

dessinerRectangle allume les pixels de l'image qui sont compris dans le rectangle défini par le coin en haut à gauche (Xmin,Ymin) jusqu'au point en bas à droite (Xmax,Ymax), comme ceci :


2. Ecrivez la procédure testRegression qui teste votre module Image dans tous les cas possibles d'utilisation. Soyez le plus exhaustif possible.

3. Créez un programme principal nommé mainTest.cpp qui contient le code suivant (simple appel à la procédure de test de regréssion).
mainTest.cpp (produisant l'exécutable bin/test):

#include "Image.h"
 
int main() {
   Image monImage;
   monImage.testRegression();
   return 0;
}

Voici quelques règles de bonne programmation à respecter :

Il est important de comprendre la différence entre la pile et le tas. De comprendre que l'allocation se fait sur un tableau 1D car c'est la seule solution qu'offre new, mais que de l'extérieur à la classe Image les codeurs veulent travailler avec des informations 2D. Les accesseurs font donc la conversion pour gérer les données internes.

Debug (GDB)

Trois fonctions d'entrée/sortie sauver (sauver une image dans un fichier), ouvrir (ouvrir une image depuis un fichier) et afficherConsole (afficher les valeurs des pixels sur la console) sont données dans le fichier TD_moduleImage/IOimage.cpp de l'archive fournie.

Vous devez tout d'abord les incorporer dans votre code (ie. les ajouter au module Image). Des bugs ont été placés intentionnelement dans ces trois fonctions :-? !!Vous devez les trouver et les corriger en utilisant GDB. Pour ceci vous pouvez tester votre programme avec une petite image (ex. de taille 3×2) et dérouler le code pas à pas.

  1. D'abord directement dans un terminal
  2. Puis en utilisant le débugueur intégré de CodeBlocks

Une fois ces trois fonctions corrigées, dans votre module Image, créez un deuxième exécutable nommé mainExemple.cpp qui contient le code suivant. Attention, vous devez lancer l'exécutable à partir du dossier racine avec la commande bin/exemple. Ceci est valable pour les trois exécutables du module Image.
mainExemple.cpp (produisant l'exécutable bin/exemple):

#include "Image.h"
 
int main() {
 
    Pixel rouge (120, 15, 10);
    Pixel vert (20, 202, 15);
    Pixel bleu (4, 58, 218);
 
    Image image1 (64,48);
    image1.effacer(bleu);
    image1.dessinerRectangle(5, 20, 30, 40, rouge);
    image1.setPix(51,4,vert);
    image1.setPix(20,30,vert);
    image1.sauver("./data/image1.ppm");
 
    Image image2;
    image2.ouvrir("./data/image1.ppm");
    image2.dessinerRectangle(29, 10, 48, 15, rouge);
    image2.dessinerRectangle(25, 24, 40, 45, vert);
    image2.sauver("./data/image2.ppm");
 
    return 0;
}

Diagramme des classes UML

UML est un langage graphique d'analyse et de conception de logiciel ou de système complexe. UML est standardisé et largement utilisé. UML vise plutôt une conception orientée objet que vous avez pu commencer à appréhender en LIFAP3 et LIFAP4. Voir le cours de LIFAP4, ainsi qu'une explication à propos des diagrammes de classes sur Wikipédia.

Vous aurez à plusieurs moments de ce cours à réaliser des diagrammes des classes de vos programmes. Pour l'édition de ce diagramme des classes UML, nous vous proposons plusieurs outils dans la section "Doc, outils et tuto". Vous avez par exemple Umbrello ou Dia installé sur les ordinateurs de l'Université. Par exemple avec Dia :

Remarque : sur les machines du Nautibus si Umbrello ou Dia ne sont pas installé, utilisez alors un outil en ligne comme ceux proposez dans la section UML de "Doc, outils et tuto".


A Faire. Réalisez le diagramme des classes UML du programme comportant les classes Image et Pixel : ce travail n'aura pas besoin d'être rendu mais il constitue un bon entrainement pour la réalisation de votre future diagramme des classes UML de projet.



Documentation de code (doxygen)

Pour générer la documentation de votre code au format HTML :

   # créez le fichier doxygen en tapant:
   doxygen -g doc/image.doxy
   # personnalisez le fichier doxygen (au minimum INPUT et OUTPUT_DIRECTORY) en tapant :
   gedit doc/image.doxy
   # generez la documentation en tapant:
   doxygen doc/image.doxy
   # ouvrez la page principale générée avec Firefox (ou autre browser) en tapant:
   firefox doc/html/index.html

Remarques :


A faire : réaliser la documentation doxygen du code de Pixel et de Image.



TD Outils - Partie 2 : Gestion de mémoire/Optimisation de code

Question : En utilisant valgrind qui va nous donner des informations sur l'exécution du programme, vous devez résoudre les problèmes de lenteur et de mémoire : fuite de mémoire (memory leak), accès invalide dû à des débordements, etc.

Mémoire perdu (memory leak) et accès mémoire non autorisé

Par défaut, valgrind permet de vérifier si les allocations mémoires se passent bien. En particulier, de savoir si vous ne débordez pas d'un tableau (ex: int t[10]; t[12]=14;), de savoir si tous les new correspondent à des delete dans le programme, etc.

$> valgrind --tool=memcheck --leak-check=full ./prog

==20689== Invalid write of size 4
==20689==    at 0x804859D: main (main.cpp:23)
==20689==  Address 0x4286FA0 is 0 bytes after a block of size 24 alloc'd
==20689==    at 0x4022A55: operator new[](unsigned int)
==20689==    by 0x8048541: main (main.cpp:18)

Ceci indique qu'il y a des problèmes d'écriture mémoire non autorisée dans main.cpp à la ligne 23, ce qui est sûrement dû à un problème de taille d'allocation ou d'indice de tableau. Pour cela valgrind vous indique la ligne d'allocation mémoire de la variable mise en cause (ici le tableau f1) à la ligne 18. Faites la correction nécessaire pour résoudre ce problème, ainsi que celles nécessaires pour résoudre d'autres problèmes d'allocation et d'accès.

De plus, dans la rubrique “LEAK SUMMARY” à la fin vous trouverez que 24 octets sont perdus car ce programme ne fait pas le bon nombre de delete. Faites le bon nombre de libérations mémoire et vérifiez que votre programme est propre en relançant valgrind. Faites la même chose pour votre module Image afin de vérifier si vous n'avez pas de fuite mémoire.

Optimisation de code

Pour optimiser votre code vous devez savoir où votre programme passe le plus de temps :

$> valgrind --tool=callgrind ./prog
$> callgrind_annotate callgrind.out.20092

La 1ère ligne a produit un fichier de statistique : callgrind.out.20092 (nom automatiquement généré, change à chaque exécution). La 2ème ligne permet de visualiser les statistiques du fichier :

--------------------------------------------------------------------------------
         Ir  file:function
--------------------------------------------------------------------------------
2,028  Calcul.cpp:intAdd(int, int) [/home/.../prog]
  570  Calcul.cpp:intMul(int, int) [/home/.../prog]

Ce programme passe 2028 fois dans intAdd et 570 fois dans intMul. C'est donc la fonction intAdd qu'il faut optimiser en priorité. Regardez et changez le code de intAdd pour avoir un programme plus efficace. Relancer valgrind pour voir le gain de performance obtenue. Faire éventuellement d'autres modifications du code pour gagner encore plus. Pour vous rendre compte du gain apporté, vous pouvez refaire l'expérience avec une valeur MAX plus grande.

Pour aller plus loin avec valgrind



TD Outils - Partie 3 : Bibliothèques

TXT et SDL2

Vous trouverez le code d'un embryon du jeu Pacman dans le répertoire du même nom dans l'archive fournie. Le projet Pacman peut être compilé sous Linux ou sous Windows à l'aide du Makefile ou du projet CodeBlocks qui utilise le Makefile. Si vous compilez sous Windows sur votre portable les bibliothèques Windows sont incluses dans le répertoire extern. Sous Linux à l'université toutes les bibliothèques nécessaires sont installées par défaut sur les machines. Sous Linux sur votre portable vous devez installer SDL2, SDL2_image , SDL2_ttf et SDL2_mixer comme ceci:

$> sudo apt-get install libsdl2-dev libsdl2-image-dev libsdl2-ttf-dev libsdl2-mixer-dev

L'objectif du TD est de vous familiariser avec SDL2, et de l'intégrer à votre module Image. Prenez donc du temps pour explorer le code, et plus particulièrement les modules Jeu, sdlJeu et txtJeu.

SDL2 dans le module Image

Dans votre module Image, créez un troisième programme principal nommé mainAffichage.cpp qui contient impérativement le code suivant, sans le modifier pour le script de notation.
mainAffichage.cpp (produisant l'exécutable bin/affichage):

#include "Image.h"
 
int main (int argc, char** argv) {
 
   Image image (11,11);
 
   Pixel gris (226, 226, 226);
   Pixel vert (1, 130, 0);
   Pixel noir (0, 0, 0);
   Pixel beigeC (210, 188, 154);
   Pixel beigeF (168, 105, 8);
   Pixel marron (102, 65, 8);
 
   image.effacer(gris);
   image.dessinerRectangle(3,0,7,10,vert);
   image.dessinerRectangle(0,1,10,1,vert);
   image.setPix(2,2,vert);
   image.setPix(8,2,vert);
   image.setPix(3,2,gris);
   image.setPix(7,2,gris);
   image.setPix(4,2,noir);
   image.setPix(6,2,noir);
   image.dessinerRectangle(1,5,9,6,vert);
   image.setPix(5,10,gris);
   image.dessinerRectangle(9,7,9,10,marron);
   image.dessinerRectangle(2,5,8,6,beigeC);
   image.dessinerRectangle(3,7,7,9,beigeC);
   image.dessinerRectangle(4,5,6,6,beigeF);
   image.setPix(5,7,beigeF);
 
   image.afficher();
 
   return 0;
}

La procédure afficher() du module Image est à ajouter dans votre classe Image et est la suivante:

 // Affiche l'image dans une fenêtre SDL2
 Procédure afficher ()

Vous devez vous inspirez du code du Pacman pour implémenter cette procédure (module sdlJeu). Vous aurez probablement à créer des fonctions membres intermédiaires qui seront privées à la classe (ex. afficherInit, afficherBoucle et afficherDetruit). L'image sera placée au centre d'une fenêtre SDL2 de taille 200×200 pixels de fond gris clair et vous devrez permettre de zoomer/dézoomer sur votre image grâce aux touches T et G et quitter la procédure afficher() grâce à la touche ESCAPE.

Valgrind et les bibliothèques : SDL2

Si vous lancez valgrind sur un exécutable utilisant une bibliothèque comportant des fuites mémoire, vous obtiendrez ces fuites même si votre code libère bien toute la mémoire allouée que vous allouez. Elles apparaissent pour la plupart en still reachable, ce qui correspond à des zones de mémoire sur lesquelles on dispose encore de pointeurs et que l'on aurait pu désallouer si on l'avait voulu. Ces fuites (qui n'en sont pas vraiment…) relèvent du fonctionnement interne des bibliothèques et sont indépendantes de votre volonté. Elles ne risquent pas de faire planter votre programme mais gênent la recherche de vrais bugs.
Heureusement, pour ne voir que vos fuites mémoire, il est possible d'indiquer à valgrind d'ignorer ces erreurs, en utilisant le fichier de suppression valgrind_lif7.supp fourni dans l'archive, et en spécifiant les options adéquates lors de l'exécution de valgrind :

$> valgrind --leak-check=full --num-callers=50 --suppressions=./valgrind_lif7.supp --show-reachable=yes -v NOM_DE_L'EXECUTABLE

Les fuites mémoire propres à SDL2 sont toujours dénombrées mais apparaissent désormais en suppressed. Documentez vous sur valgrind pour comprendre le sens des options.



TD Outils - Partie 4 : Gestionnaire de version (Git)

Créer un projet
L'université a mise en place une forge (lien) que vous utiliserez pour gérer les versions de votre TP projet. Pour créer un nouveau dépôt forge pour votre projet, un des membres du groupe doit suivre la procédure ci-dessous (il est nécessaire d'avoir un compte actif à l'université).

Ensuite vous devez ajouter les autres membres du groupe en tant que contributeur:

Pour le TP projet (mais pas pour le module Image), n'oubliez que vous aurez à ajouter votre encadrant de TP (qui sera indiqué sur Tomuss) en tant que “Reporter” avant la fin du semestre.

Pour créer votre version locale:

git clone https://forge.univ-lyon1.fr/NUMERO_ETU/NOM_PROJET.git

Tous les étudiants membres du projet doivent éxecuter cette commande sur leur propre compte pour pouvoir travailler sur le projet (après qu'ils aient été ajoutés en tant que membres). Cette opération n'est à faire qu'une seule fois pour chaque étudiant.

Travailler avec git
Une fois le répertoire de travail mis en place, vous pouvez effectuer des modifications (ajout de fichiers, suppression, modification etc.).

Sous Windows vous pouvez remplacer les commandes du terminal par les outils suivants : TortoiseGit et Git for Windows.