38 votes

L'utilisation de `-1` comme valeur maximale d'un entier non signé est-elle correcte ?

Existe-t-il un paragraphe de la norme c++ qui dit que l'utilisation de -1 pour cela est portable et la manière correcte ou la seule façon de faire correctement est d'utiliser des valeurs prédéfinies ?

J'ai eu une conversation avec mon collègue, qu'est-ce qui est mieux : utiliser -1 pour un nombre entier non signé maximum ou en utilisant une valeur de limits.h o std::numeric_limits ?

J'ai dit à mon collègue que l'utilisation de valeurs maximales prédéfinies à partir de limits.h o std::numeric_limits est la façon portable et propre de le faire, cependant, le collègue a objecté à l'idée de -1 étant aussi portable que les limites numériques, et plus encore, elle présente un avantage supplémentaire :

unsigned short i = -1; // unsigned short max

peut facilement être changé en n'importe quel autre type, tel que

unsigned long i = -1; // unsigned long max

lors de l'utilisation de la valeur prédéfinie de la limits.h ou le fichier d'en-tête std::numeric_limits nécessite également de le réécrire avec le type à gauche.

41 votes

Voir - y unsigned sur la même ligne est garanti pour faire lever quelques sourcils.

6 votes

Vous n'avez pas besoin de vous répéter si vous utilisez auto .

0 votes

@Ron je suis d'accord, mais c'est le hack que mon collègue dit à propos de, comme -1 sera converti en 0 - 1 qui utilisera le débordement et fixera le nombre maximum d'un type.

29voto

Eric Postpischil Points 36641

En ce qui concerne les conversions d'entiers, C 2011 [draft N1570] 6.3.1.3 2 dit

Sinon, si le nouveau type est non signé, la valeur est convertie en ajoutant ou en soustrayant de manière répétée une valeur de plus que la valeur maximale qui peut être représentée dans le nouveau type jusqu'à ce que la valeur soit dans la plage du nouveau type.

Ainsi, la conversion de -1 en un type de nombre entier non signé produit nécessairement la valeur maximale de ce type.

Il peut y avoir des problèmes avec l'utilisation de -1 dans divers contextes où il n'est pas immédiatement converti dans le type souhaité. S'il est immédiatement converti dans le type d'entier non signé souhaité, comme par affectation ou conversion explicite, le résultat est clair. Cependant, s'il fait partie d'une expression, son type est int et il se comporte comme un int jusqu'à ce qu'il soit converti. En revanche, UINT_MAX a le type unsigned int Il se comporte donc comme un unsigned int .

Como chux fait remarquer dans un commentaire, USHRT_MAX a effectivement un type de int Ainsi, même les limites nommées ne sont pas totalement à l'abri des problèmes de type.

0 votes

Concernant " UINT_MAX a le type unsigned int ..." ne répond pas au cas de l'OP de unsigned short . Cela suggère USHRT_MAX dont le type, AFIAK, n'est pas spécifié. (C : Peut-être pourrait-on int , unsigned , unsigned short , ...).

2 votes

@chux : C'est un bon point. Le type est spécifié ; ces macros sont des "expressions qui ont le même type que le ferait une expression qui est un objet du type correspondant converti selon les promotions d'entiers", mais cela signifie que USHRT_MAX peut être un int Il peut donc se comporter de manière inattendue si vous attendez un type non signé.

0 votes

Il pourrait être utile de faire référence à d'autres normes pour le prouver, mais pour mémoire, autant que je m'en souvienne, ce comportement était bien défini pour todo Les normes C au moment où nous écrivons ces lignes. Si je me souviens bien, C99 a le même texte. Le texte standard de C89 n'inclut pas cette phrase exacte mais elle est impliquée par les règles pour les promotions/conversions d'entiers, si je comprends bien. Et je pense que toutes les normes C++ ont été alignées sur ce comportement.

17voto

Picaud Vincent Points 3374

Ne pas utiliser la méthode standard ou ne pas montrer clairement l'intention. est souvent une mauvaise idée que nous payons plus tard

Je le suggère :

auto i = std::numeric_limits<unsigned int>::max(); 

