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
    Membre éprouvé
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    juillet 2020
    Messages
    316
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet NTIC

    Informations forums :
    Inscription : juillet 2020
    Messages : 316
    Points : 1 206
    Points
    1 206
    Par défaut
    Citation Envoyé par Jacti Voir le message
    Les goto ne sont JAMAIS nécessaires : la preuve en Java

    [...]
    Ne «JAMAIS» être nécessaire ne signifie pas pour autant «ne pas être UTILES» : la preuve en Java … les break et continue, nommés ou non, ne sont que du sucre syntaxique pour des «goto contraints».

    Il faut arrêter la simplification militante nécessaire au débutant du «goto = mal». Un débutant doit d'abord apprendre à programmer avant d'apprendre à coder, d'où la tonne de «ne fais pas ça ou sinon tu iras en enfer» ; et avec l'expérience, on s'aperçoit vite qu'en C ou dans d'autres langages un goto peut rendre un code plus lisible. Mais bon, on sait ça depuis les années 60 et l'émergence du paradigme de la programmation structurée, rien de neuf sous le soleil.

  2. #22
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    février 2006
    Messages
    10 577
    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 : 10 577
    Points : 25 920
    Points
    25 920
    Billets dans le blog
    1
    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
    Super la preuve !!! On peut aussi inverser la logique du discours: java est un langage inutile, la preuve il ne connait pas le "goto"...

    Citation Envoyé par Jacti Voir le message
    Je n'ai jamais programmé un goto dans quelque langage que ce soit durant mes 40 ans de carrière
    Mouais. "moi je ne l'ai pas fait donc ce n'est pas à faire". Tu te prends pour le mètre étalon?
    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. J'ai connu un type il ne faisait que des programmes de chiffrement/déchiffrement. Il n'a pas eu à coder une tonne de gestion de ressources dans sa carrière lui.
    3. si c'est le cas, comment tu as pû les gérer? En démultipliant les instructions de nettoyage en cas d'échec???


    Imaginons un cas d'allocation 2D avec gestion des allocations échouées
    Sans goto
    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
    char **tab;
    tab=malloc(n * sizeof(*tab));
    if (tab == NULL) return NULL;
     
    for (size_t i=0; i < n; i++) {
    	tab|i]=malloc(m * sizeof(**tab);
    	if (tab[i] == NULL) {
    		for (size_t j=0; j < i; j++) free(tab[i]);
    		free(tab);
    		return NULL;
    	}
    }
    ... (travail)...
    for (size_t i=0; i < n; j++) free(tab[i]);
    free(tab);
    return resultat;
    Double travail pour la libération. La première en cas d'erreur, la seconde quand le travail est fini.

    Maintenant, avec ce que tu nommes pompeusement "instruction inutile" du haut de tes certitudes
    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
    char **tab;
    tab=calloc(n, sizeof(*tab));
    if (tab == NULL) return NULL;
     
    errno=0;
    for (size_t i=0; i < n; i++) {
    	tab|i]=malloc(m * sizeof(**tab);
    	if (tab[i] == NULL) goto end;
    }
    ... (travail)...
     
    end:
    for (size_t i=0; i < n; i++) free(tab[i]);
    free(tab);
    return resultat if errno == 0 else NULL;
    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.

    Voyons ce que ça donne dans ce contexte (tu m'excuseras je ne connais pas Java donc je mets une pseudo-syntaxe inspirée de Python et que j'espère compréhensible)
    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
    char **tab;
    tab=calloc(n, sizeof(*tab));
    if (tab == NULL) return NULL;
     
    try:
    	for (size_t i=0; i < n; i++)
    		tab|i]=malloc(m * sizeof(**tab);
    except allocationError:
    	return NULL;
    else:
    	... (travail)...
    	return resultat;
    finally:
    	for (size_t i=0; i < n; i++) free(tab[i]);
    	free(tab);
    Tu ne trouves pas que ça ressemble à un gros "goto" bien déguisé ce "finally" qui s'exécutera avant de quitter le bloc ???

    Aucun langage n'est bon ou mauvais (Herbert Mayer). Ca s'applique aussi à ses instructions. Une instruction n'est jamais inutile, elle est juste adéquate ou pas dans une situation donnée. Et quoiqu'on puisse avoir comme "à priori" à son sujet, on ne peut pas la qualifier d'"inutile parce que tel autre langage ne l'implémente pas" (affirmation du conséquent).

    Citation Envoyé par Jacti Voir le message
    Citation Envoyé par Sve@r Voir le message
    Là je nuancerai un peu. Les globales c'est comme les goto: à n'utiliser que si nécessaire
    Les goto ne sont JAMAIS nécessaires
    Ok, j'ai pas employé le bon adjectif (enfin à propos de "goto" seulement parce que faire du signal() sans globales...). Donc ok, les goto c'est à n'utiliser que si ça se justifie.

    Citation Envoyé par Jacti Voir le message
    la preuve en Java
    Ah oui, j'oubliais...
    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

  3. #23
    Expert confirmé
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    1 315
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 1 315
    Points : 5 476
    Points
    5 476
    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.

  4. #24
    Membre habitué
    Profil pro
    Inscrit en
    octobre 2006
    Messages
    74
    Détails du profil
    Informations personnelles :
    Localisation : France, Val d'Oise (Île de France)

    Informations forums :
    Inscription : octobre 2006
    Messages : 74
    Points : 149
    Points
    149
    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.

  5. #25
    Membre habitué
    Profil pro
    Inscrit en
    octobre 2006
    Messages
    74
    Détails du profil
    Informations personnelles :
    Localisation : France, Val d'Oise (Île de France)

    Informations forums :
    Inscription : octobre 2006
    Messages : 74
    Points : 149
    Points
    149
    Par défaut
    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.

  6. #26
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    février 2006
    Messages
    10 577
    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 : 10 577
    Points : 25 920
    Points
    25 920
    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 «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  7. #27
    Membre chevronné Avatar de SofEvans
    Homme Profil pro
    Développeur C
    Inscrit en
    mars 2009
    Messages
    1 047
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C

    Informations forums :
    Inscription : mars 2009
    Messages : 1 047
    Points : 2 199
    Points
    2 199
    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

  8. #28
    Expert éminent
    Avatar de Escapetiger
    Homme Profil pro
    Administrateur système Unix - Linux
    Inscrit en
    juillet 2012
    Messages
    1 292
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Hauts de Seine (Île de France)

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

    Informations forums :
    Inscription : juillet 2012
    Messages : 1 292
    Points : 9 681
    Points
    9 681
    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

  9. #29
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    février 2006
    Messages
    10 577
    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 : 10 577
    Points : 25 920
    Points
    25 920
    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 «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  10. #30
    Expert confirmé
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    1 315
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 1 315
    Points : 5 476
    Points
    5 476
    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

  11. #31
    Membre habitué
    Profil pro
    Inscrit en
    octobre 2006
    Messages
    74
    Détails du profil
    Informations personnelles :
    Localisation : France, Val d'Oise (Île de France)

    Informations forums :
    Inscription : octobre 2006
    Messages : 74
    Points : 149
    Points
    149
    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

  12. #32
    Expert confirmé

    Inscrit en
    août 2006
    Messages
    3 890
    Détails du profil
    Informations forums :
    Inscription : août 2006
    Messages : 3 890
    Points : 5 531
    Points
    5 531
    Par défaut
    Bonjour,

    La meilleure façon de gérer les erreurs est de ne pas en faire.
    Les bons crus font les bonnes cuites.
    [Pierre Dac]

  13. #33
    Membre chevronné Avatar de SofEvans
    Homme Profil pro
    Développeur C
    Inscrit en
    mars 2009
    Messages
    1 047
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C

    Informations forums :
    Inscription : mars 2009
    Messages : 1 047
    Points : 2 199
    Points
    2 199
    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 ?

  14. #34
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    février 2006
    Messages
    10 577
    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 : 10 577
    Points : 25 920
    Points
    25 920
    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 «Shell»
    Sinon il y en a pleins d'autres. N'oubliez pas non plus les différentes faq disponibles sur ce site

  15. #35
    Membre chevronné Avatar de SofEvans
    Homme Profil pro
    Développeur C
    Inscrit en
    mars 2009
    Messages
    1 047
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France

    Informations professionnelles :
    Activité : Développeur C

    Informations forums :
    Inscription : mars 2009
    Messages : 1 047
    Points : 2 199
    Points
    2 199
    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.

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