Gérer 3 fichiers mais.... il faut libérer les ressources !

L’auteur de ce sujet a trouvé une solution à son problème.
Auteur du sujet

Bonjour,

Désolé de poster autant de sujets aujourd’hui mais la remise de mon devoir en C approche à grands pas.

On me demande de gérer 3 fichiers et les potentielles erreurs associées.
J’ai pensé à cette solution pour le fichier main.c :

#include <stdio.h>

int main(int argc, char* argv[])
{
    if (argc < 4) return 1;

    /**
     *   Ouvrir les trois fichiers au début permet de s'assurer
     *   qu'ils sont tous disponibles en lecture ou écriture.
     **/

    FILE* clients = fopen(argv[1], "r");
    if (clients == NULL) return 1;
    
    FILE* data    = fopen(argv[2], "r");
    if (data == NULL) {
        fclose(clients); // Sinon fuite de mémoire !
        return 1;
    }
    
    FILE* output  = fopen(argv[3], "w");
    if (output == NULL) {
        fclose(clients);
        fclose(data);
        return 1;
    }
    
    /**
     * Ici se trouve le long code principal qui se charge de traiter les données
     * des fichiers clients et data, puis d'insérer le résultat dans output.
     **/

    fclose(clients);
    fclose(data);
    fclose(output);
    return 0;
}

Comme on le remarque, les lignes 23 et 24 doivent libérer les ressources si le dernier fichier n’a pas su être ouvert.
Pour des opérations aussi simple, le fichier de code est déjà très long !

J’aimerais résumer les lignes 12 à 26 en quelques lignes (genre 5 lignes de code) pour alléger le code de main(). D’ailleurs, je pense que cette fonction n’a pas à gérer les erreurs d’ouverture / fermeture de fichiers.

Bref, existe-t-il une bonne pratique pour gérer les fichiers qui me permettrait de simplifier mon code et le rendre plus propre (en évitant les fuites de mémoire) ?
Sinon, comment auriez-vous fait ?

+0 -0

Lu’!

Vu que tu fermes le programme littéralement juste après, sur aucun OS moderne ça ne poserait de problème puisque l’OS se chargera de récupérer les handlers de fichier, et basta.

En C, il n’est pas rare que dans une situation comme ça, on se contente de renvoyer vers une section cleanup: avec un goto et qu’on libère tout quoi qu’il arrive. Pour fclose, c’est un peu différent, parce que ça a été designé un peu n’importe comment, et donc un fclose(NULL) échoue au lieu de ne rien faire … Du coup, si on fait un tel renvoi, il faut contrôler que le pointeur n’est pas NULL avant de libérer.

FILE* f1 = fopen(...);
if(!f1) goto cleanup ;

FILE* f2 = fopen(...);
if(!f2) goto cleanup ;

...

cleanup:
if(f1) fclose(f1);
if(f2) fclose(f2);
...

if(!f1 || !f2 || ...) return 1;
else return 0;

Édité par Ksass`Peuk

First : Always RTFM - "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein [Tutoriel Frama-C WP]

+1 -0
Auteur du sujet

Merci, j’apprends le mot-clef goto via ton exemple. :)

Mais je n’ai pas très bien compris la condition ligne 14 : quel cas pourrait valider cette condition sachant que cleanup fait déjà le boulot ?

+0 -0

L’opération fclose (comme l’opération free) ne modifie pas le pointeur, de toute façon elle ne peut pas puisque le passage se fait par valeur en C.

Édité par Ksass`Peuk

First : Always RTFM - "Tout devrait être rendu aussi simple que possible, mais pas plus." A.Einstein [Tutoriel Frama-C WP]

+2 -0

Cette réponse a aidé l’auteur du sujet

Salut,

On me demande de gérer 3 fichiers et les potentielles erreurs associées.

[…]

Bref, existe-t-il une bonne pratique pour gérer les fichiers qui me permettrait de simplifier mon code et le rendre plus propre (en évitant les fuites de mémoire) ?
Sinon, comment auriez-vous fait ?

info-matique

Je te suggère d’utiliser une boucle, un tableau et une fonction de libération dédiée.

#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

#define ARRAY_SIZE(a) (sizeof (a) / sizeof (a)[0])


static void
close_all_files(FILE *fp, size_t size)
{
    for (size_t i = 0; i < size; ++i)
        if (fp[i] != NULL)
            if (fclose(fp[i]) == EOF)
                perror("fclose");
}


int
main(int argc, char **argv)
{
    FILE *fp[3] = { NULL };

    for (int i = 1; i < argc; ++i) {
        fp[i - 1] = fopen(argv[i], "r");

        if (fp[i - 1] == NULL) {
            perror("fopen");
            goto failure;
        }
    }

    close_all_files(fp, ARRAY_SIZE(fp));
    return EXIT_SUCCESS;
failure:
    close_all_files(fp, ARRAY_SIZE(fp));
    return EXIT_FAILURE;
}

Édit : ajout d’un appel à perror() en cas d’échec de fopen().

