Copy of https://perso.isima.fr/loic/unixc/tpc-04.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] TP 4 : Hall of Fame

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

Date de première publication : 2015/10/06

On va s'intéresser à la programmation d'un Hall of Fame : on mémorisera les meilleures scores sur des jeux avec l'identiifant avec lequel on a joué...

Les informations seront stockées dans des structures particulières quue nous allons utiliser de deux manières différentes :

La base du TP se trouve là :

git clone https://gitlab.com/kiux/C4.git

Une saisie sécurisée

Jusqu'à maintenant, on vous a laissé utiliser la fonction scanf() pour faire de la saisie. Cette fonction est dangereuse (il n'y a aucun test de dépassement de mémoire tampon), il faut donc plutôt privilégier la fonction fgets(). Celle-ci a deux avantages :

Elle a, en revanche un inconvénient, la chaîne intègre le plus souvent le caractère de fin de saisie '\n', ce qui ne nous intéresse pas forcément même s'il n'est plus nécessaire de l'enlever de la mémoire tampon associée au fichier.

Voici un usage tout simple :

char saisie[255];
fgets(saisie, 255, stdin);

Si la chaîne de caractères saisie représente un nombre, il est possible de l'obtenir par conversion avec des fonctions comme atoi() "ascii to integer", atof(), strtol() ou bien encore sscanf(). Les fonctions sscanf() ne sont absolument pas déconseillées

On vous conseille de vous faire une petite fonction équivalente du fgets() mais qui enlève le '\n' de fin de saisie s'il est présent.

Le fichier de test associé au TP montre un petit exemple d'utilisation de fgets()

TEST(fgets) {
   char exemple [] = "scanf, c'est pas bien\n";
   LOG(exemple);
   char chaine1[25];
   char chaine2[10];

   FILE * file = fmemopen(exemple, sizeof(exemple)+1, "r");
   // REQUIRE ( NULL != file);

   fgets(chaine1, 25, file);
   LOG(chaine1);
   fclose(file);
   REQUIRE( strlen(exemple) == strlen(chaine1) );

   file = fmemopen(exemple, sizeof(exemple)+1, "r");
   // REQUIRE ( NULL != file);

   fgets(chaine2, 10, file);
   LOG(chaine2);
   
   REQUIRE( strlen(exemple) > strlen(chaine2) );

   fgets(chaine2, 10, file);
   LOG(chaine2);
   fclose(file);
}

LOG() est une macro de type fonction qui affiche le message donné en paramètre sur la sortie d'erreur standard

#define LOG(A) do {             \
    fprintf(stderr, "%s\n", A); \
} while(0) 

Structures et tableaux

Première rencontre avec un structure

La suite du code se met dans une fonction :

Ce qu'il faut faire pour le TP...

TEST(Sizeof) {
  int taille1 = sizeof(struct donnee);
  int taille2 = sizeof(int)+100*sizeof(char); // :-)

  CHECK (taille1 == taille2);
}
TEST(AffichageB) {
   // declaration et initialisation d'une variable de type struc
   struct donnee essai;
   
   strcpy(essai.nom, "pokemon GO");
   strcpy(essai.alias, "loic");
   essai.score = 498;

   afficherDonnee(stdout, essai); 

   // creation du flux de texte => buffer
   char buffer[1024];
   FILE * file = fmemopen(buffer, 1024, "w");
   REQUIRE ( NULL != file);

   afficherDonnee(file, essai);
   
   fclose(file);

   CHECK( 0==strcmp(buffer, "pokemon GO : loic avec 498\n") );
}

Il y a deux tests différents (B et C) pour l'affichage. Cela permet de vérifier que les types struct donnee et donnee_t sont bien utilisables dans le programme.

Si le test est correct, vous pouvez sauvegarder ("commit") votre travail sur le dépôt

TEST(Saisie) {
   struct donnee essai;
   char buffer[1024];
   strcpy(buffer, "rien\ndutout\n10");
   FILE * file = fmemopen(buffer, 1024, "r");
   // REQUIRE ( NULL != file);

   saisirDonnee(file, &essai);
   fclose(file);

   afficherDonnee(stdout, essai);

   CHECK(  0 == strcmp(essai.nom, "rien") );
   CHECK(  0 == strcmp(essai.alias, "dutout") );
   CHECK( 10 == essai.score );   
}

Là encore, la fonction saisirDonnee()n'ouvre pas le flux (fichier ou autre), elle ne fait que l'utiliser.

Le test unitaire proposé n'impose rien sur le type de retour de la fonction saisirDonnee(). Il pourrait être pertinent que la fonction renvoie 0 si un problème a eu lieu pendant la saisie (fgets()revoie NULL en cas de problème) et une valeur non nulle sinon.

Pour cette question, il est recommandé d'avoir écrit la petite fonction qui enlève le saut de ligne '\n'en dernier caractère !

2048
loic
64236
Minecraft
kiux
12304883

Si le fichier contient plus de TAILLE_MAX éléments, les éléments supplémentaires devront être écartés.

TEST(lectureFichier) {
   donnee_t tableau[TAILLE_MAX];
   int taille = 0;
   
   // test d'un fichier non existant
   taille = tableauFromFilename("inconnu.txt", tableau);      
   CHECK( 0 == taille );

   // test du fichier exemple
   taille = tableauFromFilename("jeu1.txt", tableau);

   REQUIRE( 2 == taille );
   CHECK  ( 0 == strcmp(tableau[0].nom, "2048"));
   CHECK  ( 0 == strcmp(tableau[0].alias, "loic"));
   CHECK  ( 64236 == tableau[0].score );
   CHECK  ( 0 == strcmp(tableau[1].nom, "Minecraft"));
   CHECK  ( 0 == strcmp(tableau[1].alias, "kiux"));
   CHECK  ( 12304883 == tableau[1].score );
}

Aller plus loin

Listes chaînées

Nous allons mettre en œuvre la compilation séparée pour cet exercice. Vous aurez potentiellement deux exécutables :

On réutilise les fichiers hall_of_fame.h et hall_of_fame.c

Pour la suite de ce TP, on laisse tomber temporairement la bibliothèque de tests. Il faut d'abord manipuler les différents éléments avant de tester :-)

Il est également hyper important de ne pas perdre le travail précédent :

Création de la liste chaînée

Pour écrire une liste chaînée, vous devez choisir si vous la définissez avec une tête réelle ou une tête fictive.

struct donnee creerListe(void);
afficherListe(struct donnee   tetefictive);
afficherListe(struct donnee * tetereelle);

valgrind est un véritable couteau suisse : il est impératif d'utiliser ce programme pour vérifier le rendu mémoire, mais vous devez aussi l'utiliser pour éliminer les erreurs de contexte. Si vous avez un segfault, c'est une bonne habitute de le lancer pour voir ce que le programme vous dit !

Utilisation de la liste chaînée.

Vous allez écrire un nouveau programme (main2.cqui utilise les fonctions définies dans les questions précédentes et vous allez enrichir les fonctionnalités disponibles

Le programme principal est constitué principalement d'un menu à base de switch

L'exécution du programme sera vérifiée avec valgrind.

Aller plus loin