ou @jamesdin en a suggéré une certainement meilleure, plus proche du C des habitudes :

unsigned int i = std::numeric_limits<decltype(i)>::max(); 

L'argument de votre collègue n'est pas recevable. Modification de int -> long int comme ci-dessous :

auto i = std::numeric_limits<unsigned long int>::max(); 
  • ne nécessite pas de travail supplémentaire par rapport à la -1 (grâce à l'utilisation de auto ).
  • la solution "-1" ne reflète pas directement notre intention, elle peut donc avoir des conséquences néfastes. Considérons cet extrait de code :

.

using index_t = unsigned int;

... now in another file (or far away from the previous line) ...

const index_t max_index = -1;

Premièrement, nous ne comprenons pas pourquoi max_index est -1 . Pire, si quelqu'un veut améliorer le code et définir des

 using index_t = ptrdiff_t;

_\=> alors la déclaration max_index=-1 n'est pas le max plus_ et vous obtenez un code bogué . Encore une fois, cela ne peut pas se produire avec quelque chose comme :

const index_t max_index = std::numeric_limits<index_t>::max();

CAVEAT : Néanmoins, il y a un avertissement lorsque l'on utilise std::numeric_limits . Cela n'a rien à voir avec les entiers, mais est lié à nombres à virgule flottante .

std::cout << "\ndouble lowest: "
          << std::numeric_limits<double>::lowest()
          << "\ndouble min   : "
          << std::numeric_limits<double>::min() << '\n';

des empreintes :

double lowest: -1.79769e+308    
double min   :  2.22507e-308  <-- maybe you expected -1.79769e+308 here!
  • min renvoie la plus petite valeur finie du type donné
  • lowest renvoie la plus petite valeur finie du type donné

Il est toujours intéressant de s'en souvenir, car cela peut être une source de bogues si nous n'y prêtons pas attention (utilisation de min au lieu de lowest ).

1 votes

2.22507e-308 ne ressemble pas à la "plus petite valeur finie du type donné". Je m'attendrais à 4.940656e-324 . C'est peut-être le plus petit normal de la valeur du type donné ? Réf : min renvoie la valeur minimale positive normalisée

0 votes

@chux j'ai coupé/copié Référence cpp . Mais je suis d'accord, c'est le plus petit nombre normalisé ici.

2 votes

Ou alternativement : unsigned int i = std::numeric_limits<decltype(i)>::max();

16voto

chux Points 13185

Est -1 correct d'utiliser comme valeur maximale d'un entier non signé ?

Oui, il est fonctionnellement correct lorsqu'il est utilisé comme une affectation/initialisation directe. Pourtant, il semble souvent douteux @Ron .

Constantes de limits.h o std::numeric_limits permettent une meilleure compréhension du code, mais nécessitent une maintenance si le type de i changement.


[Note] OP abandonne plus tard le C étiquette.

Pour ajouter une alternative à l'attribution d'une valeur maximale (disponible en C11) qui permet de réduire la maintenance du code :

Utilisez l'expression "aimé/ détesté". _Generic

#define info_max(X) _Generic((X), \
  long double: LDBL_MAX, \
  double: DBL_MAX, \
  float: FLT_MAX, \
  unsigned long long: ULLONG_MAX, \
  long long: LLONG_MAX, \
  unsigned long: ULONG_MAX, \
  long: LONG_MAX, \
  unsigned: UINT_MAX, \
  int: INT_MAX, \
  unsigned short: USHRT_MAX, \
  short: SHRT_MAX, \
  unsigned char: UCHAR_MAX, \
  signed char: SCHAR_MAX, \
  char: CHAR_MAX, \
  _Bool: 1, \
  default: 1/0 \
  )

int main() {
  ...
  some_basic_type i = info_max(i);
  ...
}

La macro ci-dessus info_max() ont des limitations concernant des types comme size_t , intmax_t etc. qui ne sont pas forcément énumérés dans la liste ci-dessus. Il existe des macros plus complexes qui peuvent s'en charger. L'idée ici est illustrative.

0 votes