Édité par Taurre

#JeSuisArius

+1 -0
Auteur du sujet

@Taurre solution très élégante, je m’y pencherais pour essayer de la comprendre. :)

En attendant, j’ai trouvé une solution à mon problème, qu’en pensez-vous de procéder ainsi ?

Je découpe en plusieurs fichiers pour alléger le main et je fais quelque chose comme ça :

main.c

#include <stdio.h>
#include <stdlib.h>
#include "files.c"

int main(int argc, char* argv[])
{
    if (argc < 4) return 1;

    FILE* files[3] = {NULL, NULL, NULL}; // Evitons les pointeurs fous
    open_files(argv, files);

    FILE* clients = files[0];
    FILE* data    = files[1];
    FILE* output  = files[2];
    
    /**
     * Ici se trouve le long code principal qui se charge de traiter les données
     * des fichiers clients et data, puis d'insérer le résultat dans output.
     **/

    fclose(clients);
    fclose(data);
    fclose(output);
    return 0;
}

files.c

FILE** open_files(char* argv[], FILE* result[])
{
    result[0] = fopen(argv[1], "r");
    if (result[0] == NULL) exit(1);

    result[1] = fopen(argv[2], "r");
    if (result[1] == NULL) {
        fclose(result[0]);
        exit(1);
    }

    result[2] = fopen(argv[3], "w");
    if (result[2] == NULL) {
        fclose(result[0]);
        fclose(result[1]);
        exit(1);
    }

    return result;
}

J’ai l’impression que ma solution tient la route, si je présente ça au devoir ?

+0 -0

Cette réponse a aidé l’auteur du sujet

Si tu utilises un tableau, de mon point de vue, il est beaucoup plus efficace de recourir à une boucle (c’est bien l’avantage des tableaux, d’ailleurs : on peut les parcourir aisément). Cela étant, si tu veux t’en passer, tu peux te simplifier un peu la vie dans open_files() comme suit.

FILE** open_files(char* argv[], FILE* result[])
{
    result[0] = fopen(argv[1], "r");
    if (result[0] == NULL)
        goto fopen_1;

    result[1] = fopen(argv[2], "r");
    if (result[1] == NULL)
        goto fopen_2;

    result[2] = fopen(argv[3], "w");
    if (result[2] == NULL)
        goto fopen_3;

    return result;
fopen_3:
    fclose(result[1]);
fopen_2:
    fclose(result[0]);
fopen_1:
    exit(1);
}

Édité par Taurre

#JeSuisArius

+0 -0
Auteur du sujet

D’accord merci et dernières petites questions :

(1) Puis-je remplacer :

FILE* files[3] = {NULL, NULL, NULL}; // Evitons les pointeurs fous

…par :

FILE* files[3] = {NULL}; // Evitons les pointeurs fous

C’est bien deux lignes de codes strictement équivalentes ?

(2) Je n’ai pas très bien compris la ligne n°13, que fait-elle ?

if (fclose(fp[i] == EOF))

(3) Quelle différence entre int main(int argc, char* argv[]) et int main(int argc, char** argv) ?

+0 -0

Cette réponse a aidé l’auteur du sujet

(1) Puis-je remplacer :

FILE* files[3] = {NULL, NULL, NULL}; // Evitons les pointeurs fous

…par :

FILE* files[3] = {NULL}; // Evitons les pointeurs fous

C’est bien deux lignes de codes strictement équivalentes ?

info-matique

Le résultat est le même, en effet.

If an object that has automatic storage duration is not initialized explicitly, its value is indeterminate. If an object that has static or thread storage duration is not initialized explicitly, then:

[…]

  • if it has pointer type, it is initialized to a null pointer;

[…]

If there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate, or fewer characters in a string literal used to initialize an array of known size than there are elements in the array, the remainder of the aggregate shall be initialized implicitly the same as objects that have static storage duration.

ISO/IEC 9899:2017, doc. N2176, 6.7.9 Initialization, al. 10 et 21, p. 101 et 102.

(2) Je n’ai pas très bien compris la ligne n°13, que fait-elle ?

if (fclose(fp[i] == EOF))

Elle vérifie que la fonction fclose() ne rencontre pas une erreur (parce que oui, elle peut). Toutefois, dans les faits, cela est surtout utile si tu effectues des écritures (afin d’être certain que tout est bien écrit sur le disque).

(3) Quelle différence entre int main(int argc, char* argv[]) et int main(int argc, char** argv) ?

info-matique

La syntaxe. ;)

Édité par Taurre

#JeSuisArius

+0 -0

Attention :

if (fclose(fp[i] == EOF))

Devrait être :

if (fclose(fp[i]) == EOF)

ache.one                 🦹         👾                                🦊

+1 -0
Vous devez être connecté pour pouvoir poster un message.
Connexion

Pas encore inscrit ?

Créez un compte en une minute pour profiter pleinement de toutes les fonctionnalités de Zeste de Savoir. Ici, tout est gratuit et sans publicité.
Créer un compte