Si vous ne l’avez toujours pas fait …

Faites vous un fichier prêt à utiliser pour les TPs (ou la vie de tous les jours ;) ) : vide.c.

#include <assert.h>
#include <stdio.h> /* printf, fopen, perror, ... */
#include <stdlib.h> /* exit, malloc, atoi, ... */
#include <string.h> /* strlen, memcpy, ... */

int main(int argc, char *argv[])
{
    return EXIT_SUCCESS;
}

On pourra rajouter la fonction exit_if proposé ci-après

Gestion d’erreur

Les assertions

Une assertion arrête le programme si elle n’est pas vérifié. On peut de plus les activer ou les désactiver à la compilation (ceci permet de fournir des version de production). Comme on peut les désactiver, le bon fonctionnement de votre programme ne doit JAMAIS en dépendre.

Pour utiliser les assertion il faut inclure assert.h.

void foo(char *ptr, int min, int max)
{ // Exemple d'utilisation
    assert(ptr); // Le pointeur ne doit pas etre null
    assert(min <= max); // Min ne doit pas etre superieur a max
    // - Où devraient être ces commentaires ?

    // ...
}
  • Écrire une fonction moyenne. Que peut il se passer de mal ?
  • Discuter la pertinence de l’utilisation d’une assertion.
  • Comment désactive on une assertion ?

Votre fonction de d’arrêt

Vous pouvez aussi créer votre propre fonction d’“arrêt”. A la différence d’une assertion classique, on ne peut pas désactiver cette fonction à la compilation.

void exit_if(int condition, const char *prefix)
{
    if (condition) {
        perror(prefix);
        exit(EXIT_FAILURE);
    }
}

perror affiche un message correspondant à la dernière erreur rencontré (éventuellement préfixé par prefix) sur la sortie d’erreur. C’est globalement équivalent à fprintf(stderr, "%s: %s\n", prefix, strerror(errno));

void foo(char *fname)
{ // Exemple d'utilisation
    FILE *fd = fopen(fname, "r");
    exit_if(fd == NULL, "foo:fopen");
    // - essayez aussi avec exit_if(fd == NULL, fname);
    // - peut on remplacer `exit_if` par `assert` ?
    // - peut on cumuler `exit_if` et `assert` ?

    // ... do something with fd
}

Bien que cette fonction ne soit clairement pas efficace, elle facilite grandement la clarté de votre code. Il existe (hors du cadre du cours) des moyens pour la rendre plus efficace, vous pouvez donc vous en servir sans scrupules ni mauvaise conscience.

Exercice : write

Écriture et création de fichiers (fopen, fprintf/fputs, fclose)

Pour se chauffer, nous proposons d’écrire le hello world de la manipulation de fichier, i.e., un programme qui crée le fichier donné en argument (via argv) avec Bonjour monde. comme contenu.

  • Remplacez le texte par hello world, que constate t on ? Bonus une amélioration simple consiste a prendre en argument le texte à écrire.

  • Que se passe t il si le fichier existe déjà ? Comment y remédier (man fopen) ? Bonus rajouter l’option -f a votre programme pour qu’il effectue quand même l’action si le fichier existe). Profitez en pour rajouter l’option -a pour ajouter (append) à la fin du fichier.

Exercice : cat

Lecture/Écriture caractère par caractère (fgetc/fputc)

On va essayer de reprogrammer la commande cat (man 1 cat). Dans un premier temps, on ne considère qu’un fichier en entré (argv).

  • Ouvrir le fichier
  • Lire le premier caractère du fichier, et l’afficher sur la console
  • Lire caractère par caractère le fichier, et l’afficher sur la console
  • Gérer plus d’un fichier en entré.
  • Quel genre d’erreur peuvent survenir, et comment s’en protéger

  • La Lecture caractère par caractère peut sembler inefficace, que proposez vous ?
  • Ne dispose t on pas d’une base pour la copie de fichier ?

Les réponses à ces deux questions sont données par les exercices suivant.

  • Bonus: Comme le vrai cat, si le nom de fichier est -, on utilisera l’entrée standard

Exercice : head

Lecture ligne par ligne pour les fichiers textes (fgets/fputs)

Nous essayerons cette fois de reprogrammer la fonction head qui affiche les n (par défaut 5) premières lignes d’un fichier. Modifier le programme précédent pour rajouter le compteur de ligne rajoute beaucoup de bruit (une nouvelle boucle, donc une double boucle). Nous proposons donc d’utiliser la fonction fgets pour lire ligne par ligne.

  • Implémenter head

Si on regarde plus sérieusement le code écrit, on peut (on doit !) se demander ce qu’il ce passe si la ligne est plus longue que le buffer ? (Essayez en réduisant la taille du tampon). Sachant que fgets lit une ligne (terminée par un \n), pourquoi le \n fait parti du résultat ?

  • Bonus: Corrigez votre programme pour qu’il marche avec des lignes de tailles arbitraires.

  • Bonus: La commande tail est un peu l’opposé de la commande head, elle montre les n dernières lignes d’un fichier. Attention cette commande est beaucoup plus complexe à réaliser ! Remarque: le problème soulevé par la taille du tampon de fgets est hautement exacerbé par tail. Sans le faire, réfléchissez à pourquoi y a t il une telle augmentation de difficulté.

Exercice : cp (Copie de fichier)

Lecture bloc par bloc pour les fichiers binaires (fread/fwrite)

  • Écrire une fonction int copy(FILE *dst, FILE *src) qui copie un fichier par blocs (fread, fwrite). Cette fonction retournera 0 en cas de succès.

  • Écrire une fonction int cp(const char *dst, const char *src) qui copie un fichier. Cette fonction retournera 0 en cas de succès.

  • Écrire le programme copie (de fichiers) qui acceptera deux paramètres source et destination.

  • Essayez de faire varier la taille des blocs et comparer le temps de copie pour un gros fichier.

Exercice : Implémenter un vérificateur sfv (à la maison)

Le Simple File Verification (SFV) est un format de fichier créé pour permettre le stockage d’une somme de contrôle (le contrôle de redondance cyclique : CRC32 checksums) et ainsi permettre la vérification de l’intégrité d’un ou plusieurs fichiers. Wikipedia

  • Créez un dossier sfv et places vous y.
  • Récupérez crc32.c crc32.h
  • Implémentez la fonction manquante dans crc32
  • Écrivez le programme (sfv_generator) qui calcule le crc32 de chacun de ses paramètres (sauf le premier) et les sauvegardes dans un fichier (le premier paramètre). Le format du fichier est le suivant :
nom_de_fichier crc32

En utilisant les règles par défaut de make, un Makefile est très simple a écrire (votre fichier est censé s’appeler sfv_generator.c) :

sfv_generator: sfv_generator.o crc32.o

Vous pouvez bien entendu préfixer votre Makefile par vos options de compilations préférées (en première ligne du fichier).

CFLAGS=-std=c99 -Wall
  • Passons maintenant au programme sfv_verify qui vérifiera pour chacun des fichiers passés en paramètre s’ils sont correct (c’est-à-dire que leur crc est le même que celui spécifié dans le fichier).

  • Pour lire le fichier de données, vous pouvez vous servir de fscanf()

sfv_verify: sfv_verify.o crc32.o