32 votes

La lecture du négatif en non signé doit-elle échouer via std::cin (gcc, clang disagree) ?

Par exemple,

#include <iostream>

int main() {
  unsigned n{};
  std::cin >> n;
  std::cout << n << ' ' << (bool)std::cin << std::endl;
}

Lorsque l'entrée -1 , clang 6.0.0 sorties 0 0 tandis que gcc 7.2.0 sorties 4294967295 1 . Je me demande qui est le bon. Ou peut-être que les deux sont corrects car la norme ne le précise pas ? Par échec, je veux dire (bool)std::cin est évaluée fausse. clang 6.0.0 échoue l'entrée -0 aussi.


À partir de Clang 9.0.0 et GCC 9.2.0, les deux compilateurs, utilisant soit libstdc++ soit libc++ dans le cas de Clang, s'accordent sur le résultat du programme ci-dessus, indépendamment de la version C++ (>= C++11) utilisée, et impriment

4294967295 1

c'est-à-dire qu'ils fixent la valeur à ULLONG_MAX et ne pas mettre le failbit sur le flux.

4 votes

Que signifie "échouer" ici ? Vous ne pouvez pas obtenir -1, c'est sûr.

0 votes

J'ai essayé de répondre à cette question... c'est un terrier de lapin. Au minimum, pourriez-vous ajouter quel standard C++ vous compilez, il y a tellement de changements "jusqu'à", "après" etc. que ne pas le savoir va rendre presque impossible de donner une réponse définitive.

4 votes

@ArndtJonasson Je suppose que "échouer" signifie que failbit du flux d'entrée a été défini - et donc la deuxième sortie serait 0 plutôt que 1.

27voto

Holt Points 6689

Je pense que les deux sont erronés en C++17. 1 et que le résultat attendu devrait être :

4294967295 0

Bien que la valeur renvoyée soit correcte pour les dernières versions des deux compilateurs, je pense que l'option ios_­base::failbit devrait être fixé, mais je pense aussi qu'il y a une confusion sur la notion de champ à convertir dans la norme qui pourrait expliquer les comportements actuels.

