IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C Discussion :

Les différentes façons de gérer les erreurs en C


Sujet :

C

  1. #21
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    Citation Envoyé par Jacti Voir le message
    1) goto est une instruction inutile. La preuve avec Java qui ne supporte pas le goto (c'est un mot réservé qui ne fait aucune action). Il est réservé afin de ne pas pouvoir l'utiliser comme identificateur de variable ou nom de fonction. Je n'ai jamais programmé un goto dans quelque langage que ce soit durant mes 40 ans de carrière (je ne parle pas de l'assembleur évidemment).
    goto ne devient inutile qu'après avoir remplacé ses utilisations légitimes possibles par d'autres fonctionnalités plus ciblées.

    Par exemple, la boucle while :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
        while(condition) {
            foo();
        }
        bar();
    permet de remplacer le code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    begin_loop:
        if(!condition)
            goto end_loop;
        foo();
        goto begin_loop;
    end_loop:
        bar();
    Mais les fonctionnalités offertes par le langage C ne suffisent pas pour qu'il soit sage de bannir entièrement goto. En effet, en langage C, pour gérer la libération des ressources, il est pertinent d'utiliser goto. SofEvans a donné un exemple avec une fermeture de fichier et des libérations de mémoire :
    Citation Envoyé par SofEvans Voir le message
    Pour finir, je prêche ma petite paroisse : n'ayez pas peur d'utiliser GOTO pour la gestion d'erreur.
    Je remet un exemple :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    bool MyFunction(void)
    {
        char *logPathfile = NULL;
        FILE *logFile = NULL;
        char *msg = NULL;
        bool returnValue = false;
     
        logPathfile = malloc(...);
        if (!logPathfile) {
            // Message erreur  (possible d'utiliser perror (3) / strerror (3))
            goto END_FUNCTION;
        }
     
        sprintf(logPathfile, "%s", "/home/user/exemple.txt");
     
        logFile = fopen(logPathfile, "w");
        if (!logFile) {
            // Message erreur (possible d'utiliser perror (3) / strerror (3))
            goto END_FUNCTION;
        }
     
        msg = malloc(...);
        if (!msg) {
            // Message erreur (possible d'utiliser perror (3) / strerror (3))
            goto END_FUNCTION;
        }
     
     
     
        /* .. le reste du code, avec possiblement d autres tests */
     
     
     
     
        // Fin de la fonction
        returnValue = true;
     
        /* GOTO */END_FUNCTION:
        free(logPathfile);
        if (logFile) {
            fclose(logFile);
        }
        free(msg);
     
        return returnValue;
    }
    Dans cet exemple, en Java, on n'aurait pas utilisé goto. La fermeture du fichier aurait été gérée par un try-with-resources (ou un try/finally dans les anciennes versions) et la libération de la mémoire aurait été directement gérée par le ramasse-miettes.

    En C++ et en Rust, on aurait utilisé le RAII.

    La plupart des langages ont des fonctionnalités dédiées à la libération des ressources qui remplacent les usages de goto, mais pas le langage C.

  2. #22
    Membre habitué
    Profil pro
    Inscrit en
    Octobre 2006
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France, Val d'Oise (Île de France)

    Informations forums :
    Inscription : Octobre 2006
    Messages : 82
    Points : 178
    Points
    178
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    Malheureusement ça ne dit rien de ta carrière mais surtout ça ne dit pas
    1. la quantité de C que tu as fait dans ta carrière
    2. la nature des codes en C que tu as fait dans ta carrière, notamment si tu as dû gérer les cas d'échecs multiples comme l'a exposé SoftEvans dans ce post
    3. si c'est le cas, comment tu as pû les gérer? En démultipliant les instructions de nettoyage en cas d'échec???
    Ma carrière (non exhautive) :
    Spécialiste de la sémantique des langage de programmation (sémantique dénotationnelle, opérationnelle, axiomatique, etc.) : 10 an de conception, développement et maintenance de compilateurs (PL/1, Pascal, C) et plus particulièrement :
    - conception et développement d'un générateur de code commun aux langages Fortran, Pascal, C, PL/1 (50K lignes de code)
    - Chef de projet du développement d'un compilateur de langage C depuis la page blanche
    - Conception et développement d'un optimiseur local et global sur un langage intermédiaire en amont du générateur de code commun

    Je ne peux pas dire combien de lignes de C mais ça doit bien avoisiner les 40K lignes de code devant gérer toutes situations dont celles que tu décris. Un avantage dans l'environnement dans lequel j'étais : la notion de "virtual memory file" qui permet de lire et d'écrire en mémoire, la lecture et l'écriture physique étant faites par le système et donc la gestion des erreurs aussi.

    Formateur (principalement en intra entreprise) :
    - Cours sur le langage C++ et cours avancés montrant les techniques de métaprogrammation à l'aide de la généricité (patterns traits, policy, SFINAE : Substitution Failure Is Not An Error, CRTP : Curiously Recurring Template Pattern)
    - Cours de langage C (de base et avancé)
    - Cours de Lisp, ADA, Java
    - Cours qualité ISO 9000
    - Audit de code C++ et Java sur des applications de plus de 40K lignes de code à l'aide du logiciel Logiscope pour des application de l'Armée de Terre

    Consultant habilité confidentiel défense et confidentiel OTAN pour l'Armée.


    Citation Envoyé par Sve@r Voir le message
    Un peu facile de décrier une instruction dans un langage donné sous prétexte que tel autre langage plus récent ne l'implémente pas. Sauf que ce que tu ne prends pas en considération, c'est que Java implémente à la place une gestion des exceptions.
    Effectivement je voulais parler du mécanisme des exceptions en Java qui est beaucoup plus propre que le goto mais j'ai oublié.


    Citation Envoyé par Sve@r Voir le message
    Tu ne trouves pas que ça ressemble à un gros "goto" bien déguisé ce "finally" qui s'exécutera avant de quitter le bloc ???
    Je suis d'accord sauf que le code n'est pas "noyé" dans le reste et que ça facilite grandement la maintenance.

    On pourrait aussi parler du langage Eiffel de Bertand Meyer et du concept de programmation par contrat avec les pré et post conditions qui peuvent générer du code sur options.

    Citation Envoyé par Pyramidev Voir le message

    La plupart des langages ont des fonctionnalités dédiée à la libération des ressources qui remplacent les usages de goto, mais pas le langage C.
    Entièrement d'accord avec toi sur tout ce que tu as dit.

  3. #23
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 689
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 689
    Points : 30 983
    Points
    30 983
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par Jacti Voir le message
    Ma carrière (non exhautive) :
    Spécialiste de la sémantique...

    Formateur...

    Consultant habilité confidentiel défense et confidentiel OTAN pour l'Armée.
    Mais tout ça ne dit pas comment tu gères les ouvertures de ressources multiples et surtout la libération des n-1 ressources ouvertes si l'ouverture de la ressource "n" échoue (sans goto s'entend).
    Et pour l'habilitation CD, la première des trois, tout le monde l'a. C'est l'habilitation "vulgum pecus". Suffit de ne pas avoir fait de prison et de ne pas être en contact avec des ressortissants étrangers de pays considérés comme "spécieux". Elle est tellement peanut qu'elle a été supprimée en août 2021 (je veux parler de la CD France).

    Citation Envoyé par Jacti Voir le message
    Effectivement je voulais parler du mécanisme des exceptions en Java qui est beaucoup plus propre que le goto mais j'ai oublié.
    Ben oui... mais ça n'existe pas en C. Donc on fait avec et on remplace par "goto" qui, dans ce contexte, est ce qu'on peut faire de mieux et de plus propre. Ou alors on fait sans et c'est encore plus touffu, donc moins bien et moins propre. Ou alors on ne fait pas de C (après-tout, il y a effectivement d'autres langages bien sympas donc si on n'y est pas obligé il n'y a aucune raison de se faire un claquage des neurones).

    Citation Envoyé par Jacti Voir le message
    On pourrait aussi parler du langage Eiffel de Bertand Meyer et du concept de programmation par contrat avec les pré et post conditions qui peuvent générer du code sur options.
    Oui, je ne le connais pas donc je veux bien te croire si tu dis qu'il est excellent, mais encore une fois, "aucun langage de programmation n'est parfait. Il n'existe même pas un langage meilleur que d'autres ; il n'y a que des langages en adéquation ou peu conseillés pour des buts particuliers. (Herbert Mayer, homonyme amusant de Bertrand Meyer)". Reste le fait inéluctable qu'ici on parle de ce qui se fait en C et de comment le faire au mieux avec ce qu'il offre. Venir dire "telle instruction est inutile" (parce qu'elle n'existe pas dans tel langage plus récent, et peut-être aussi parce qu'inconsciemment on se souvient des ravages qu'elle a produit dans tel autre langage plus ancien) ce n'est pas faire montre d'ouverture d'esprit.
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  4. #24
    Membre émérite Avatar de SofEvans
    Homme Profil pro
    Développeur C
    Inscrit en
    Mars 2009
    Messages
    1 076
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 076
    Points : 2 328
    Points
    2 328
    Par défaut
    * Se connecte à developpez
    * Voit que le sujet sur la gestion d'erreur C est passé de 16 à 25 réponse
    * Se dit "Nom de dieu, qu'est-ce qu'il s'est passé ? Y-a-t’il eu un débat endiablé très intéressant apportant plein d'information que je ne connais pas ??"

    goto est une instruction inutile. La preuve avec Java qui ne supporte pas le goto

    Ah p*tain ...
    Bon ben je vais me chercher une bière ... ça sera plus intéressant.

    Sérieusement Jacti ?

    Tu savais aussi que while c'est une instruction inutile ?
    Bahoui, on peut remplacer tout les while par des for.
    Et en plus, le mot clef while n'existe pas en Go, alors franchement si ça c'est pas de la preuve bétonné !

    Pis tant qu'a faire, tu savais aussi que "++" c'est une instruction totalement inutile ?
    Ébahoui, on peut remplacer tout les "i++" par des "i += 1".
    D'ailleurs, Ruby n'as pas d'opérateur de pré/post incrémentation ! Abahçaalors, j'aurais dû faire avocat, j'aurais perdu aucune affaire.


    Bon, histoire de ne pas faire que du Troll, je rajoute une petite brique ainsi que quelques exemple.




    Déjà, je considère que les méthodes de gestion d'erreur présenté dans le premier post sont "commune" et "traditionnelle".
    Je veux dire par la, c'est que dans 95% des cas, on tomberas sur une gestion d'erreur de ce type.

    Ensuite, je suis plutôt partisans d'utiliser toujours la gestion la plus "simple" et la plus "logique".
    Je ne suis pas pour faire toujours la même gestion : ça n'as aucun sens de faire une gestion à base de goto si on a aucune variable a nettoyer et aucune action de rollback à faire.
    Si en plus la fonction n'est pas susceptible d'évoluer significativement, franchement, c'est ridicule de faire du goto.

    Par exemple, quand je fait une structure, je code plusieurs fonctions permettant de gérer/manipuler cette dernière.
    Si la structure possède des propriétés à nettoyer, j'ai une fonction "NomStruct_Constructor" et une fonction "NomStruct_Destructor".
    Le principe est simple : la première fonction appelé pour toute variable de type dia_s c'est le constructeur, la dernière, c'est le destructeur.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /* dia : dynamic int array */
     
    typedef struct dia {
        int *array; 
        size_t size; /* Array size */
        size_t nbElement; /* Number of element in array */
    } dia_s;
     
    void Dia_Constructor(dia_s *self)
    {
        self->array = NULL;
        self->size = 0;
        self->nbElement = 0;
    }
     
    void Dia_Destructor(dia_s *self)
    {
        free(self->array);
    }

    Si ensuite je veux pouvoir faire une allocation dynamique d'une variable dia_s, je met à disposition les fonctions "NomStruct_New" et une fonction "NomStruct_Delete".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    dia_s *Dia_New(void)
    {
        dia_s *new = NULL;
     
        new = malloc(sizeof(*new));
        if (!new) {
             // Message erreur (possible d'utiliser perror (3) / strerror (3))
        } else {
            Dia_Constructor(&new);
        }
     
        return new;
    }
     
    void Dia_Delete(dia_s *self)
    {
        if (self) {
            Dia_Destructor(self);
            free(self);
        }
    }
    Le point que je veux mettre en avant, c'est que je n'ai pas utiliser goto dans Dia_New.
    La fonction n'est absolument pas censé évoluer, c'est complétement ridicule d'utiliser goto dans cette situation (1 test avec 1 variable) et l'utilisation de goto complexifierais le code pour rien.

    Par contre, si je devais faire une fonction prenant un chemin vers un fichier et parsant le fichier pour peupler le tableau dynamique, c'est plus probable que j'utiliserais goto (erreur ouverture fichier, erreur lecture fichier, erreur de format, erreur de données [int], données en overflow [int] ...).

    Tiens, petit truc que j'aime bien faire avec mes structures :


    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /* dia : dynamic int array */
     
    typedef struct dia {
        int *array; 
        size_t size; /* Array size */
        size_t nbElement; /* Number of element in array */
    } dia_s;
     
    #define DIA_CONSTRUCTOR  { \
        .array = NULL,         \
        .size = 0,             \
        .nbElement = 0         \
        }
     
    void Dia_Constructor(dia_s *self)
    {
        dia_s clean = DIA_CONSTRUCTOR;
     
        *self = clean;
    }

    Ca me permet de pouvoir déclarer et initialiser une variable en même temps

  5. #25
    Expert éminent sénior
    Avatar de Escapetiger
    Homme Profil pro
    Administrateur système Unix - Linux
    Inscrit en
    Juillet 2012
    Messages
    1 476
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 61
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Administrateur système Unix - Linux

    Informations forums :
    Inscription : Juillet 2012
    Messages : 1 476
    Points : 11 051
    Points
    11 051
    Par défaut
    Citation Envoyé par Jacti Voir le message
    Les goto ne sont JAMAIS nécessaires : la preuve en Java
    Initié dans les années 80 au langage COBOL puis le C et enfin, pratiquant régulier des shells Korn, Bash et autres, j'ai vu effectivement des horreurs avec goto mais également des programmes bien pensés avec cette instruction à la mauvaise réputation.

    Voici ce qu'en dit un certain Linus Torvalds en 2003 :

    From: Linus Torvalds
    Subject: Re: any chance of 2.6.0-test*?
    Date: Sun, 12 Jan 2003 12:22:26 -0800 (PST)

    On Sun, 12 Jan 2003, [redacted by request 12/3/2020] wrote:
    >
    > However, I have always been taught, and have always believed that
    > "goto"s are inherently evil. They are the creators of spaghetti code

    No, you've been brainwashed by CS people who thought that Niklaus Wirth
    actually knew what he was talking about. He didn't. He doesn't have a
    frigging clue.

    > (you start reading through the code to understand it (months or years
    > after its written), and suddenly you jump to somewhere totally
    > unrelated, and then jump somewhere else backwards, and it all gets ugly
    > quickly). This makes later debugging of code total hell.

    Any if-statement is a goto. As are all structured loops.

    Ans sometimes structure is good. When it's good, you should use it.

    And sometimes structure is _bad_, and gets into the way, and using a
    "goto" is just much clearer.

    For example, it is quite common to have conditionals THAT DO NOT NEST.

    In which case you have two possibilities

    - use goto, and be happy, since it doesn't enforce nesting

    This makes the code _more_ readable, since the code just does what
    the algorithm says it should do.

    - duplicate the code, and rewrite it in a nesting form so that you can
    use the structured jumps.

    This often makes the code much LESS readable, harder to maintain,
    and bigger.

    The Pascal language is a prime example of the latter problem. Because it
    doesn't have a "break" statement, loops in (traditional) Pascal end up
    often looking like total shit, because you have to add totally arbitrary
    logic to say "I'm done now".

    Linus
    L'intégralité des interventions dans ce fil historique :

    Using Goto in Linux Kernel Code | Koblents.com

    Cordialement,
    « Developpez.com est un groupe international de bénévoles dont la motivation est l'entraide au sens large » (incl. forums developpez.net)
    Club des professionnels en informatique

  6. #26
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 689
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 689
    Points : 30 983
    Points
    30 983
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par Escapetiger Voir le message
    Voici ce qu'en dit un certain Linus Torvalds en 2003
    Etant totalement une bille en anglais, j'ai profité de la magie du net pour me le traduire.
    J'en fais donc profiter ceux qui, comme moi, ne parlent pas plus anglais que ce que je ne le parle pas
    Le dimanche 12 janvier 2003, [expurgé par demande le 12/3/2020] a écrit :
    >
    > Cependant, on m'a toujours enseigné, et j'ai toujours cru que
    > les "goto "s étaient intrinsèquement mauvais. Ils sont les créateurs du code spaghetti.

    Non, tu as subi un lavage de cerveau de la part de gens de la CS qui pensaient que Niklaus Wirth
    savait vraiment de quoi il parlait. Ce n'est pas le cas. Il n'a pas la moindre idée.

    > (vous commencez à lire le code pour le comprendre (des mois ou des années après qu'il ait été écrit), et soudainement vous sautez à un endroit totalement
    > sans rapport, et puis on saute ailleurs à l'envers, et tout devient laid
    > rapidement). Cela rend le débogage ultérieur du code totalement infernal.

    Tout énoncé if est un goto. De même que toutes les boucles structurées.

    Et parfois la structure est bonne. Quand elle est bonne, il faut l'utiliser.

    Et parfois, la structure est _mauvaise_, elle est gênante et l'utilisation d'un "goto" est beaucoup plus claire.

    Par exemple, il est assez courant d'avoir des conditionnelles qui ne s'emboîtent pas.

    Dans ce cas, vous avez deux possibilités

    - utiliser goto, et être heureux, puisqu'il n'impose pas l'imbrication.

    Cela rend le code _plus_ lisible, puisque le code fait simplement ce que
    ce que l'algorithme dit qu'il doit faire.

    - dupliquer le code, et le réécrire dans une forme d'imbrication de façon à pouvoir
    utiliser les sauts structurés.

    Cela rend souvent le code beaucoup MOINS lisible, plus difficile à maintenir,
    et plus gros.

    Le langage Pascal est un excellent exemple de ce dernier problème. Parce qu'il n'a pas d'instruction "break", les boucles en Pascal (traditionnel) finissent par
    ressembler souvent à de la merde, parce que vous devez ajouter une logique totalement arbitraire pour dire "j'ai fini maintenant".

    Linus
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  7. #27
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 471
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Haute Garonne (Midi Pyrénées)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : Avril 2016
    Messages : 1 471
    Points : 6 110
    Points
    6 110
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    de la part de gens de la CS
    CS = Computer Science

    Citation Envoyé par Sve@r Voir le message
    Tout énoncé if
    Toute instruction if

  8. #28
    Membre habitué
    Profil pro
    Inscrit en
    Octobre 2006
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France, Val d'Oise (Île de France)

    Informations forums :
    Inscription : Octobre 2006
    Messages : 82
    Points : 178
    Points
    178
    Par défaut
    Citation Envoyé par SofEvans Voir le message

    Sérieusement Jacti ?
    Je reconnais que le "coup" de Java était provocateur. Il fallait le prendre comme tel.
    Sinon je suis pratiquement d'accord avec ce que tu dis

  9. #29
    Expert confirmé

    Inscrit en
    Août 2006
    Messages
    3 942
    Détails du profil
    Informations forums :
    Inscription : Août 2006
    Messages : 3 942
    Points : 5 654
    Points
    5 654
    Par défaut
    Bonjour,

    La meilleure façon de gérer les erreurs est de ne pas en faire.
    Si les cons volaient, il ferait nuit à midi.

  10. #30
    Membre émérite Avatar de SofEvans
    Homme Profil pro
    Développeur C
    Inscrit en
    Mars 2009
    Messages
    1 076
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 076
    Points : 2 328
    Points
    2 328
    Par défaut
    Citation Envoyé par Jacti Voir le message
    Je reconnais que le "coup" de Java était provocateur. Il fallait le prendre comme tel.
    Bah mission accomplie alors, je pense que plusieurs personnes (dont moi) ont plutôt réagi assez fortement.
    Ceci étant dit, je ne comprends pas le sens ni le but de la remarque initiale du coup si la comparaison avec Java n'était que provocation.

    Tu dis que "goto est une instruction inutile" et puis juste après "Les goto ne sont JAMAIS nécessaires", mais comment ferais-tu pour gérer le nettoyage des allocations mémoire (direct via malloc/calloc et indirect via par exemple fopen, socket, etc etc) dans une même fonction qui peut planter à plusieurs endroits ?

    J'ai déjà vu plusieurs gestions, et aucune ne m'as jamais paru plus clair que ce que j'ai présenté avec goto (d'où pourquoi je l'utilise en fait).

    Bien sûr, il y a toujours quelques petites astuces pour se passer de goto, comme par exemple remplacer les strings dynamiques par leurs équivalents statiques maximum (on évite donc un malloc) ou par exemple si dans un grosse fonction, on fait un fopen qui ne sert qu'a détecter qu'on peut effectivement ouvrir le fichier.

    Alors au lieu de faire

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    bool Function(void)
    {
        FILE *test = NULL;
        /* plein de variables qui n'ont pas besoin d'être nettoyées */
        bool returnValue = false;
     
     
        /* plein de code */
     
     
        test = fopen("path", "r");
        if (!test) {
            /* message erreur (perror / strerror) */
            goto END_FUNCTION;
        }
     
     
        /* plein de code, mais qui n'utilise pas "test" */
     
     
        returnValue = true;
     
        /* GOTO */END_FUNCTION:
        if (test) {
            fclose(test);
        }
     
        return returnValue;
    }
    on peut ramener tout à un seul endroit

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    bool Function(void)
    {
        /* plein de variables qui n'ont pas besoin d'être nettoyées */
     
     
        /* plein de code */
     
     
        FILE *test = fopen("path", "r");
        if (!test) {
            /* message erreur (perror / strerror) */
            return false;
        }
        fclose(test);
     
     
        /* plein de code qui n'utilise pas "test"  */
     
     
        return true;
    }
    mais c'est un cas particulier qui part du principe qu'aucune variables n'aient besoin d'être nettoyer et que les actions de rollback (s'il y en a) soient uniquement nécessaire à l'endroit du test en défaut.

    Bref, comment gères-tu le nettoyage de multiples variables à des multiples endroits en C ?
    Tu empiles les appels de fonctions ? Tu empiles les if ? Tu mets chaque morceaux de code dans un test d'erreur ?

  11. #31
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 689
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Oise (Picardie)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Aéronautique - Marine - Espace - Armement

    Informations forums :
    Inscription : Février 2006
    Messages : 12 689
    Points : 30 983
    Points
    30 983
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par SofEvans Voir le message
    Ceci étant dit, je ne comprends pas le sens ni le but de la remarque initiale du coup si la comparaison avec Java n'était que provocation.
    Arrête de l'enfoncer, tu n'as pas compris qu'il a tenté une ellipse? Comme ces gosses qui finissent par dire "mais je plaisantais" quand ils sont acculés dans leurs incohérences. Fais donc semblant d'y croire...

    Citation Envoyé par SofEvans Voir le message
    comme par exemple remplacer les strings dynamiques par leurs équivalents statiques maximum (on évite donc un malloc)
    Un malloc ça se gère aussi sans goto (si malloc échoué on quitte la fonction et basta). Ce sont les mallocs en 2D via boucle interne qui sont over chiants (comme je l'ai montré). Si je dois allouer un tableau 2D ben je resterai en 1D et je ferai de la conversion coordonnées 1D <=> coordonnées 2D.
    Ou alors je rajouterai un second tableau de pointeurs pointant vers chaque début de ligne de la zone ; et c'est ce tableau que j'utiliserai comme tableau 2D
    Exemple
    Code c : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    #include <stdio.h>
    #include <stdlib.h>
     
    // Allocation d'un tableau 2D
    // L'allocation se fera au travers d'une zone 1D et d'un tableau de pointeurs,
    // Chaque pointeur pointant vers chaque début de "ligne" de la zone
    int** matrice2D(size_t lig, size_t col) {
    	int* zone;		// Zone correspondant au tableau 2D
    	int** final;		// Tableau final
     
    	// Allocation de la zone mémoire représentant tout le tableau
    	zone=malloc(lig*col*sizeof(*zone));
    	if (zone == NULL) return NULL;
     
    	// Allocation des pointeurs référençant les lignes de la zone
    	final=malloc(lig * sizeof(*final));
    	if (final == NULL) {
    		free(zone);
    		return NULL;
    	}
     
    	// Remplissage des pointeurs avec chaque début de ligne de la zone
    	for (size_t i=0; i < lig; i++) final[i]=zone + i * col;
     
    	// Le tableau final est créé
    	return final;
    }
     
    // Nettoyage tableau
    void free2D(int** tab) {
    	// Il faut d'abord nettoyer la zone mémoire (qui commence à la première ligne)
    	free(tab[0]);
     
    	// Maintenant on peut nettoyer le tableau de pointeurs
    	free(tab);
    }
     
    // Test
    int main() {
    	int** m=matrice2D(4, 5);
     
    	// Remplissage
    	for (size_t lig=0; lig < 4; lig++) {
    		for (size_t col=0; col < 5; col++) {
    			m[lig][col]=(lig+1) * (col+1);
    		}
    	}
     
    	// Affichage
    	for (size_t lig=0; lig < 4; lig++) {
    		for (size_t col=0; col < 5; col++) {
    			printf("m[%lu][%lu]=%d ", lig, col, m[lig][col]);
    		}
    		fputc('\n', stdout);
    	}
     
    	// Nettoyage
    	free2D(m);
    }
    Dans le travail de création de la zone, la seule redondance est ce free(zone) qui sert à nettoyer la première allocation si la seconde échoue. On n'a pas vraiment besoin d'un goto pour ça...

    Citation Envoyé par SofEvans Voir le message
    ou par exemple si dans un grosse fonction, on fait un fopen qui ne sert qu'a détecter qu'on peut effectivement ouvrir le fichier.
    A mon avis, ce genre de fonction verra ses limites devant le bug TOCTOU...

    Citation Envoyé par SofEvans Voir le message
    Bref, comment gères-tu le nettoyage de multiples variables à des multiples endroits en C ?
    Ben oui, j'avais aussi posé la question...
    Mon Tutoriel sur la programmation «Python»
    Mon Tutoriel sur la programmation «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site
    Et on poste ses codes entre balises [code] et [/code]

  12. #32
    Membre émérite Avatar de SofEvans
    Homme Profil pro
    Développeur C
    Inscrit en
    Mars 2009
    Messages
    1 076
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C

    Informations forums :
    Inscription : Mars 2009
    Messages : 1 076
    Points : 2 328
    Points
    2 328
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    Arrête de l'enfoncer, tu n'as pas compris qu'il a tenté une ellipse? Comme ces gosses qui finissent par dire "mais je plaisantais" quand ils sont acculés dans leurs incohérences. Fais donc semblant d'y croire...
    Je vais attendre un peu avant de me faire cette opinion : j'ai quand même envie de voir si je ne vais pas apprendre quelque chose de nouveau.
    Des fois on passe à côté de petites astuces pendant longtemps et j'aime m'améliorer quand je le peux.
    Si Jacti à pu faire une carrière aussi longue sur des sujets aussi complexe qu'il l'as décrit sans utiliser goto, alors c'est soit qu'il a réussi à ne jamais être dans une situation où je préconise le goto (multiple libération à de multiple endroit), soit autre chose ... (utilisation de longjmp ? Empilage de fonction, de if ?).
    Je me dis que cela peut être intéressant d'avoir son retour d'expérience.


    Citation Envoyé par Sve@r Voir le message
    Un malloc ça se gère aussi sans goto

    [...]

    Dans le travail de création de la zone, la seule redondance est ce free(zone) qui sert à nettoyer la première allocation si la seconde échoue. On n'a pas vraiment besoin d'un goto pour ça...
    Oui, on est d'accord ^^
    J'ai d'ailleurs aussi mis un exemple via la fonction Dia_New dans cette réponse : goto, c'est pas systématique, ça dépend du contexte.

    Du coup il semblerait que je me sois mal exprimé :
    Quand je parle de remplacer un tableau dynamique par un tableau statique avec valeur max pour éviter un malloc et ne pas utiliser goto, je faisais plus référence à la situation où tu as plusieurs plusieurs variable à allouer et que ces variables sont utiles du début à la fin de la fonction.
    Si tu as, par exemple, 3 strings à allouer dans la fonction, alors une gestion des erreurs via goto semble pertinente.
    Mais si tu peux remplacer tes strings par leurs équivalent statiques avec taille maximal (genre au pif : "char *filepath;" par "char filepath[PATH_MAX];"), alors tu n'as plus besoin d'allocations, et donc gérer les erreurs via goto n'est plus indiqué du tout.

    Désolé pour l’ambiguïté, mais pour ma défense, je me suis parfaitement bien compris

    Citation Envoyé par Sve@r Voir le message
    A mon avis, ce genre de fonction verra ses limites devant le bug TOCTOU...
    Ca dépend ... du contexte

    J'ai déjà eu le cas suivant :

    Un programme doit faire un traitement "lourd" (~3 ou 4 heures) et écrire le résultat dans un fichier.
    Le traitement consiste à lire plusieurs tables/enregistrement dans une BDD que l'on doit lock complétement pour l'occasion (BDD type ISAM, donc pas possible d'avoir le I de ACID .... youpi ....).
    Le chemin du fichier est spécifié via argument du programme ou fichier de configuration du programme.

    On s'est aperçu que certaines configuration demandait d'écrire le résultat à un emplacement où le programme n'avait pas forcément les droits de création du fichier (ou pire, carrément pas les droits d'écritures).

    Donc on était dans la situation un peu débile où pour savoir si on allait pouvoir écrire le résultat du traitement, il fallait faire le traitement et tenter de l'écrire.
    Traitement qui prends ~3 à 4 heures, donc c'était toujours un peu la surprise (nous n'avions pas la main sur les valeurs du fichier de configuration).

    Du coup, on a ajouté le test de "le fichier est déjà présent OU on a le droit de créer le fichier" ainsi que "il est possible d'écrire dans le fichier" au tout début du programme uniquement dans le but de ne pas perdre de temps à faire le traitement (et surtout, du coup, de lock la BDD) pour rien.
    Bien sûr, ça n'empêche pas de vérifier que le fopen et les fprintf s’exécutent bien lors de l'écriture du résultat du traitement exactement pour la raison que tu cites : le TOCTOU.

  13. #33
    Membre régulier Avatar de vivid
    Profil pro
    Inscrit en
    Février 2006
    Messages
    166
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2006
    Messages : 166
    Points : 119
    Points
    119
    Par défaut
    A vouloir tout simplifier on ce retrouve forcément avec une perte de technicité.., exemple ; la daube JAVA

  14. #34
    Membre habitué
    Profil pro
    Inscrit en
    Octobre 2006
    Messages
    82
    Détails du profil
    Informations personnelles :
    Localisation : France, Val d'Oise (Île de France)

    Informations forums :
    Inscription : Octobre 2006
    Messages : 82
    Points : 178
    Points
    178
    Par défaut
    Citation Envoyé par SofEvans Voir le message
    Si Jacti à pu faire une carrière aussi longue sur des sujets aussi complexe qu'il l'as décrit sans utiliser goto, alors c'est soit qu'il a réussi à ne jamais être dans une situation où je préconise le goto (multiple libération à de multiple endroit), soit autre chose ... (utilisation de longjmp ? Empilage de fonction, de if ?).
    Je me dis que cela peut être intéressant d'avoir son retour d'expérience.
    Désolé d'avoir tant tardé à répondre mais je n'ai pas vu de mails d'alerte qui montraient que la discussion se poursuivait.
    Dans la plupart des cas cités j'utilisais un longjump bien que je n'aime pas cette issue. J'utilisais aussi l'empilage des appels de fonctions quand il est nécessaire de "sortir" proprement à tous les étages pour une gestion propre de la pile d'exécution. Cette solution est facilitée par un style de programmation "fonctionnel" où les fonctions ne font jamais plus de 10 lignes (si, si c'est possible et j'ai audité plusieurs programmes de plus de 400 KLOC dans lesquels c'était le cas chez les militaires puisque c'était une de leur règle de programmation). En dernier recours l'imbrication de "if" mais c'est loin d'être l'idéal.
    L'évitement de l'instruction goto n'est pas un gadget car, en particulier, quand on fait un goto en avant cela "casse" l'optimisation puisqu'à l'exécution, ne sachant pas d'où l'on vient (il peut y avoir plusieurs "goto" en avant à la même étiquette dans des contextes différents) on ne peut pas "garder le contexte" et on ne peut donc pas utiliser les valeurs contenues dans certains registres alors qu'on aurait pu peut-être en réutiliser sans refaire des "load" à tout va.
    Il faut une certaine connaissance et habitude des mécanismes de compilation pour pouvoir tenir ce genre de raisonnement.
    Dans les cas extrêmes, que je n'ai rencontrés qu'en codant dans le système d'exploitation, j'utilisais les VMF (Virtual memory files) pour allouer mes ressources et c'est le gestionnaire de mémoire virtuelle qui se chargeait de "nettoyer" comme il faut, mais j'avais la chance que le système d'exploitation supporte les VMF. Je ne programmais jamais moi-même des "entrées/sorties" au sens de read et write, tout passait par des macros systèmes du gestionnaire de mémoire virtuelle.

Discussions similaires

  1. Réponses: 5
    Dernier message: 06/11/2020, 08h45
  2. Différentes façons de gérer les erreurs
    Par Maniz dans le forum VB.NET
    Réponses: 2
    Dernier message: 27/10/2011, 11h46
  3. Proc. Stock. : Gérer les erreurs
    Par audreyc dans le forum SQL Procédural
    Réponses: 4
    Dernier message: 29/03/2006, 14h51
  4. gérer les erreurs intebase avec delphi
    Par mondanikoffi dans le forum Bases de données
    Réponses: 1
    Dernier message: 14/12/2004, 15h46

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo