50 votes

Est-ce que &errno est un C légal ?

Per 7.5,

[errno] se développe en une valeur l modifiable175) de type int, dont la valeur est fixée à un nombre d'erreur positif par plusieurs fonctions de la bibliothèque. Il n'est pas précisé si errno est une macro ou un identifiant déclaré avec un lien externe. Si une définition de macro est supprimée afin d'accéder à un objet réel, ou si un programme définit un identifiant avec le nom errno, le comportement est indéfini.

175) La macro errno ne doit pas nécessairement être l'identifiant d'un objet. Elle peut s'étendre à une lvalue modifiable résultant d'un appel de fonction (par exemple, *errno()).

Je ne sais pas si c'est suffisant pour exiger que errno ne constitue pas une violation de contrainte. Le langage C dispose de valeurs l (telles que les variables de classe registre-stockage) ; toutefois, celles-ci ne peuvent être automatiques que dans les cas suivants errno n'a pas pu être définie en tant que telle) pour laquelle l & est une violation de contrainte.

Si &errno est légal C, doit-il être constant ?

17voto

nelhage Points 1660

Le §6.5.3.2p1 précise donc que

L'opérande de l'opérateur unaire & doit être soit un désignateur de fonction, le résultat d'un opérateur [] ou unaire *, soit une valeur l désignant un objet qui n'est pas un champ de bits et qui n'est pas déclaré avec le spécificateur de classe de stockage de registre.

