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. #1
    Chroniqueur Actualités
    Avatar de Bruno
    Homme Profil pro
    Rédacteur technique
    Inscrit en
    Mai 2019
    Messages
    1 825
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Cameroun

    Informations professionnelles :
    Activité : Rédacteur technique
    Secteur : High Tech - Produits et services télécom et Internet

    Informations forums :
    Inscription : Mai 2019
    Messages : 1 825
    Points : 36 050
    Points
    36 050
    Par défaut Les différentes façons de gérer les erreurs en C
    Les différentes façons de gérer les erreurs en C,
    le C ne dispose pas d'une seule façon claire pour gérer les erreurs

    Le langage de programmation C offre au développeur une marge de contrôle importante sur la machine (notamment sur la gestion de la mémoire) et est de ce fait utilisé pour réaliser les « fondations » (compilateurs, interpréteurs…) des langages plus modernes. C'est un langage de programmation impératif généraliste, de bas niveau. Inventé au début des années 1970 pour réécrire Unix, C est devenu un des langages les plus utilisés, encore de nos jours. De nombreux langages plus modernes comme C++, C#, Java et PHP ou JavaScript ont repris une syntaxe similaire au C et reprennent en partie sa logique.

    En tant que tel, le langage C ne fournit pas de support direct pour la gestion des erreurs, mais étant un langage de programmation système, il vous fournit un accès à un niveau inférieur sous la forme de valeurs de retour. La plupart des appels de fonctions C ou même Unix renvoient -1 ou NULL en cas d'erreur et définissent un code d'erreur errno. Ainsi, un programmeur C peut vérifier les valeurs retournées et peut prendre les mesures appropriées en fonction de la valeur de retour.

    Nom : cB.png
