Diviser par zéro dans une expression constante
Mon compilateur de jouets se bloque si je divise par zéro dans une expression constante:
int x = 1 / 0;
Ce comportement est-il autorisé par les normes C et / ou c++?
5 réponses
La simple présence de 1 / 0
ne permet pas au compilateur de planter. Tout au plus, il est permis de supposer que l'expression ne sera jamais évaluée, et donc que l'exécution n'atteindra jamais la ligne donnée.
Si l'expression est garantie d'être évaluée, la norme n'impose aucune exigence au programme ou au compilateur. Alors le compilateur peut planter.
1 / 0 est seulement UB s'il est évalué.
La norme C11 donne un exemple explicite de 1 / 0
comportement défini lorsqu'il n'est pas Évalué:
Ainsi, dans l'initialisation suivante,
static int i = 2 || 1 / 0;
L'expression est une expression constante entière valide avec la valeur un.
Section 6.6, note 118.
1 / 0 n'est pas une expression constante.
La Section 6.6 de la norme C11, sous Contraintes, dit -
- Les expressions constantes ne doivent pas contenir d'affectation, d'incrément, de décrémentation, d'appel de fonction ou de virgule opérateurs, sauf lorsqu'ils sont contenus dans une sous-expression qui n'est pas évaluée.
- chaque expression constante doit être évaluée à une constante qui se situe dans la plage de valeurs représentables pour son type.
Depuis 1/0 n'évalue pas une constante dans la plage de valeurs représentable par un int, 1/0 n'est pas une expression constante. C'est une règle sur ce qui compte comme une expression constante, comme la règle de ne pas avoir d'affectations. Vous pouvez voir qu'au moins pour C++, Clang ne considère pas 1/0 comme une expression constante :
prog.cc:3:18: error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = 1/ 0 ;
^ ~~~~
Cela n'aurait pas beaucoup de sens qu'un 1 / 0 non évalué soit UB.
(x == 0) ? x : 1 / x
est parfaitement bien défini, même si x est 0 et que l'évaluation de 1 / x est UB. Si c'était le cas que (0 == 0) ? 0 : 1 / 0
étaient UB, ce serait un non-sens.
Oui, la division par Zéro est un comportement indéfini et ni la norme C ni C++ n'imposent d'exigences dans de tels cas. Bien que dans ce cas, je crois que vous devriez au moins émettre un diagnostic ( voir ci-dessous).
Avant de citer les normes, je dois noter que bien que cela puisse être un comportement conforme la qualité de l'implémentation est un problème différent, être simplement conforme n'est pas la même chose qu'être utile. Pour autant que je sache, gcc, clang, Visual Studio et Intel (selon l'équipe tpg2114) considère les erreurs du compilateur interne (ICEs) comme des bogues qui devraient être signalés. Il convient de noter que gcc et clang actuels produisent un avertissement pour ce cas apparemment indépendamment des drapeaux fournis. Dans le cas où les deux opérandes sont des littéraux/constantes, le cas que nous avons ici, il semble plutôt simple de détecter et de fournir un diagnostic pour cela. clang produit le diagnostic suivant pour ce cas (voir vivre):
warning: division by zero is undefined [-Wdivision-by-zero]
int x = 1 / 0 ;
^ ~
Du projet de section standard C11 6.5.5
opérateurs multiplicatifs (emphase mine):
Le résultat de l'opérateur / est le quotient de la division du premier opérande par le deuxième; [...] si la valeur de le deuxième opérande est zéro, le comportement est indéfini.
Et donc c'est un comportement indéfini.
Le projet de section standard C++ 5.6
[expr.mul] dit:
L'opérateur binaire / donne le quotient [...] si le second opérande de / ou % est égal à zéro, le comportement n'est pas défini [...]
Encore un comportement indéfini.
Le draft C++ standard et le draft C standard ont une définition similaire pour le comportement indéfini en disant:
[...]pour lesquelles la présente Norme internationale n'impose aucune exigence
L'expression n'impose aucune exigence semble trop autoriser comportement, y comprisdémons nasaux . Les deux ont une note similaire disant quelque chose dans le sens de:
Un comportement indéfini peut être attendu lorsque cette norme internationale omet toute définition explicite de comportement ou lorsqu'un programme utilise une construction erronée ou de données erronées. comportement indéfini admissible cela va d'ignorer complètement la situation avec des résultats imprévisibles , à se comporter pendant la traduction ou exécution du programme dans un document caractéristique de l'environnement (avec ou sans émission de un message de diagnostic), pour terminer une traduction ou une exécution (avec l'émission d'un message de diagnostic) .
Donc, bien que les notes ne soient pas normatives, il semble que si vous allez terminer pendant la traduction, vous devriez au moins émettre un diagnostic. Le terme terminant n'est pas défini, il est donc difficile de discuter de ce que cela permet. Je ne pense pas avoir vu un cas où clang et gcc ont une glace sans diagnostic.
le code doit-il être exécuté?
Si nous lisons le code qui ne sera jamais exécuté peut-il invoquer un comportement indéfini? nous pouvons voir au moins dans le cas de C il y a place au débat où le 1 / 0
doit être exécuté pour invoquer un comportement indéfini. Ce qui est pire dans le cas c++ La définition du comportement n'est pas présente donc une partie de l'analyse utilisée pour le cas C ne peut pas être utilisée pour le C++ cas.
Il semble que si le compilateur peut prouver que le code ne sera jamais exécuté, alors nous pouvons raisonner que ce serait comme si le programme n'avait pas de comportement indéfini mais je ne pense pas que ce soit prouvable, juste un comportement raisonnable.
Du point de vue C wg14 defect report 109 clarifie davantage cela. L'exemple de code suivant est donné:
int foo()
{
int i;
i = (p1 > p2); /* Must this be "successfully translated"? */
1/0; /* Must this be "successfully translated"? */
return 0;
}
Et la réponse comprenait:
En Outre, si chaque exécution possible d'un un programme donné entraînerait un comportement indéfini, le programme donné n'est pas strictement conforme.
une implémentation conforme ne doit pas manquer de traduire un programme strictement conforme simplement parce qu'une exécution possible de ce programme entraînerait un comportement indéfini. Étant donné que foo peut ne jamais être appelé, l'exemple donné doit être traduit avec succès par une implémentation conforme.
Donc dans le cas de C, sauf s'il peut être garanti que le code invoquant un comportement indéfini sera exécuté alors le compilateur doit traduire avec succès le programme.
C++ constexpr cas
Si x
était une variable constexpr:
constexpr int x = 1 / 0 ;
, Il serait mal formé et produit par gcc un avertissement et clang fait d'erreur (voir en direct):
error: constexpr variable 'x' must be initialized by a constant expression
constexpr int x = 1/ 0 ;
^ ~~~~
note: division by zero
constexpr int x = 1/ 0 ;
^
warning: division by zero is undefined [-Wdivision-by-zero]
constexpr int x = 1/ 0 ;
^ ~
Notant utilement que la division par zéro n'est pas définie .
Le projet de section standard C++ 5.19
expressions constantes [expr.const] dit:
Une expression conditionnelle e est une expression constante de base à moins que l'évaluation de e, suivant les règles de la abstract machine (1.9), évaluerait l'une des expressions suivantes
Et comprend la puce suivante:
Une opération qui aurait un comportement indéfini [Note: incluant, par exemple, un dépassement d'entier signé (Clause 5), certaines arithmétiques de pointeur (5.7), division par zéro (5.6) ou certaines opérations de décalage (5.8) - note de fin];
Est 1 / 0 une expression constante en C11 -
1 / 0
n'est pas une expression constante en C11, nous pouvons le voir à partir de la section 6.6
expressions constantes qui dit:
Chaque expression constante doit être évaluée à une constante qui est dans la plage de représentable valeurs pour son type.
Bien que, il permet:
Une implémentation peut accepter d'autres formes de constante expression.
, Donc 1 / 0
n'est pas une expression constante en C ou C++ mais cela ne change pas la réponse, car il n'est pas utilisé dans un contexte qui nécessite une expression constante. Je soupçonne que L'OP signifiait que 1 / 0
est disponible pour un pliage constant puisque les deux opérandes sont des littéraux, cela expliquerait également le crash.
De l'ébauche de norme C (N1570):
6.5.5 opérateurs multiplicatifs
...
- Le résultat de l'opérateur / est le quotient de la division du premier opérande par le deuxièmement; le résultat de l'opérateur % est le reste. Dans les deux opérations, si la valeur de le deuxième opérande est zéro, le comportement est indéfini.
Et à propos du comportement indéfini dans le chapitre 3. Termes, définitions et symboles:
3.4.3
- comportement indéfini
comportement, lors de l'utilisation d'une construction de programme non portable ou erronée ou de données erronées, pour lesquels la présente Norme internationale n'impose aucune exigence- NOTE le comportement indéfini possible va d'ignorer complètement la situation avec imprévisible résultats, à se comporter pendant la traduction ou l'exécution du programme d'une manière documentée caractéristique du de l'environnement (avec ou sans émission d'un message de diagnostic) , pour terminer une traduction ou d'exécution (avec l'émission d'un message de diagnostic).
Donc planter le compilateur est autorisé.
D'autres ont déjà mentionné le texte pertinent des normes, donc, je ne vais pas répéter cela.
La fonction d'évaluation de l'expression de mon compilateur C prend une expression en Notation polonaise inverse (tableau de valeurs (nombres et identificateurs) et opérateurs) et renvoie deux choses: un indicateur pour savoir si l'expression évalue ou non à une constante et la valeur si c'est une constante (0 sinon). Si le résultat est une constante, le RPN entier se réduit à cette constante. 1/0 n'est pas un expression constante car elle n'évalue pas à une valeur entière constante. Le RPN n'est pas réduit pour 1/0 et reste intact.
En C, Les variables statiques peuvent être initialisées avec des valeurs constantes uniquement. Ainsi, les erreurs du compilateur quand il voit qu'un initialiseur pour une variable statique n'est pas une constante. Les Variables de stockage automatique peuvent être initialisées avec des expressions non constantes. Dans ce cas, mon compilateur génère du code pour évaluer 1/0 (il a toujours le RPN pour cette expression!). Si ce le code est atteint au moment de l'exécution, UB se produit comme prescrit par les normes de langage. [Sur x86, cet UB prend la forme de l'exception CPU division by zero, tandis que sur MIPS, cet UB donne une valeur de quotient incorrecte(le CPU n'a pas d'exception division by zero).]
Mon compilateur supporte correctement les courts-circuits dans / / - expressions et & & - expressions. Ainsi, il évalue 1 || 1/0
comme 1 et {[1] } comme 0, que l'opérande de droite de l'opérateur logique soit ou non une constante. Le expression evaluating function supprime les opérandes de droite de ces opérateurs (avec les opérateurs) lorsqu'ils ne doivent pas être évalués et donc 1 || 1/0
se transforme en 1 != 0
(rappelons que les opérandes de & & et / / subissent une comparaison avec 0), ce qui donne 1 et 0 && 1/0
se transforme en 0 != 0
, ce qui donne 0.
Un autre cas à prendre en charge est INT_MIN / -1
et INT_MIN % -1
(idem pour les types entiers plus grands). Le quotient n'est pas représentable en tant qu'int signé (dans le cas des entiers signés du complément 2, ce qui est ce que nous avons dans tous les processeurs modernes) et donc C'est UB aussi (vous obtenez la même division par zéro exception sur x86 à l'exécution). Je gère cette affaire de la même manière. Cette expression ne peut pas initialiser une variable de stockage statique et elle est rejetée si elle n'est pas évaluée dans l'opérateur logique&&/||. Il peut initialiser une variable automatique, conduisant éventuellement à UB au moment de l'exécution.
J'émet également un avertissement quand une telle division est rencontrée.
Le comportement du compilateur n'est pas lié à la valeur de l'expression. Le compilateur ne devrait pas planter. Période.
J'imagine qu'une implémentation pédante, donnée par une expression comme celle-ci, compiler en code qui exécutera 1/0 au moment de l'exécution, mais je ne pense pas que serait considéré comme une bonne caractéristique.
Donc l'espace restant est que le compilateur devrait refuser de le compiler, et traitez - le comme une classe d'erreur de code source.