Justification des fonctions de la bibliothèque C ne définissant jamais errno à zéro

9

La norme C exige qu'aucune fonction de bibliothèque standard C ne soit mise errnoà zéro. Pourquoi est-ce exactement cela?

Je pourrais comprendre qu'il soit utile d'appeler plusieurs fonctions et de ne vérifier errnoqu'après la dernière - par exemple:

errno = 0;
double x = strtod(str1, NULL);
long y = strtol(str2, NULL);
if (errno)
    // either "strtod" or "strtol" failed
else
    // both succeeded

Cependant, cela n'est-il pas considéré comme une "mauvaise pratique"? Comme vous ne vérifiez errnoqu'à la toute fin, vous savez seulement qu'une des fonctions a échoué, mais pas la fonction qui a échoué. Le simple fait de savoir que quelque chose a échoué est-il suffisant pour la plupart des programmes pratiques?

J'ai essayé de chercher divers documents de justification C, mais beaucoup d'entre eux n'ont pas beaucoup de détails <errno.h>.

Drew McGowen
la source
1
Je suppose que c'est "ne payez pas pour ce que vous ne voulez pas". Si vous vous souciez errno, vous pouvez toujours le mettre à zéro vous-même.
Kerrek SB
2
Il faut également savoir qu'une fonction peut errnoavoir une valeur non nulle même si elle réussit. (Il pourrait appeler une autre fonction qui échoue, mais cela ne constitue pas une défaillance de la fonction extérieure.)
Keith Thompson

Réponses:

10

La bibliothèque C n'est pas définie errnosur 0 pour des raisons historiques 1 . POSIX ne prétend plus que ses bibliothèques ne modifieront pas la valeur en cas de succès, et la nouvelle page de manuel Linux pour leerrno.h reflète:

Le <errno.h>fichier d'en-tête définit la variable entière errno, qui est définie par les appels système et certaines fonctions de bibliothèque en cas d'erreur pour indiquer ce qui n'a pas fonctionné. Sa valeur n'est significative que lorsque la valeur de retour de l'appel indique une erreur (c'est-à-dire -1de la plupart des appels système -1ou NULLde la plupart des fonctions de bibliothèque); une fonction qui réussit peut changer errno.

La justification de l'ANSI C indique que le comité a estimé qu'il était plus pratique d'adopter et de normaliser la pratique actuelle d'utilisation errno.

Le mécanisme de rapport d'erreurs centré sur le réglage de errnoest généralement considéré avec la tolérance au mieux. Il nécessite un `` couplage pathologique '' entre les fonctions de bibliothèque et utilise une cellule de mémoire inscriptible statique, ce qui interfère avec la construction de bibliothèques partageables. Néanmoins, le Comité a préféré standardiser ce mécanisme existant, quoique déficient, plutôt que d'inventer quelque chose de plus ambitieux.

Il existe presque toujours un moyen de vérifier les erreurs en dehors de la vérification si elles errnosont définies. Vérifier si l' errnoensemble obtenu n'est pas toujours fiable, car certains appels nécessitent d'appeler une API distincte pour obtenir la raison de l'erreur. Par exemple, ferror()est utilisé pour rechercher une erreur si vous obtenez un résultat court à partir de fread()ou fwrite().

Chose intéressante, votre exemple d'utilisation strtod()est l'un des cas où la définition errnode 0 avant l'appel est requise pour détecter correctement si une erreur s'est produite. Toutes les fonctions strto*()chaîne à nombre ont cette exigence, car une valeur de retour valide est renvoyée même en cas d'erreur.

errno = 0;
char *endptr;
double x = strtod(str1, &endptr);
if (endptr == str1) {
    /*...parse error */
} else if (errno == ERANGE) {
    if (x == 0) {
        /*...underflow */
    } else if (x == HUGE_VAL) {
        /*...positive overflow */
    } else if (x == -HUGE_VAL) {
        /*...negative overflow */
    } else {
        /*...unknown range error? */
    }
}

