• [^] # Re: Je pense que tu confonds les cas où c'est nécessaire

    Posté par . En réponse au journal Genèse d'un journal. Évalué à 8.

    Ah bah, oui, ton exemple contient déjà plusieurs erreurs !

    Le principe d ́un appel à une fonction est que, soit elle réussit, et dans ce cas toutes les réservations (allocation mémoire, descripteurs de fichiers, sockets réseau, verrous, etc … ) qu ́elle a à faire sont faites, soit aucune n ́est faite. Donc, en sortie de foo(), soit tout est fait, soit rien n ́est fait. Donc pas la peine de nettoyer foo() si ça échoue. Ditto pour bar(). Par contre, si bar() échoue, c ́est que foo() a réussi, donc il faut libérer foo().

    Si une fonction comme foo() doit effectuer plusieurs réservations, et que cette fonction retourne un code d ́erreur, comment savoir la raison de l ́échec ? Il fait beaucoup plus sens de dépiler les réservations dans la fonction elle-même en cas d ́erreur. La fonction de nettoyage undo_foo() est pertinente uniquement pour libérer après usage, pas en cas d ́erreur.

    Par exemple, cette séquence me semble plus correcte :

    # return !0 on success, 0 on error
    int foo_and_bar() {
     int ret;
     if( !foo() ) {
     errno = -EFOO;
     goto foo_err;
     }
     if( !(ret=bar()) ) {
     errno = -EBAR;
     goto bar_err;
     }
     return ret;
    bar_err:
     undo_foo();
    foo_err:
     return 0;
    }
    
    

    Cette séquence respecte le même principe décrit ci-dessus : soit toutes les réservations ( foo et bar) sont effectuées, soit aucune ne l ́est.

    Dans ce cas, le goto est vraiment très utile. C ́est un des rares cas où ça l ́est.

    C ́est d ́ailleurs, il me semble, le schéma utilisé dans le noyau Linux pour les traitements d ́erreurs, comme dans beaucoup d ́autres logiciels.

    Hop,
    Moi.