Que je pensez à peut être considéré comme signifiant que &lvalue convient pour toute valeur de lval qui n'entre pas dans ces deux catégories. Et comme vous l'avez mentionné, errno ne peut pas être déclaré avec le spécificateur de classe de stockage de registre, et je pense (bien que je n'aie pas les références pour le vérifier maintenant) que vous ne pouvez pas avoir un champ de bits qui a le type de plain int .

Donc je crois que la spécification exige &(errno) pour être légal C.

Si &errno est un C légal, doit-il être constant ?

D'après ce que j'ai compris, une partie du but de permettre errno pour être une macro (et la raison pour laquelle elle est dans la glibc par exemple) est de lui permettre d'être une référence à un stockage local au thread, auquel cas elle ne sera certainement pas constante entre les threads. Et je ne vois aucune raison de s'attendre à ce qu'elle soit constante. Tant que la valeur de errno conserve la sémantique spécifiée, je ne vois pas pourquoi une bibliothèque C perverse ne pourrait pas modifier &errno pour faire référence à différentes adresses mémoire au cours d'un programme -- par exemple en libérant et en réallouant le backing store à chaque fois que vous définissez des paramètres d'accès à la mémoire. errno .

Vous pourriez imaginer maintenir un tampon circulaire des dernières valeurs N errno définies par la bibliothèque, et avoir &errno toujours pointer vers le plus récent. Je ne pense pas que cela soit particulièrement utile, mais je ne vois pas en quoi cela viole la spécification.

14voto

Nemo Points 32838

Je suis surpris que personne n'ait cité le C11 spec encore. Je m'excuse pour cette longue citation, mais je pense qu'elle est pertinente.

7.5 Erreurs

L'en-tête définit plusieurs macros...

...et

errno

qui se développe en une lvalue modifiable(201) de type int et le fil local durée de stockage, dont la valeur est fixée à un nombre d'erreur positif par plusieurs fonctions de la bibliothèque. Si une définition de macro est supprimée afin d'accéder à un objet réel, ou si un programme définit un identifiant avec le nom errno le comportement est indéfini.

La valeur de errno dans le fil initial est zéro au démarrage du programme (la valeur initiale de errno dans d'autres threads est une valeur indéterminée), mais n'est jamais mise à zéro par une fonction de bibliothèque. (202) La valeur de errno peut être fixée à une valeur non nulle par un appel de fonction de qu'il y ait ou non une erreur, à condition que l'utilisation de l'option errno n'est pas documentée dans la description de la fonction dans la présente norme internationale.

(201) La macro errno ne doit pas nécessairement être l'identifiant d'un objet. Il peut s'étendre à un valeur lval modifiable résultant d'un appel de fonction (par exemple, *errno() ).

(202) Ainsi, un programme qui utilise errno pour le contrôle d'erreur doit le mettre à zéro avant un appel de fonction de bibliothèque, puis l'inspecter avant un appel de fonction de bibliothèque ultérieur. Sur Bien entendu, une fonction de bibliothèque peut enregistrer la valeur de errno à l'entrée, puis le mettre à zéro, tant que la valeur originale est restaurée si errno La valeur de l'objet est encore nulle juste avant le retour.

"Fil local" signifie register est sorti. Type int signifie que les champs de bits sont hors jeu (IMO). Donc &errno ça me semble légal.

L'utilisation persistante de mots comme "il" et "il". le site valeur" suggère que les auteurs de la norme n'ont pas envisagé de &errno étant non-constant. Je suppose que l'on pourrait imaginer une implémentation où &errno n'était pas constant au sein d'un fil de discussion particulier, mais pour être utilisé de la façon dont les notes de bas de page le disent (mettre à zéro, puis vérifier après l'appel de la fonction de bibliothèque), il devrait être délibérément contradictoire, et éventuellement nécessiter un support de compilateur spécialisé juste pour être contradictoire.

En bref, si la spécification autorise une valeur non constante pour la fonction &errno je ne pense pas que ce soit délibéré.

[mise à jour]

R. pose une excellente question dans les commentaires. Après y avoir réfléchi, je crois maintenant connaître la bonne réponse à sa question, ainsi qu'à la question initiale. Voyons si je peux vous convaincre, cher lecteur.

R. fait remarquer que GCC permet quelque chose comme cela au niveau supérieur :

register int errno asm ("r37");  // line R

Cela permettrait de déclarer errno comme une valeur globale contenue dans le registre r37 . De toute évidence, il s'agirait d'une lvalue modifiable au niveau du thread. Ainsi, une implémentation C conforme pourrait-elle déclarer errno comme ça ?

La réponse est pas de . Lorsque vous ou moi utilisons le mot "déclaration", nous avons généralement à l'esprit un concept familier et intuitif. Mais la norme ne s'exprime pas de manière familière ou intuitive ; elle parle précisément Elle vise à n'utiliser que des termes bien définis. Dans le cas de "déclaration", la norme elle-même définit le terme ; et lorsqu'elle utilise ce terme, elle utilise sa propre définition.

En lisant la spécification, vous pouvez apprendre précisément ce qu'est une "déclaration" et ce qu'elle est précisément no . En d'autres termes, la norme décrit le langage "C". Elle ne décrit pas "un langage qui n'est pas C". En ce qui concerne la norme, "C avec extensions" est simplement "un langage qui n'est pas C".

Ainsi, du point de vue de la norme, la ligne R n'est pas du tout une déclaration . Il n'est même pas analysé ! Il pourrait aussi bien être lu :

long long long __Foo_e!r!r!n!o()blurfl??/**

En ce qui concerne la spécification, il s'agit tout autant d'une "déclaration" que la ligne R, c'est-à-dire pas du tout.

Donc, quand la spécification C11 dit, dans la section 6.5.3.2 :

L'opérande de la fonction unaire & L'opérateur est soit une fonction le résultat d'une fonction [] ou unaire * ou une lvalue qui désigne un objet qui n'est pas un bit-field et qui n'est pas déclaré avec le le registre storage-class specifier.

...ça veut dire quelque chose de très précis qui ne... no se référer à quelque chose comme la ligne R.

Maintenant, considérez la déclaration de la int objet à laquelle errno se réfère. (Remarque : je ne parle pas de la déclaration de la errno nom car, bien entendu, une telle déclaration pourrait ne pas exister si errno est, disons, une macro. Je veux dire que la déclaration du sous-jacent int objet).

Le langage ci-dessus indique que vous pouvez prendre l'adresse d'une valeur l à moins qu'elle ne désigne un champ de bits ou qu'elle désigne un objet "déclaré". register . Et la spécification pour le sous-jacent errno l'objet dit qu'il est modifiable int lvalue avec une durée locale.

Maintenant, il est vrai que la spécification ne dit pas que le sous-jacent errno doit être déclaré du tout. Peut-être qu'il apparaît simplement par une magie de compilateur définie par l'implémentation. Mais encore une fois, quand la spécification dit "déclaré avec le spécificateur de classe de stockage de registre", elle utilise sa propre terminologie.

Donc, soit le sous-jacent errno l'objet est "déclaré" au sens classique du terme, auquel cas il ne peut pas être à la fois register et thread-local ; ou il n'est pas déclaré du tout dans ce cas, il n'est pas déclaré register . De toute façon, comme il s'agit d'une lvalue, vous pouvez prendre son adresse.

(Sauf s'il s'agit d'un champ de bits, mais je pense que nous sommes d'accord sur le fait qu'un champ de bits n'est pas un objet de type int .)

3voto

Richard Chambers Points 3586

L'implémentation originale de errno était une variable int globale que divers composants de la bibliothèque C standard utilisaient pour indiquer une valeur d'erreur s'ils rencontraient une erreur. Cependant, même à cette époque, il fallait faire attention à code réentrant ou avec des appels de fonctions de bibliothèque qui pourraient définir errno à une valeur différente lors de la gestion d'une erreur. Normalement, on enregistre la valeur dans une variable temporaire si le code d'erreur est nécessaire pendant un certain temps en raison de la possibilité qu'une autre fonction ou un autre morceau de code définisse la valeur de errno soit explicitement, soit par le biais d'un appel de fonction de bibliothèque.

Ainsi, avec cette implémentation originale d'un int global, l'utilisation de l'adresse de l'opérateur et le fait de dépendre de l'adresse pour qu'elle reste constante étaient pratiquement intégrés dans la structure de la bibliothèque.

Cependant, avec le multithreading, il n'y avait plus de global unique, car un global unique n'était pas thread safe. Ainsi, l'idée d'avoir stockage local du fil peut-être en utilisant une fonction qui renvoie un pointeur vers une zone allouée. Vous pourriez donc voir une construction semblable à l'exemple suivant, totalement imaginaire :

#define errno (*myErrno())

typedef struct {
    // various memory areas for thread local stuff
    int  myErrNo;
    // more memory areas for thread local stuff
} ThreadLocalData;

ThreadLocalData *getMyThreadData () {
    ThreadLocalData *pThreadData = 0;   // placeholder for the real thing
    // locate the thread local data for the current thread through some means
    // then return a pointer to this thread's local data for the C run time
    return pThreadData;
}

int *myErrno () {
    return &(getMyThreadData()->myErrNo);
}

Puis errno serait utilisé comme s'il s'agissait d'une variable globale unique plutôt que d'une variable int thread safe par errno = 0; ou le vérifier comme if (errno == 22) { // handle the error et même quelque chose comme int *pErrno = &errno; . Tout cela fonctionne parce qu'au bout du compte, la zone de données locale du fil est allouée et reste en place et ne se déplace pas, et la définition de la macro qui fait de la errno ressemble à un extern int cache la plomberie de sa mise en œuvre réelle.

La seule chose que nous ne voulons pas est d'avoir l'adresse de errno passer soudainement d'une tranche de temps à l'autre d'un thread avec une sorte de séquence dynamique d'allocation, de clonage et de suppression pendant que nous accédons à la valeur. Lorsque votre tranche de temps est terminée, elle est terminée et, à moins que vous ne disposiez d'une sorte de synchronisation ou d'un moyen de conserver l'unité centrale après l'expiration de votre tranche de temps, le déplacement de la zone locale du thread me semble une proposition très risquée.

Cela implique à son tour que vous pouvez compter sur l'adresse de l'opérateur pour vous donner une valeur constante pour un thread particulier, même si cette valeur constante diffère d'un thread à l'autre. Je peux très bien voir la bibliothèque utiliser l'adresse de errno afin de réduire la surcharge liée à l'exécution d'une sorte de recherche locale de fil à chaque fois qu'une fonction de bibliothèque est appelée.

Ayant l'adresse de errno en tant que constante dans un thread assure également une compatibilité ascendante avec les anciens codes sources qui utilisaient le fichier d'inclusion errno.h comme ils auraient dû le faire (voir l'exemple suivant). page de manuel de linux pour errno qui avertit explicitement de ne pas utiliser extern int errno; comme cela était courant autrefois).

D'après ma lecture de la norme, il s'agit d'autoriser ce type de stockage local des threads tout en conservant une sémantique et une syntaxe similaires à celles de l'ancienne norme extern int errno; quand errno est utilisé et permettant l'ancien usage également pour une sorte de compilateur croisé pour un dispositif embarqué qui ne supporte pas le multithreading. Toutefois, la syntaxe peut être similaire en raison de l'utilisation d'une définition de macro, de sorte que la déclaration raccourcie à l'ancienne ne devrait pas être utilisée, car cette déclaration n'est pas ce que l'on attend de l'utilisateur. errno est vraiment.

0voto

md5 Points 14957

Nous pouvons trouver un contre-exemple : parce qu'un champ de bits peut avoir le type int , errno peut être un champ de bits. Dans ce cas, &errno serait invalide. Le comportement de la norme est ici de ne pas dire explicitement vous pouvez écrire &errno donc la définition de la comportement indéfini s'applique ici.

C11 (n1570), § 4. Conformité
Le comportement non défini est indiqué dans la présente Norme internationale par les mots " comportement non défini " ou par l'omission d'un élément quelconque. par les mots "comportement non défini" ou par l'omission d'un élément quelconque. définition explicite du comportement.

0voto

R.. Points 93718

Cela semble être une mise en œuvre valide lorsque &errno serait une violation de la contrainte :

struct __errno_struct {
    signed int __val:12;
} *__errno_location(void);

#define errno (__errno_location()->__val)

Donc je pense que la réponse est probablement non...

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X