A quel stade de la compilation les identifiants réservés sont-ils réservés?

Juste un peu de curiosité au travail, ici. Tout en travaillant sur quelque chose de dangereux, j’ai réfléchi à la mise en œuvre de divers compilateurs et de leurs bibliothèques standard associées. Voici la progression de mes pensées:

  1. Certaines classes d’identifiants sont réservées à une implémentation en C ++ et C.

  2. Un compilateur doit effectuer les étapes de compilation (prétraitement, compilation, liaison) comme si elles avaient été effectuées en séquence.

  3. Le préprocesseur C n’a pas connaissance du statut réservé aux identificateurs.

  4. Par conséquent, un programme peut utiliser des identifiants réservés si et seulement si :

    1. Les identifiants réservés utilisés sont tous des symboles de préprocesseur.

    2. Le résultat du prétraitement n’inclut pas les identificateurs réservés.

    3. Les identifiants ne sont pas en conflit avec les symboles prédéfinis par le compilateur ( GNUC et al.)

Est-ce valide? Je suis incertain sur les points 3 et 4.3. De plus, y a-t-il un moyen de le tester?

(Les commentaires sur la question expliquent que nous parlons d’ identificateurs réservés au sens de la section 7.1 de C99, c’est-à-dire d’identifiants correspondant à /^_[A-Z_]/ n’importe où, /^_/ dans la scope du fichier, /^str[az]/ avec un lien externe, etc. Alors, voici au moins une partie de ce que vous demandez …)

Ils ne sont pas réservés en ce sens que le compilateur (à quelque phase que ce soit) est censé diagnostiquer leur mauvaise utilisation. Ils sont plutôt réservés en ce que si vous êtes assez idiot pour (mal) les utiliser vous-même, vous ne pourrez pas vous plaindre si votre programme cesse de fonctionner ou arrête de comstackr à une date ultérieure.

Nous avons tous vu ce qui se passe lorsque des personnes ne disposant que d’une quantité de connaissances dangereuse consultent les en-têtes du système, puis écrivent leurs propres gardes d’en-tête:

 #ifndef _MYHEADER_H #define _MYHEADER_H // ... #endif 

Ils invoquent un comportement indéfini, mais rien ne le diagnostique comme ” erreur: identifiant réservé utilisé par le code de l’utilisateur final “. Au lieu de cela la plupart du temps ils ont de la chance et tout va bien; mais parfois, ils entrent en collision avec un identifiant d’intérêt pour la mise en œuvre, et des choses confuses se produisent.