La norme dit - [facet.num.get.virtuals#3.3] :

La séquence de caractères accumulée à l'étape 2 (le champ) est convertie en une valeur numérique par les règles de l'une des fonctions déclarées dans l'en-tête <cstdlib> :

  • Pour une valeur entière signée, la fonction strtoll .

  • Pour une valeur entière non signée, la fonction strtoull .

  • Pour une valeur à virgule flottante, la fonction strtold .

Donc nous nous rabattons sur std::strtoull qui doit retourner 2 ULLONG_MAX et ne pas mettre errno dans ce cas (ce que font les deux compilateurs).

Mais dans le même bloc ( accentuation est le mien) :

La valeur numérique à mémoriser peut être l'une des suivantes :

  • zéro, si la fonction de conversion ne convertit pas le champ entier.

  • la valeur représentable la plus positive (ou négative), si la champ à convertir à un type d'entier signé représente une valeur trop grande positive (ou négative) pour être représentée en val .

  • la valeur représentable la plus positive, si le champ à convertir à un type d'entier non signé représente une valeur qui ne peut pas être représentée en val .

  • la valeur convertie, sinon.

La valeur numérique résultante est stockée dans val . Si la fonction de conversion ne convertit pas le champ entier, ou si le champ représente une valeur en dehors de la plage des valeurs représentables, ios_­base::failbit est affecté à err .

Remarquez que toutes ces discussions sur le "champ à convertir" et non la valeur réelle renvoyée par std::strtoull . Le champ ici est en fait la séquence élargie de caractères '-', '1' .

Puisque le champ représente une valeur (-1) qui ne peut pas être représentée par un champ unsigned la valeur renvoyée devrait être UINT_MAX et le failbit doit être réglé sur std::cin .


<sup>1 </sup><code>clang</code> était en fait juste avant C++17 parce que le troisième point de la citation ci-dessus l'était :

- la valeur représentable la plus négative ou zéro pour un type d'entier non signé, si le champ représente une valeur négative trop grande pour être représentée en <code>val</code> . <code>ios_base::failbit</code> est affecté à <code>err</code> .

<sup>2 </sup><a href="http://en.cppreference.com/w/cpp/string/byte/strtoul" rel="noreferrer"><code>std::strtoull</code></a> renvoie à <code>ULLONG_MAX</code> car (merci @NathanOliver) - C/7.22.1.4.5 :

Si la séquence du sujet a la forme attendue et que la valeur de la base est zéro, la séquence de caractères commençant par le premier chiffre est interprétée comme une constante entière selon les règles du 6.4.4.1. [...] Si la séquence sujet commence par un signe moins, la valeur résultant de la conversion est niée (dans le type de retour).

0 votes

Je crois que ce que vous cherchez est la séquence de caractères commençant par le premier chiffre est interprétée comme une constante entière selon les règles du 6.4.4.1. con Si la séquence sujet commence par un signe moins, la valeur résultant de la conversion est niée (dans le type de retour). de l'article 7.22.1.4.5 de la norme C. Je pense que cela rendrait cette réponse "standard complète" :)

1 votes

@NathanOliver J'ai ajouté ceci mais je suis en train de réécrire la réponse parce que j'ai trouvé d'autres preuves dans la norme - je la supprime pendant que je la modifie. Merci quand même pour la citation !

0 votes

@NathanOliver J'ai mis à jour la réponse, je serais heureux d'avoir votre point de vue sur celle-ci.

3voto

darune Points 3515

La question porte sur les différences entre les implémentations de la bibliothèque. libc++ y libstdc++ - et pas tant sur les différences entre les compilateurs( clang , gcc ).

Référence cpp clarifie bien ces incohérences :

Le résultat de la conversion d'une chaîne de chiffres négatifs en une chaîne non signée. a été spécifié pour produire zéro jusqu'à c++17 bien que certains ont suivi le protocole de std::strtoull ce qui annule dans le type cible, ce qui donne ULLONG_MAX pour "-1", et ainsi produire la la plus grande valeur du type cible à la place. À partir de c++17 , strictement en suivant le site std::strtoull est le comportement correct.

Cela se résume à :

  • ULLONG_MAX ( 4294967295 ) est correcte à l'avenir, puisque c++17 (les deux compilateurs le font correctement maintenant)
  • Auparavant, il aurait dû être 0 avec une lecture stricte de la norme ( libc++ )
  • Certaines implémentations (notamment libstdc++ ) ont suivi std::strtoull à la place (ce qui est maintenant considéré comme le comportement correct).

L'échec de la mise en place et la raison pour laquelle elle a été mise en place, pourrait être une question plus intéressante (au moins du point de vue de la juriste linguistique perspective). Sur le site libc++ ( clang ) version 7, il fait maintenant la même chose que libstdc++ - cela semble suggérer qu'il a été choisi pour être identique à celui de l'avant (même si cela va à l'encontre de la lettre de la norme, selon laquelle il devrait être zéro avant). c++17 ) - mais jusqu'à présent je n'ai pas pu trouver de changelog ou de documentation pour ce changement.

Le bloc de texte intéressant est le suivant (en supposant qu'il s'agit d'une version pré-c++17) :

Si la fonction de conversion aboutit à une valeur négative trop grande pour que pour s'insérer dans le type de v, la valeur représentable la plus négative est stockée dans v, ou zéro pour les types d'entiers non signés .

D'après cela, la valeur est spécifiée comme étant 0 . De plus, il n'est indiqué nulle part que cela doit entraîner la mise en place du failbit.

0 votes

La réponse en haut de page dit la même chose à propos de l'ensemble des valeurs. Cependant, elle affirme en plus que le failbit doit être activé dans les deux cas, ce qui n'est pas ce que font les compilateurs actuels. La page cppreference ne mentionne pas explicitement s'il doit être défini. Est-ce que cela est censé signifier qu'il ne doit pas être activé dans les deux cas ? Dans ce cas, l'ancien comportement de Clang serait également erroné. Je pense que c'est ce qui doit être expliqué explicitement.

0 votes

@uneven_mark J'ai mis à jour ma réponse - il s'avère que clang a fait la même chose que gcc.

0 votes

C'est un problème de bibliothèque, donc au lieu de gcc contre clang, nous devrions parler de libstdc++ contre libc++. Godbolt utilise par défaut libstdc++ pour les deux. Vous devez spécifier -stdlib=libc++ alors vous observerez les résultats de l'OP. Je pense qu'il faudrait une explication plus détaillée de la raison pour laquelle le failbit ne devrait pas être activé, étant donné qu'il s'agit d'une question de droit du langage et que la réponse fortement votée est contradictoire pour C++17 et avant.

0voto

einpoklum Points 2893

La sémantique prévue de votre std::cin >> n sont décrites aquí (comme, apparemment, std::num_get::get() est appelé pour cette opération). Cette fonction a subi quelques modifications sémantiques, notamment en ce qui concerne le choix de placer 0 ou non, dans C++11 et à nouveau dans C++17.

Je ne suis pas tout à fait sûr, mais je pense que ces différences peuvent expliquer le comportement différent que vous observez.

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