Affichages : 785291
Taille : 43,7 Ko

    Essayons de simuler une condition d'erreur et d'ouvrir un fichier qui n'existe pas. Ici, les deux fonctions sont utilisées pour montrer leur usage, mais il est possible d'utiliser une ou plusieurs façons d'imprimer les erreurs. Le deuxième point important à noter est qu'il est possible d'utiliser le flux de fichiers stderr pour afficher toutes les erreurs.

    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
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
     
    extern int errno ;
     
    int main () {
     
       FILE * pf;
       int errnum;
       pf = fopen ("unexist.txt", "rb");
     
       if (pf == NULL) {
     
          errnum = errno;
          fprintf(stderr, "Value of errno: %d\n", errno);
          perror("Error printed by perror");
          fprintf(stderr, "Error opening file: %s\n", strerror( errnum ));
       } else {
     
          fclose (pf);
       }
     
       return 0;
    }

    Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant

    Value of errno: 2
    Error printed by perror: No such file or directory
    Error opening file: No such file or directory


    Le C n'a pas une seule façon claire de gérer les erreurs

    Statut de sortie

    Il fournit une fonction exit() qui prend deux valeurs pour afficher une fin réussie ou non réussie en utilisant EXIT_SUCCESS et EXIT_FAILURE. Cette fonction exit() est définie dans le fichier d'en-tête stdlib.h de la bibliothèque standard.

    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
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <stdlib.h>
    int main ()
    {
    FILE * f;
    f = fopen ("article.txt", "rb");
    if (f == NULL)
    {
    printf("The Value of errno printed is : %d\n", errno);
    printf("Error message printed while opening the file with errno: %s\n",
    strerror(errno));
    perror("Error message printed by perror");
    exit(EXIT_FAILURE);
    printf("The message will not be printed\n");
    }
    else
    {
    fclose (f);
    exit(EXIT_SUCCESS);
    printf("The message will be printed\n");
    }
    return 0;
    }

    L'algorithme de l'autruche

    Si une condition d'erreur est suffisamment rare, il est toujours possible de faire l'autruche et choisir d'ignorer cette possibilité. Cela peut rendre le code beaucoup plus joli, mais au détriment de la robustesse.

    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
    #include <stdio.h>
     
    int parse_natural_base_10_number(const char* s) {
        int parsed = 0;
        for (size_t i = 0; s[i] != '\0'; i++) {
            parsed *= 10;
            parsed += s[i] - '0';
        }
     
        return parsed;
    }
     
     
    int main() {
        printf("Expecting garbage or crash on bad values\n");
        const char* examples[] = { "10", "foo", "42", "" };
        for (size_t i = 0; i < 4; i++) {
            const char* example = examples[i];
            int parsed = parse_natural_base_10_number(example);
            printf("parsed: %d\n", parsed);
        }
     
        return 0;
    }

    Expecting garbage or crash on bad values
    parsed: 10
    parsed: 6093
    parsed: 42
    parsed: 0


    Un exemple concret de cela peut être observé avec l'utilisation de malloc par le micrologiciel des dispositifs de flipper.

    Crash

    Parfois, les erreurs sont pratiquement irrécupérables. Selon McCue, la plupart des applications devraient probablement abandonner lorsque malloc renvoie NULL. Si vous êtes sûr qu'il n'y a pas de moyen de récupérer une condition d'erreur et que l'appelant ne voudra pas la gérer d'une autre manière, il est simplement possible d'imprimer un message disant ce qui s'est mal passé et quitter le programme.

    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
     
    #include <stdio.h>
    #include <stdlib.h>
     
    int parse_natural_base_10_number(const char* s) {
        int parsed = 0;
        for (size_t i = 0; s[i] != '\0'; i++) {
            if (s[i] < '0' || s[i] > '9') {
                printf(
                   "Got a bad character ('%c') in %s, crashing.", 
                   s[i], 
                   s
                );
                exit(1);
            }
            else {
                parsed *= 10;
                parsed += s[i] - '0';
            }
        }
     
        return parsed;
    }
     
    int main() {
        const char* examples[] = { "10", "42", "foo" };
        for (size_t i = 0; i < 3; i++) {
            const char* example = examples[i];
            int parsed = parse_natural_base_10_number(example);
            printf("parsed: %d\n", parsed);
        }
     
        return 0;
    }

    parsed: 10
    parsed: 42
    Got a bad character ('f') in foo, crashing.


    Retourner un nombre négatif

    Si la fonction renvoie normalement un nombre naturel, il est possible d'utiliser un nombre négatif pour indiquer un échec. Cela s'applique aussi bien à l'exemple qu'à des cas tels que le renvoi du nombre d'octets lus dans un fichier. S'il existe différents types d'erreurs pour ce genre de cas,il est également possible d'utiliser des nombres négatifs spécifiques pour indiquer les différentes catégories.

    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
    #include <stdio.h>
     
    int parse_natural_base_10_number(const char* s) {
        int parsed = 0;
        for (size_t i = 0; s[i] != '\0'; i++) {
            if (s[i] < '0' || s[i] > '9') {
                return -1;
            }
            else {
                parsed *= 10;
                parsed += s[i] - '0';
            }
        }
     
        return parsed;
    }
     
    int main() {
        const char* examples[] = { "10", "foo", "42" };
        for (size_t i = 0; i < 3; i++) {
            const char* example = examples[i];
            int parsed = parse_natural_base_10_number(example);
            if (parsed < 0) {
                printf("failed: %s\n", example);
            }
            else {
                printf("worked: %d\n", parsed);
            }
        }
     
        return 0;
    }

    worked: 10
    failed: foo
    worked: 42


    Retourner NULL

    Si la fonction renvoie normalement un pointeur, il est possible d'utiliser NULL pour indiquer que quelque chose s'est mal passé. La plupart des fonctions qui renverraient des pointeurs effectueraient une allocation au tas pour que cela soit sain, donc ce schéma n'est probablement pas applicable lorsque vous voulez éviter les allocations. Pour McCue, il serait stupide d'allouer un [/C]int[/C] au tas.

    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
    #include <stdio.h>
    #include <stdlib.h>
     
    int* parse_natural_base_10_number(const char* s) {
        int parsed = 0;
        for (size_t i = 0; s[i] != '\0'; i++) {
            if (s[i] < '0' || s[i] > '9') {
                return NULL;
            }
            else {
                parsed *= 10;
                parsed += s[i] - '0';
            }
        }
     
        int* result = malloc(sizeof (int));
        *result = parsed;
        return result;
    }
     
    int main() {
        const char* examples[] = { "10", "foo", "42" };
        for (size_t i = 0; i < 3; i++) {
            const char* example = examples[i];
            int* parsed = parse_natural_base_10_number(example);
            if (parsed == NULL) {
                printf("failed: %s\n", example);
            }
            else {
                printf("worked: %d\n", *parsed);
            }
     
            free(parsed);
        }
     
        return 0;
    }

    worked: 10
    failed: foo
    worked: 42


    Un exemple concret de ce schéma est celui de malloc. Si malloc ne parvient pas à allouer de la mémoire, au lieu de renvoyer un pointeur vers la mémoire nouvellement allouée, il renvoie un pointeur nul.

    Retourner un booléen et prendre un paramètre externe

    L'une des choses les moins évidentes qu'il est possible de faire en C est d'avoir un ou plusieurs arguments d'une fonction out params. Cela signifie qu'il fait partie du contrat de la fonction qu'elle écrira dans la mémoire derrière un pointeur. Si une fonction peut échouer, une traduction naturelle de ceci peut être de retourner un booléen indiquant si elle l'a fait et de passer un paramètre out qui n'est n'inspecté que lorsque true est retourné.

    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
    #include <stdio.h>
    #include <stdbool.h>
     
    bool parse_natural_base_10_number(const char* s, int* out) {
        int parsed = 0;
        for (size_t i = 0; s[i] != '\0'; i++) {
            if (s[i] < '0' || s[i] > '9') {
                return false;
            }
            else {
                parsed *= 10;
                parsed += s[i] - '0';
            }
        }
     
        *out = parsed;
        return true;
    }
     
    int main() {
        const char* examples[] = { "10", "foo", "42" };
        for (size_t i = 0; i < 3; i++) {
            const char* example = examples[i];
            int parsed;
            bool success = parse_natural_base_10_number(
                example, 
                &parsed
            );
            if (!success) {
                printf("failed: %s\n", example);
            }
            else {
                printf("worked: %d\n", parsed);
            }
        }
     
        return 0;
    }

    Retourner une énumération et prendre un paramètre de sortie

    Un booléen peut seulement indiquer que quelque chose a réussi ou échoué. Si vous voulez savoir pourquoi quelque chose a échoué, remplacer un enum par un booléen est un mécanisme assez naturel.

    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
    47
    48
    49
    50
    51
    #include <stdio.h>
     
    enum ParseNaturalNumberResult {
        PARSE_NATURAL_SUCCESS,
        PARSE_NATURAL_EMPTY_STRING,
        PARSE_NATURAL_BAD_CHARACTER
    } ;
     
    enum ParseNaturalNumberResult parse_natural_base_10_number(
       const char* s, 
       int* out
    ) {
        if (s[0] == '\0') {
            return PARSE_NATURAL_EMPTY_STRING ;
        }
     
        int parsed = 0 ;
        for (size_t i = 0 ; s[i] != '\0' ; i++) {
            if (s[i] < '0' || s[i] > '9') {
                return PARSE_NATURAL_BAD_CHARACTER ;
            }
            else {
                parsed *= 10 ;
                parsed += s[i] - '0' ;
            }
        }
     
        *out = parsed ;
        return PARSE_NATURAL_SUCCESS ;
    }
     
    int main() {
        const char* examples[] = { "10", "foo", "42", "" } ;
        for (size_t i = 0 ; i < 4 ; i++) {
            const char* exemple = exemples[i] ;
            int parsed ;
            switch (parse_natural_base_10_number(example, &parsed)) {
                cas PARSE_NATURAL_SUCCESS :
                    printf("a fonctionné : %d\n", parsed) ;
                    pause ;
                cas PARSE_NATURAL_EMPTY_STRING :
                    printf("failed because empty string\n") ;
                    pause ;
                cas PARSE_NATURAL_BAD_CHARACTER :
                    printf("failed because bad char : %s\n", exemple) ;
                    break ;
            }
        }
     
        return 0 ;
    }
    worked: 10
    failed because bad char: foo
    worked: 42
    failed because empty string


    Retourner un booléen et prendre deux paramètres en sortie

    Alors qu'un enum peut donner la "catégorie" d'une erreur, il n'a pas de place pour enregistrer des informations plus spécifiques que cela. Par exemple, il est assez raisonnable de vouloir savoir, si vous rencontrez un caractère inattendu, où se trouve ce caractère dans la chaîne de caractères. En ajoutant un deuxième paramètre out, il est possible d'avoir un endroit pour mettre cette information.

    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
    47
    48
    49
    50
    #include <stdio.h>
    #include <stdbool.h>
     
    bool parse_natural_base_10_number(
       const char* s, 
       int* out_value, 
       size_t* out_bad_index
    ) {
        int parsed = 0;
        for (size_t i = 0; s[i] != '\0'; i++) {
            if (s[i] < '0' || s[i] > '9') {
                *out_bad_index = i;
                return false;
            }
            else {
                parsed *= 10;
                parsed += s[i] - '0';
            }
        }
     
        *out_value = parsed;
        return true;
    }
     
    int main() {
        const char* examples[] = { "10", "foo", "42", "12a34" };
        for (size_t i = 0; i < 4; i++) {
            const char* example = examples[i];
     
            int parsed;
            size_t bad_index;
            bool success = parse_natural_base_10_number(
                example, 
                &parsed, 
                &bad_index
            );
            if (!success) {
                printf("failed: %s\n        ", example);
                for (size_t j = 0; j < bad_index; j++) {
                    printf(" ");
                }
                printf("\n");
            }
            else {
                printf("worked: %d\n", parsed);
            }
        }
     
        return 0;
    }

    worked: 10
    failed: foo

    worked: 42
    failed: 12a34


    Retourner un enum et plusieurs paramètres de sortie

    Une extension naturelle des deux modèles précédents est que si vous avez plusieurs façons dont un calcul peut échouer, il est possible de retourner un enumavec chaque façon et prendre un paramètre out pour chaque façon qui nécessiterait des données.

    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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    #include <stdio.h>
    #include <string.h>
     
    enum ParseNaturalNumberResult {
        PARSE_NATURAL_SUCCESS,
        PARSE_NATURAL_EMPTY_STRING,
        PARSE_NATURAL_BAD_CHARACTER,
        PARSE_NUMBER_TOO_BIG
    };
     
    struct BadCharacterInfo {
        size_t index;
    };
     
    struct TooBigInfo {
        size_t remaining_characters;
    };
     
    enum ParseNaturalNumberResult parse_natural_base_10_number(
            const char* s,
            int* out_value,
            struct BadCharacterInfo* bad_character_info,
            struct TooBigInfo* too_big_info
    ) {
        if (s[0] == '\0') {
            return PARSE_NATURAL_EMPTY_STRING;
        }
     
        int parsed = 0;
        for (size_t i = 0; s[i] != '\0'; i++) {
            if (s[i] < '0' || s[i] > '9') {
                bad_character_info->index = i;
                return PARSE_NATURAL_BAD_CHARACTER;
            }
            else {
                int digit = s[i] - '0';
                int new_parsed = (parsed * 10) + digit;
                if ((new_parsed - digit) / 10 != parsed) {
                    too_big_info->remaining_characters = strlen(s) - i;
                    return PARSE_NUMBER_TOO_BIG;
                }
                else {
                    parsed = new_parsed;
                }
            }
        }
     
        *out_value = parsed;
        return PARSE_NATURAL_SUCCESS;
    }
     
    int main() {
        const char* examples[] = { "10", 
                                   "foo", 
                                   "42", 
                                   "", 
                                   "99999999999999" };
        for (size_t i = 0; i < 5; i++) {
            const char* example = examples[i];
            int parsed;
            struct BadCharacterInfo bad_character_info;
            struct TooBigInfo too_big_info;
     
            switch (parse_natural_base_10_number(
                example, 
                &parsed, 
                &bad_character_info,
                &too_big_info
            )) {
                case PARSE_NATURAL_SUCCESS:
                    printf("worked: %d\n", parsed);
                    break;
                case PARSE_NATURAL_EMPTY_STRING:
                    printf("failed because empty string\n");
                    break;
                case PARSE_NATURAL_BAD_CHARACTER:
                    printf(
                        "failed because bad char at index %zu: %s\n",
                        bad_character_info.index,
                        example
                    );
                    break;
                case PARSE_NUMBER_TOO_BIG:
                    printf(
                        "number was too big. had %zu digits left: %s\n",
                        too_big_info.remaining_characters,
                        example
                    );
                    break;
            }
        }
     
        return 0;
    }

    worked: 10
    failed because bad char at index 0: foo
    worked: 42
    failed because empty string
    number was too big. had 5 digits left: 99999999999999


    Définir une valeur statique locale de thread

    Une autre option consiste à définir, en cas d'erreur, une variable statique locale. Cela évite d'avoir à propager explicitement une erreur tout le long de la pile à partir de l'endroit où elle se produit et rend l'API "normale" de la fonction aussi propre et nette que les approches autruche ou crash. Une fois que vous avez défini la valeur statique locale du thread, vous pouvez soit :

    1. Retourner une valeur prévisible indiquant un problème (NULL, un nombre négatif, etc.), ce qui incite le programmeur à vérifier la valeur statique locale du thread.
    2. Retourner une valeur non initialisée et compter sur le programmeur pour savoir que la valeur pourrait être fausse à moins qu'il ne vérifie la valeur statique locale du thread.

    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
    #include <stdio.h>
    #include <stdbool.h>
     
    _Thread_local static bool parse_number_error = false;
     
    int parse_natural_base_10_number(const char* s) {
        int parsed = 0;
        for (size_t i = 0; s[i] != '\0'; i++) {
            if (s[i] < '0' || s[i] > '9') {
                parse_number_error = true;
            }
            else {
                parsed *= 10;
                parsed += s[i] - '0';
            }
        }
     
        return parsed;
    }
     
    int main() {
        const char* examples[] = { "10", "42", "foo" };
        for (size_t i = 0; i < 3; i++) {
            const char* example = examples[i];
            int parsed = parse_natural_base_10_number(example);
            if (parse_number_error) {
                parse_number_error = false;
                printf("error: %s\n", example);
            }
            else {
                printf("parsed: %d\n", parsed);
            }
        }
     
        return 0;
    }

    parsed: 10
    parsed: 42
    error: foo


    Un grand nombre d'apis intégrées utilisent une constante statique partagée appelée errno et si elles échouent, elles lui attribuent une valeur non nulle. Il existe ensuite des fonctions comme perror qui peuvent extraire des messages à partir du code d'erreur spécifique. Techniquement, il est possible utiliser errno aussi, tant que vos conditions d'erreur peuvent tenir dans son encodage int.

    Des langages tels C2 ou C3 pour remplacer le C ?

    Comme dit précédemment, la gestion des erreurs dans le langage de programmation C n'est pas prise en charge, car il fournit quelques fonctions et des valeurs de numéros d'erreur qui sont imprimées comme des messages d'erreur. Le lamgage ne dispose que d'un support de bibliothèque très limité : il faut ajouter des chemins de recherche pour les fichiers d'en-tête, inclure certains fichiers d'en-tête et établir des liens avec des bibliothèques statiques ou dynamiques. Ces étapes sont toutes séparées. Si vous appelez des fonctions de bibliothèque sans les lier, vous pouvez avoir des références non définies.

    C2 a corrigé ce problème en faisant de l'utilisation de la bibliothèque une chose totalement automatique. Vous utilisez la bibliothèque ou vous ne l'utilisez pas. De plus, C2 supporte les bibliothèques sources. Il s'agit de bibliothèques qui sont utilisées sous forme de source (=C2). Cela permet une meilleure intégration et optimisation, en particulier lors de l'utilisation de nombreuses fonctions "simples" qui ne font que renvoyer un membre d'une structure opaque, par exemple. Cela permet également aux développeurs d'organiser leurs archives de code d'une manière beaucoup plus facile.

    C3 est un langage de programmation système basé sur le C. C'est une évolution du C permettant les mêmes paradigmes et conservant la même syntaxe dans la mesure du possible. C3 a commencé comme une extension du langage C2 par Bas van den Berg. Il a évolué de manière significative, non seulement au niveau de la syntaxe mais aussi en ce qui concerne la gestion des erreurs, les macros, les génériques et les chaînes de caractères.

    Source : Mccue

    Et vous ?

    Que pensez-vous du langage C ? Dépassé ou d'actaulité

    Quel langage de programmation utilisez-vous pour contourner les manquements du C ?

    À votre avis, C2 et C3 pourraient remplacer le C ?

    N'aurait-il pas été plus bénéfique de conjuguer les efforts pour améliorer le C plutôt que de créer C2 ou encore C3 et peut être C4 ?

    Voir aussi :

    C3 : un langage de programmation système basé sur le C, permet un accès sécurisé aux tableaux, les conteneurs de haut niveau et manipulation des chaînes de caractères

    Microsoft célèbre les 20 ans de .NET, son Framework de développement, les dépôts .NET seraient dans le top 30 des projets open source à plus haute vélocité sur GitHub depuis 2017

    Microsoft a publié la version stable de Visual Studio 2022 avec une nouvelle expérience de rechargement à chaud pour les applications natives C++, cette version est disponible uniquement en 64 bits

    Un développeur publie un langage de programmation qui peut être traduit automatiquement en C, C++, C#, Java, JavaScript, etc., avec une traduction rapide et sans machine virtuelle
    Contribuez au club : corrections, suggestions, critiques, ... Contactez le service news et Rédigez des actualités

  2. #2
    Membre expérimenté
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Juillet 2020
    Messages
    352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet NTIC

    Informations forums :
    Inscription : Juillet 2020
    Messages : 352
    Points : 1 376
    Points
    1 376
    Par défaut
    Bonjour,
    une «autre méthode» (classique) consiste à utiliser errno pour indiquer le statut du résultat. On assigne 0 à errno, on effectue l'opération, si une erreur survient alors errno n'est plus nul et contient un code erreur ; on pourra avantageusement réutiliser les codes standards. Par exemple :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
        errno=0;
        int n=parse_natural_base_10_number(test_string);
        if (errno) {
            perror("parsing failed");
        } else {
            printf("parsed %d\n", n);
        }
    parse_natural_base_10_number pourrait par exemple utiliser EINVAL si la chaîne contient des caractères illégaux, ERANGE si la chaîne contient bien un nombre mais irreprésentable en int, etc.


    Sinon, dans le même genre, on peut créer un type adéquat. On ne parse pas une chaîne, on essaye de la parser et du coup le résultat d'une telle opération devrait être non un entier mais un type représentant soit un (succès, entier) soit un (échec, raison). Par 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
     
        enum try_parse_error { INVALID_CHAR, RANGE };
        const char *try_parse_error_str[] = {
            [INVALID_CHAR]="unexpected char",
            [RANGE]="integer range error",
        };
     
        struct try_parse_int_result {
            bool success;
            union {
                int value;
                enum try_parse_error error;
            }
        }
     
    ...
     
        struct try_parse_int_result result=try_parse_int(test_string);
        if (result.success) {
            printf("parsed %d\n", result.value);
        } else {
            printf("parsing failed with error : %s\n", try_parse_error_str[result.error]);
        }

  3. #3
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 631
    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 631
    Points : 30 865
    Points
    30 865
    Billets dans le blog
    1
    Par défaut
    Bonjour
    Citation Envoyé par WhiteCrow Voir le message
    Sinon, dans le même genre, on peut créer un type adéquat. On ne parse pas une chaîne, on essaye de la parser et du coup le résultat d'une telle opération devrait être non un entier mais un type représentant soit un (succès, entier) soit un (échec, raison).
    Solution très élégante Mais je préfère celle du errno qui me semble être justement le truc fait pour ça.
    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. #4
    Membre émérite
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    851
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 851
    Points : 2 293
    Points
    2 293
    Par défaut
    Citation Envoyé par Sve@r Voir le message
    Bonjour

    Solution très élégante Mais je préfère celle du errno qui me semble être justement le truc fait pour ça.
    Jusqu'au jour où tu fais du multi-threading. Les variables globales comme errno deviennent beaucoup moins fun d'un coup.

  5. #5
    Membre expérimenté
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Juillet 2020
    Messages
    352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet NTIC

    Informations forums :
    Inscription : Juillet 2020
    Messages : 352
    Points : 1 376
    Points
    1 376
    Par défaut
    De nos jours errno est thread local … donc moins de soucis de côté là. En revanche il y a toujours un problème de réentrance dans un même thread, genre un signal handler qui se déclenche et modifie errno.
    Il faut clairement plus de rigueur quand on utilise errno que lors qu'on définit un type adapté, chose que l'on retrouve dans la plupart des langages plus récents.

  6. #6
    Membre éprouvé
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Avril 2014
    Messages
    498
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Avril 2014
    Messages : 498
    Points : 1 178
    Points
    1 178
    Par défaut
    Ou la gestion des exceptions: setjmp() longjmp()

  7. #7
    Membre régulier
    Homme Profil pro
    Développeur en systèmes embarqués
    Inscrit en
    Juillet 2012
    Messages
    21
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Développeur en systèmes embarqués
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Juillet 2012
    Messages : 21
    Points : 95
    Points
    95
    Par défaut
    Citation Envoyé par imperio Voir le message
    Jusqu'au jour où tu fais du multi-threading. Les variables globales comme errno deviennent beaucoup moins fun d'un coup.

    La variable errno est propre au thread, ce n'est pas une pas une variable globale.

    https://stackoverflow.com/questions/1694164/is-errno-thread-safe

  8. #8
    Membre éprouvé
    Profil pro
    Inscrit en
    Septembre 2009
    Messages
    1 821
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Septembre 2009
    Messages : 1 821
    Points : 979
    Points
    979
    Par défaut
    Et comment vous faite pour afficher les noms des fonctions qui ont appelées la fonction qui génère l'erreur ?... c'est très pratique pour le debug

  9. #9
    Membre habitué
    Profil pro
    Chef de projet
    Inscrit en
    Septembre 2008
    Messages
    40
    Détails du profil
    Informations personnelles :
    Localisation : France, Vosges (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet

    Informations forums :
    Inscription : Septembre 2008
    Messages : 40
    Points : 181
    Points
    181
    Par défaut
    Je ne trouve pas le C "dépassé". Le C est utile dans des programmes bas niveau, proches CPU (quand on tape du C, on peut presque imaginer l'assembleur derrière). Faire des pilotes sans C (ou sans langage bas niveau), c'est difficile à imaginer.

    Par contre, le C dans une appli de gestion, c'est un non direct. Il y a des langages plus adaptés.

    De bons langages, il y en a eu plein - je ne suis pas sûr qu'il faille éternellement en ajouter (certains pas très bien réfléchis d'ailleurs...). Et utiliser des modificateurs à la compilation pour ajouter des comportements n'est pas non plus toujours souhaitable.

    Ce qui prime au-delà du langage, c'est les bibliothèques disponibles et son interropérabilité.

    Finalement, je choisirais facilement un langage plus haut niveau dès que j'ai une hétérogénéïté des formats de chaînes à traiter (UTF8,ASCII, autre...)

  10. #10
    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 327
    Points
    2 327
    Par défaut
    Citation Envoyé par boboss123 Voir le message
    Et comment vous faite pour afficher les noms des fonctions qui ont appelées la fonction qui génère l'erreur ?... c'est très pratique pour le debug
    Usuellement, la callstack est disponible en phase de développement via l'utilisation d'un débugueur (plus pratique que le printf).
    En phase de prod, c'est normalement pas possible car les binaires/bibliothèques sont généralement compilé sans les symboles (symboles comprenant les noms de fonction entre autres).

    Après, si tu veux avoir une callstack sur un log d'une erreur de prod, y'a pas 36 moyens et ils sont tous (à ma connaissance) dépendant du système et limité.

    Sur linux par exemple, tu peux utiliser backtrace (3) (puis backtrace_symbols (3)) mais pour avoir quelques chose d'exploitable, il faut linker avec l'option "-rdynamic".
    Et même avec tout ça, le nom des fonctions static n'apparaissent pas et le nom des fonctions des bibliothèque qui n'ont pas étés compilées avec -rdynamic non plus.

  11. #11
    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 327
    Points
    2 327
    Par défaut
    Et pis tiens, pour rajouter ma petite pierre à l’édifice.

    L'article parle plutôt bien des différentes méthodes pour gérer les erreurs, mais deux/trois petits trucs en plus :

    Déjà, on ne gère pas les erreurs de la même manière si on est en train de coder une bibliothèque ou un programme.

    La politique du "j'ai une erreur, je crash", c'est quelque chose qui me fait vraiment rager quand je trouve ça dans une bibliothèque.
    Une bibliothèque ne devrait jamais crasher, mais toujours retourner la main à la fonction appelante (à charge de celui qui utilise la bibliothèque de gérer).
    Bien sûr, celui qui fait la bibliothèque doit bien faire attention à gérer les erreurs de manière a ce que l'appelant puisse gérer de son côté (et la, l'article détail bien).
    J'ai personnellement une préférence pour une variable globale local au thread à la manière d'errno (si c'est utile, bien sûr).

    Idem pour la politique de l'autruche : il vaut mieux éviter ça dans une bibliothèque.
    Rien de plus énervant que de déboguer pendant plusieurs jours pour se rendre compte qu'un cas extrêmement rare (souvent issue d'une race condition, histoire de rajouter du fun) n'as pas été testé (et provoque une erreur silencieuse, toujours pour plus de fun).

    Le petit plus sympathique : quand la bibliothèque fait des logs et te permet de rediriger les logs.
    Bon usuellement c'est stderr et ça fait très bien le taff, mais des fois pouvoir dire "le fichier de log, c'est celui-là", ça peut être sympa.


    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;
    }
    Ainsi, de cette manière, vous pouvez être assuré de ne pas faire de fuite de mémoire quelque soit l'endroit où le code échoue, et l'ajout d'autres variables à nettoyer se fait simplement, même si la fonction est longue.

    On peut aussi implémenter "simplement" des actions de rollback.
    Imaginez que la fonction précédente ait pour but de créer un nouveau fichier et qu'en cas d'erreur, le fichier doit être supprimé afin de laisser le système propre.
    On peut donc ajouter à 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
    22
    23
        ...
     
        // Fin de la fonction
        returnValue = true;
     
        /* GOTO */END_FUNCTION:
     
        /* action de rollback */
        if (!returnValue) {
            if (logPathfile) {
                remove(logPathfile);
            }
        }
     
        /* action de clean memoire */
        free(logPathfile);
        if (logFile) {
            fclose(logFile);
        }
        free(msg);
     
        return returnValue;
    }


    Après tout est question de mesure : si vos actions de rollback prennent 200 lignes et font 48 actions, c'est p't'être qu'il faudrait organiser différemment le code.
    Bref, goto c'est bien, goto c'est cool, n'hésitez pas à l'utiliser pour la gestion d'erreur en C. Pour les autres utilisations, c'est à vos risque et péril.

  12. #12
    Membre éprouvé
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Avril 2014
    Messages
    498
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Avril 2014
    Messages : 498
    Points : 1 178
    Points
    1 178
    Par défaut
    Citation Envoyé par SofEvans Voir le message
    Et pis tiens, pour rajouter ma petite pierre à l’édifice.

    Bref, goto c'est bien, goto c'est cool, n'hésitez pas à l'utiliser pour la gestion d'erreur en C. Pour les autres utilisations, c'est à vos risque et péril.
    Toi tu risques des ennuis si tu sors ça sur n'importe quel forum haha.

    On m'a apprit que go to est à bannir et je suis plutot d'accord avec ça avec une grosse nuance: l'emploi de rares goto n'est pour moi à reserver que pour les excellents programmeurs.

    Pour les exceptions, c'est setjmp() longjmp() qui fait le job des go to.

  13. #13
    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 327
    Points
    2 327
    Par défaut
    Citation Envoyé par tabouret Voir le message
    On m'a apprit que go to est à bannir et je suis plutot d'accord avec ça.

    Pour les exceptions, c'est setjmp() longjmp() qui fait le job des go to.
    Je ne comprends pas pourquoi il vaut mieux utiliser setjmp()/longjmp() ...

    setjmp()/longjmp() c'est un saut global (n'importe où dans le code), goto c'est un saut local (limité à la fonction).

    Si encore tu avais dit,

    On m'a apprit que setjmp() longjmp() est à bannir [...]
    Pour les exceptions, c'est goto qui fait le job [...]
    j'aurais compris la logique (il vaut mieux un saut local que global), mais là ???

    Après, moi aussi on m'as appris que goto c'est caca et qu'il faut pas l'utiliser.
    On m'as aussi appris qu'il ne faut jamais utiliser "for" ...

    Autant dire que les interdictions de ce genre, elles ont vites fait de sauter.

  14. #14
    Membre éprouvé
    Homme Profil pro
    Ingénieur sécurité
    Inscrit en
    Avril 2014
    Messages
    498
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 39
    Localisation : France, Hauts de Seine (Île de France)

    Informations professionnelles :
    Activité : Ingénieur sécurité
    Secteur : Industrie

    Informations forums :
    Inscription : Avril 2014
    Messages : 498
    Points : 1 178
    Points
    1 178
    Par défaut
    Citation Envoyé par SofEvans Voir le message
    j'aurais compris la logique (il vaut mieux un saut local que global), mais là ???

    Après, moi aussi on m'as appris que goto c'est caca et qu'il faut pas l'utiliser.
    On m'as aussi appris qu'il ne faut jamais utiliser "for" ...

    Autant dire que les interdictions de ce genre, elles ont vites fait de sauter.

    J'ai du développer une allergie aux goto mais oui ton argument tient la route. Goto de mon point de vue ne doit jamais être utilisé par un débutant.

    Goto ca peut se comprendre le code devient vite degueulasse, illisible et non maintenable.
    Mais for(), je ne comprends pas.

  15. #15
    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 327
    Points
    2 327
    Par défaut
    Citation Envoyé par tabouret Voir le message
    Goto ca peut se comprendre le code devient vite degueulasse, illisible et non maintenable.
    Mais for(), je ne comprends pas.
    "for" était interdit par la norme de codage de l'école pour exactement la même raison que goto : abusé, for peut vite devenir dégueulasse.
    Il faut dire que la norme limitait le nombre de ligne qu'une fonction (24 de mémoire) peut avoir ainsi que le nombre de colonne par ligne (80 caractère max).
    Donc fallait faire rentrer ton code la dedans, et certains étudiants ont abusé de for (la partie init notamment).

    Genre ça

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    var_int_1 = 10;
    var_int_2 = 20;
    var_str_1 = "toto";
     
    for (i = 0; str[i]; ++i)
    ça devenait ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    for (var_int_1 = 10, var_int_2 = 20, var_str_1 = "toto", i = 0; str[i]; ++i)
    Hop, 3 lignes de gagné ...

    Et j'imagine qu'aujourd'hui encore, leur norme alacon existe toujours et n'as pas changé ...


    Citation Envoyé par tabouret Voir le message
    Goto de mon point de vue ne doit jamais être utilisé par un débutant.
    De mon point de vue, c'est plus qu'il n'existe pas de situation où un débutant devrait normalement utilisé goto qui fait qu'un débutant ne doit jamais utilisé goto (parce que sinon oui, ca part vite en vrille).
    Mais personnellement, si un débutant veut faire la gestion d'erreur d'une bibliothèque qu'il développe, clairement je lui recommanderais goto (bon faut que le code soit un peu conséquent quand même, sinon ca fait vraiment miséreux).
    Il suffit de bien expliquer, un débutant n'est pas un idiot, il peut comprendre.

    C'est un peu comme les variables globales : on les diabolise, on dit qu'ils faut jamais les utiliser mais quand tu dois gérer les signaux tu te retrouves à être obligé de les utiliser.
    Un peu comme avec n'importe quelle fonction de callback qui n'accepte pas de paramètre (coucou qsort ....).

  16. #16
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 631
    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 631
    Points : 30 865
    Points
    30 865
    Billets dans le blog
    1
    Par défaut
    Citation Envoyé par SofEvans Voir le message
    Il faut dire que la norme limitait le nombre de ligne qu'une fonction (24 de mémoire) peut avoir ainsi que le nombre de colonne par ligne (80 caractère max).
    Ah oui, ces "pseudo-règles" de normalisation et d'efficacité édictées par des profs qui n'ont jamais fait de C.
    Une fonction doit faire un job et un seul (elle peut alors être réutilisées dans différents contextes, principe du légo). Elle peut prendre pour ça 10 ou 100 lignes, tant que ce qui se fait est bien expliqué (et autrement que par des i++; // J'incrémente i) alors pas de souci.

    Citation Envoyé par SofEvans Voir le message
    ça devenait ça : for (var_int_1 = 10, var_int_2 = 20, var_str_1 = "toto", i = 0; str[i]; ++i). Hop, 3 lignes de gagné ...
    Effectivement le résultat logique et évidemment totalement contre-productif de cette inquisition. Normalement, face à un tel résultat, n'importe quel prof de correctement câblé remettrait en cause cette doctrine. Mais quand l'idéologie d'un idéologue ne marche pas, il en déduit toujours qu'il n'y a pas assez d'idéologie. Total, on interdit le for().

    Résultat: while (var_int_1 = 10, var_int_2 = 20, var_str_1 = "toto", i = 0, str[i++]). On va faire quoi ensuite? Interdire le while ???

    Citation Envoyé par SofEvans Voir le message
    Et j'imagine qu'aujourd'hui encore, leurs normes alacon existe toujours et n'as pas changé ...
    Tu as ta réponse.

    Citation Envoyé par SofEvans Voir le message
    Il suffit de bien expliquer, un débutant n'est pas un idiot, il peut comprendre.
    Il n'est pas idiot mais il n'a pas encore l'expérience et le recul. Déjà on lui dit qu'il faut checker le retour de malloc() et de fopen() il va râler (lui il veut voir son code fonctionner au plus vite et pas coder des alternatives qu'il pense inutiles). Puis il ne "sent" pas comment faire si malloc() ou fopen() échouent (il n'a pas encore la "vision" des appels). Donc voilà, il est assez désarmé pour bien comprendre les avantages de ta méthode (j'ai beaucoup aimé tes exemples et si tu fouilles un peu mes posts tu devrais trouver les mêmes).

    Citation Envoyé par SofEvans Voir le message
    C'est un peu comme les variables globales : on les diabolise, on dit qu'ils faut jamais les utiliser mais quand tu dois gérer les signaux tu te retrouves à être obligé de les utiliser.
    Un peu comme avec n'importe quelle fonction de callback qui n'accepte pas de paramètre (coucou qsort ....).
    Là je nuancerai un peu. Les globales c'est comme les goto: à n'utiliser que si nécessaire Bref on a des outils, certains sont dangereux (scie sauteuse) mais parfois utiles (scier un gros truc).
    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]

  17. #17
    Expert éminent
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    Avril 2016
    Messages
    1 460
    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 460
    Points : 6 064
    Points
    6 064
    Par défaut
    Citation Envoyé par SofEvans Voir le message
    C'est un peu comme les variables globales : on les diabolise, on dit qu'ils faut jamais les utiliser mais quand tu dois gérer les signaux tu te retrouves à être obligé de les utiliser.
    Un peu comme avec n'importe quelle fonction de callback qui n'accepte pas de paramètre (coucou qsort ....).
    Remarque : depuis C11, à la place de qsort, on peut aussi utiliser qsort_s qui permet d'avoir une callback avec état, sans passer par des variables globales.

  18. #18
    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
    Je ne comprends pas pourquoi il vaut mieux utiliser setjmp()/longjmp() ...

    setjmp()/longjmp() c'est un saut global (n'importe où dans le code), goto c'est un saut local (limité à la fonction).
    .....

    (il vaut mieux un saut local que global)
    setjump()/longjump() et goto ne peuvent être comparés car :
    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).
    2) setjmp()/longjmp() sert effectivement à effectuer un saut global qui est nécessaire dans certain cas (dépiler la pile des appels pour revenir au "main()" par exemple) afin de traiter une situation qu'on ne peut pas traiter localement. Par exemple, dans un compilateur, il y a des cas ou une erreur détectée dans la phase d'analyse sémantique ne permet pas de continuer à compiler. Dans ce cas il faut dépiler la pile des appels jusqu'au main() pour arrêter la compilation proprement. En C, seuls setjmp()/longjmp() permettent d'effectuer cette action.

    Citation Envoyé par tabouret Voir le message
    Goto de mon point de vue ne doit jamais être utilisé par un débutant.
    Et encore moins par un développeur confirmé...

    Citation Envoyé par Sve@r Voir le message
    Les globales c'est comme les goto: à n'utiliser que si nécessaire
    Les goto ne sont JAMAIS nécessaires : la preuve en Java

    Les variables globales ne doivent être utilisées que si elles servent à gérer l'environnement de l'application. Par exemple, dans une interface graphique, la fenêtre active sera dans une variable globale pour éviter qu'à tout bout de champ on appelle une fonction pour savoir quelle est la fenêtre active.

  19. #19
    Membre expérimenté
    Homme Profil pro
    Chef de projet NTIC
    Inscrit en
    Juillet 2020
    Messages
    352
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 51
    Localisation : France, Moselle (Lorraine)

    Informations professionnelles :
    Activité : Chef de projet NTIC

    Informations forums :
    Inscription : Juillet 2020
    Messages : 352
    Points : 1 376
    Points
    1 376
    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.

  20. #20
    Expert éminent sénior
    Avatar de Sve@r
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Février 2006
    Messages
    12 631
    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 631
    Points : 30 865
    Points
    30 865
    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 «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]

Discussions similaires

  1. Réponses: 5
    Dernier message: 06/11/2020, 09h45
  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, 12h46
  3. Proc. Stock. : Gérer les erreurs
    Par audreyc dans le forum SQL Procédural
    Réponses: 4
    Dernier message: 29/03/2006, 15h51
  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, 16h46

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