Le code ci-dessus est basé sur le comportement de strtod()tel que documenté sur Linux . La norme C stipule uniquement que le sous-dépassement ne peut pas renvoyer une valeur supérieure au plus petit positif double, et qu'il soit errnodéfini ou non sur la mise en ERANGEœuvre définie 2 .

Il existe en fait un compte rendu complet des avis de certification qui recommande de toujours définir la valeur errno0 avant un appel à la bibliothèque et de vérifier sa valeur après l'appel indique qu'une défaillance s'est produite . En effet, certains appels de bibliothèque sont définis errnomême si l'appel lui-même a réussi 3 .

La valeur de errnoest 0 au démarrage du programme, mais elle n'est jamais définie sur 0 par aucune fonction de bibliothèque. La valeur de errnopeut être définie sur non nulle par un appel de fonction de bibliothèque, qu'il y ait ou non une erreur, à condition que l'utilisation de errnone soit pas documentée dans la description de la fonction dans la norme C. Il est important pour un programme d'inspecter le contenu errnouniquement après qu'une erreur a été signalée. Plus précisément, errnon'a de sens qu'après qu'une fonction de bibliothèque qui se déclenche errnosur erreur a renvoyé un code d'erreur.


1. Auparavant, je prétendais que c'était pour éviter de masquer une erreur d'un appel précédent. Je ne trouve aucune preuve à l'appui de cette affirmation. J'ai également eu un faux printf()exemple.
2. Merci à @chux de l'avoir signalé. La référence est C.11 §7.22.1.3 ¶10.
3. Signalé par @KeithThompson dans un commentaire.

jxh
la source
Problème mineur: il semble qu'un sous-dépassement puisse entraîner un résultat différent de 0: "les fonctions renvoient une valeur dont l'amplitude n'est pas supérieure au plus petit nombre positif normalisé dans le type de retour" C11 7.22.1.3.10.
chux
@chux: Merci. Je ferai un montage. Étant donné que la définition errnode ERANGEest l'implémentation définie en cas de sous-dépassement, il n'y a en fait aucun moyen portable de détecter le sous-dépassement. Mon code a suivi ce que j'ai trouvé dans la page de manuel Linux sur mon système.
jxh
Vos commentaires sur les strto*fonctions sont exactement la raison pour laquelle j'ai demandé si mon exemple serait considéré comme une mauvaise pratique, mais c'est la seule façon de errnone pas être mis à zéro serait utile, ou même s'appliquer.
@DrewMcGowen: Je suppose que le seul point que vous pouvez prendre est que cela errnopeut être réglé même en cas de succès, donc simplement utiliser le fait qu'il a été défini n'est pas vraiment un assez bon indicateur qu'une erreur s'est produite. Vous devez vérifier les résultats des appels individuels pour savoir si une erreur s'est produite.
jxh
1

Vous pouvez faire une vérification d'erreur pour les deux appels de fonction, si vous vous souciez vraiment.

errno = 0;
double x = strtod(str1, NULL);
if (errno)
    // strtod"  failed
else
    // "strtod" succeeded

long y = strtol(str2, NULL);
if (errno)
    // "strtol" failed
else
    // "strtol" succeeded

Puisque nous ne savons jamais comment la fonction mach est appelée dans un processus, comment la lib peut-elle définir des errnos pour chaque appel de fonction, n'est-ce pas?


la source
1

Changer arbitrairement l'errorno est analogue à «attraper et avaler» une exception. Avant qu'il y ait des exceptions qui se propageraient à travers différentes couches d'un programme et atteindraient finalement un point où l'appelant intercepterait et répondrait à l'exception d'une certaine manière ou passerait simplement la balle, il y avait des erreurs. Ne pas modifier l'errno, sauf si vous gérez, remplacez, traitez l'erreur comme non pertinente, est essentiel à ce paradigme de propagation de la gestion des erreurs de première génération.

Andyz Smith
la source