Bon point concernant les constantes (autres que -1) qui se cassent lorsque la taille de l'int non signé change, à moins d'utiliser une macro comme la vôtre.

0 votes

El -1 nécessite également une maintenance si le type change en un type signé.

0 votes

@jpmc26 Vrai à propos de -1 et une modification de certains nombre entier signé qui est recouvert de info_max(i) . Pourtant, la question de l'OP porte principalement sur divers entier non signé et c'est là que -1 L'affectation réduit la maintenance. OMI, toute utilisation de some_unsigned_type x = -1; au moins, oblige à un commentaire explicatif. Je considère également que c'est un problème mineur car les paramètres du compilateur peuvent avertir du changement de signe et j'aime le code sans avertissement.

9voto

besc Points 1515

L'aspect technique a été couvert par d'autres réponses ; et bien que vous vous concentriez sur l'exactitude technique dans votre question, il est important de souligner à nouveau l'aspect propre, car c'est à mon avis le point le plus important.

El raison majeure pourquoi il s'agit d'un mauvaise idée d'utiliser cette astuce particulière : Le code est ambigu. Il n'est pas clair si quelqu'un a utilisé l'astuce des non signés intentionnellement ou s'il a fait une erreur et voulait en fait initialiser une variable signée à -1. Si votre collègue fait un commentaire après que vous ayez présenté cet argument, dites-lui d'arrêter de faire l'idiot :)

Je suis en fait un peu déconcerté que quelqu'un ait même envisagé ce tour sérieusement. Il existe une manière non ambiguë, intuitive et idiomatique de fixer une valeur à son maximum en C : les macros _MAX. Et il existe un moyen supplémentaire, tout aussi non ambigu, intuitif et idiomatique en C++, qui offre une plus grande sécurité de type : numeric_limits. L'astuce de -1 est un cas classique d'intelligence.

2 votes

Utilisation de _MAX les macros obligent à la maintenance du code i le changement de unsigned short a unsigned long . Le site some_unsigned_type i = -1; "trick" n'a pas besoin de cet entretien. Cela ne signifie pas que i = -1; est une excellente idée à usage général. Pourtant, elle peut avoir un sens dans certains cas qui ne sont pas stupides. Le contexte plus large est le besoin de faire un bon jugement - quelque chose que le PO n'a pas présenté.

3 votes

@chux Vrai à propos du contexte. Mais je suis convaincu que les cas légitimes d'utilisation de cette astuce sont rares. Et même dans ce cas, je ne voudrais pas le voir dans sa forme nue, mais enveloppé dans une macro ou en utilisant une constante nommée pour éliminer l'ambiguïté.

0 votes

Je suis d'accord sur tous les points de la commentaire .

8voto

rustyx Points 2722

La norme C++ dit ceci signed a unsigned les conversions ( [conv.integral]/2 ) :

Si le type de destination est non signé, la valeur résultante est le plus petit entier non signé congruent à l'entier source (modulo 2 n où n est le nombre de bits utilisés pour représenter le type non signé). [ Nota: Dans une représentation en complément à deux, cette conversion est conceptuelle et il n'y a pas de changement dans la configuration des bits (s'il n'y a pas de troncature). - note de fin ]

Alors oui, convertir -1 à un n Un entier non signé de -bit vous donnera toujours 2 n -1 quel que soit le type d'entier signé par lequel le -1 a commencé.

Que ce soit ou non unsigned x = -1; est plus ou moins lisible que unsigned x = UINT_MAX; mais c'est une autre discussion (il y a certainement une chance qu'il soulève un peu de des sourcils, peut-être même les vôtres lorsque vous regarderez votre propre code plus tard ;).

1 votes

Je pense que c'est ce langage qui définit le cas large unsigned = narrower signed comme effectuant une extension de signe. Pour obtenir une extension nulle, vous devez effectuer un cast vers un narrow unsigned avant d'assigner. Exemples pour x86-64 : godbolt.org/g/yHj8fC (qui utilise le complément à 2, ce qui démontre seulement que vous obtenez l'extension de signe (par exemple à tout-un, pas à 0x00000000FFFFFFFF )

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