De même, j’ai souvent une fonction visible de l’extérieur, nommée ssortingp() ou presque:

 char *ssortingp(char *s) { // remove leading whitespace } 

En lisant les paragraphes 7.1.3, 7.26 et 7.26.11 de C99, cela invoque un comportement indéfini. Cependant, j’ai décidé de ne pas m’en soucier. L’identifiant n’est pas réservé car il est prévu que quelque chose de grave se produise aujourd’hui, mais parce que la norme se réserve le droit d’inventer une nouvelle routine standard str-ip() dans une future révision. Et j’ai décidé que je pense que ssortingngip , quel qu’il soit, est un nom improbable pour une opération de chaîne à append dans le futur – donc, dans le cas improbable qui se produit, je franchirai ce pont dès que j’aurai à cela. Techniquement, j’appelle un comportement indéfini, mais je ne m’attends pas à être mordu.

Enfin, un contre-exemple à votre point 4:

 #include  #define memcpy(d,s,n) (my_crazy_function((n), (s))) void foo(char *a, char *b) { memcpy(a, b, 5); // intends to invoke my_crazy_function memmove(a, b, 5); // standard behaviour expected } 

Ceci est conforme à vos 4.1, 4.2, 4.3 (si je comprends votre intention sur ce dernier). Cependant, si memmove est également implémenté sous forme de macro (via 7.1.4 / 1) écrite en termes de memcpy , vous aurez alors des problèmes.

L’histoire est plus compliquée que cela, je pense, au moins pour le si et seulement si . Ce dont je me souviens de C99:

Par exemple, 3. est faux, le jeton defined est réservé même pendant la phase de pré- __LINE__ , et les pseudo-macros telles que __LINE__ , __func__ etc. ne peuvent pas non plus être redéfinies.

Ensuite, la réservation d’identifiants dépend de la scope.

  • Certains identifiants sont explicitement réservés aux symboles externes, par exemple setjmp .
  • Les identifiants commençant par un trait de soulignement, puis un autre trait de soulignement ou une lettre majuscule sont réservés partout en C. Vous ne devez jamais les toucher, même avec le pré-processeur.
  • Les identifiants commençant par un trait de soulignement, puis une lettre minuscule, sont interdits dans la scope du fichier car ils peuvent faire référence à des symboles externes. Ils peuvent être utilisés librement dans la scope de la fonction.

4.2 n’est pas complètement correct non plus. Tout d’abord, il suffit d’ un comportement indéfini (c’est-à-dire très diabolique) pour définir une macro ayant pour nom un mot clé dans les conditions suivantes:

Un en-tête standard est inclus tandis qu’une macro est définie avec le même nom qu’un mot clé (7.1.2).

Ensuite, une macro qui contient son propre nom dans son développement est “sûre”, car il est garanti que le développement ne sera pas récursif. Quelque chose comme ce qui suit serait valide, bien que non recommandé:

 #define if(...) \ for(int _i = 0; _i < 1; ++_i) \ for(int _cond = (__VA_ARGS__); \ _i < 1; \ printf("line %d val %d\n", __LINE__, _cond), \ ++_i) \ if(_cond) 

(En passant, personne n’utilise cette macro, elle comstack et fait à peu près à quoi elle ressemble, mais elle a des casses qui laissent exploser.)

Le préprocesseur C n’a pas connaissance du statut réservé aux identificateurs.

Je ne suis pas sûr de ce que vous entendez par “conscient”, mais je ne pense pas que vous puissiez nécessairement supposer ceci – 7.1.3 dit

Tous les identificateurs commençant par un trait de soulignement, soit un signe de soulignement en majuscule ou un autre, sont toujours réservés pour toute utilisation.

L’implémentation du préprocesseur (ou du compilateur) peut utiliser ces identifiants réservés aux fins qui lui conviennent. Elle n’a pas besoin de vous avertir en cas d’utilisation abusive de ces identificateurs.

Je suggérerais “un programme peut utiliser des identifiants réservés si et seulement si” le standard (par exemple l’ensemble de macros prédéfinies) ou l’implémentation le dit dans sa documentation.

Bien sûr, je pense que vous allez vous en tirer en utilisant des identifiants qui sont réservés dans de nombreux cas – les implémentations ne vous dérangent pas pour vous causer des problèmes. Un très grand nombre de codes utilisent des noms réservés, et je suppose que les implémentations préfèrent ne pas le déchiffrer sans raison suffisante. Cependant, il serait préférable que vous évitiez complètement cet espace de noms si vous n’implémentez pas de chaîne d’outils de compilation.

Des identificateurs tels que _UNDERSCORE_CAP et double__underscore sont réservés pour une utilisation par la mise en œuvre à sa guise. Ce n’est pas un problème si l’implémentation les utilise, par exemple en ayant un identifiant _File ou une macro dans , c’est le but de la réservation. C’est un problème potentiel si l’utilisateur en utilise un.

Par conséquent, afin de diagnostiquer cela, le compilateur devrait garder une trace de l’origine des identifiants. Il ne serait pas suffisant de simplement vérifier le code qui ne se trouve pas dans , car ceux-ci peuvent définir des macros pouvant être utilisées et susceptibles de s’étendre à quelque chose utilisant des mots réservés à l’implémentation. Par exemple, isupper peut être défini dans tant que

 #define isupper(x) (_UPPER_BIT & _CHAR_TRAITS[x]) 

ou quelque chose comme ça. (Cela fait longtemps que je n’ai pas vu la définition sur laquelle je me suis basé.)

Par conséquent, pour garder une trace de cela, le pré-processeur devrait conserver des enregistrements sur lesquels les macros provenaient de là, entre autres choses. Un suivi qui compliquerait considérablement le préprocesseur, à quoi les rédacteurs du compilateur semblent penser qu’il n’ya aucun gain correspondant.

Si vous demandez si vous pouvez #define if while rendant votre code illisible, alors oui. C’était une pratique courante dans la compétition C occultée. Cela irait effectivement contre votre 4.2, cependant.

Pour des choses comme GNUC, celles-ci sont prédéfinies, mais vous pouvez généralement les redéfinir et les annuler. Ce n’est pas vraiment une bonne idée de le faire, mais vous pouvez le faire. Plus intéressant serait de redéfinir ou dé- __LINE__ symboles __LINE__ , __FILE__ et du préprocesseur comme celui-ci (b / c, ils changent automatiquement).