Ajouter ou assigner un littéral entier à un size_t

Dans CI, vous voyez beaucoup de code qui ajoute ou atsortingbue un littéral entier à une variable size_t .

 size_t foo = 1; foo += 1; 

Quelle conversion a lieu ici, et peut-il arriver qu’un size_t soit “mis à niveau” vers un int puis reconverti en un size_t ? Est-ce que ça restrait quand même si j’étais au maximum?

 size_t foo = SIZE_MAX; foo += 1; 

Ce comportement est-il défini? C’est un type non signé size_t auquel on ajoute un int signé (qui peut être un type plus gros?) Et qui est reconverti en un size_t . Existe-t-il un risque de dépassement d’entier signé?

Serait-il logique d’écrire quelque chose comme foo + bar + (size_t)1 au lieu de foo + bar + 1 ? Je ne vois jamais un code comme celui-là, mais je me demande si c’est nécessaire si les promotions sur les entiers posent problème.

C89 ne dit pas comment un size_t sera classé ou ce que c’est exactement:

La valeur du résultat est définie par l’implémentation et son type (un type intégral non signé) est size_t défini dans l’en-tête.

La norme C actuelle offre la possibilité d’une implémentation qui entraînerait un comportement indéfini lors de l’exécution du code suivant. Toutefois, une telle implémentation n’existe pas et ne le fera probablement jamais:

 size_t foo = SIZE_MAX; foo += 1; 

Le type size_t correspond au type 1 non signé, avec une plage minimale: 2 [0,65535].

Le type size_t peut être défini comme un synonyme du type unsigned short. Le type short non signé peut être défini avec 16 bits de précision, avec la plage: [0,65535]. Dans ce cas, la valeur de SIZE_MAX est 65535.

Le type int peut être défini avec 16 bits de précision (plus un bit de signe), la représentation du complément à deux et la plage: [-65536,65535].

L’expression foo + = 1 équivaut à foo = foo + 1 (sauf que foo n’est évalué qu’une seule fois mais que cela n’a aucune pertinence ici). La variable foo sera promue à l’aide de promotions entières 3 . Il sera promu au type int car le type int peut représenter toutes les valeurs de type size_t et le rang de size_t, synonyme de unsigned short, est inférieur au rang d’int. Étant donné que les valeurs maximales de size_t et int sont identiques, le calcul provoque un débordement de signature, entraînant un comportement indéfini.

Ceci est valable pour la norme actuelle, mais également pour C89, car il n’y a pas de ressortingctions plus ssortingctes sur les types.

La solution pour éviter le débordement signé pour toute implémentation imaginable consiste à utiliser une constante entière non signée:

 foo += 1u; 

Dans ce cas, si foo a un rang inférieur à int, il sera promu à unsigned int en utilisant les conversions arithmétiques habituelles.


1 (Cité extrait de l’ISO / CEI 9899 / 201x 7.19 Définitions communes 2)
taille_t
qui est le type entier non signé du résultat de l’opérateur sizeof;

2 (Cité de ISO / IEC 9899 / 201x 7.20.3 Limites d’autres types de nombres entiers 2)
limite de size_t
SIZE_MAX 65535

3 (Cité de ISO / IEC 9899 / 201x 6.3.1.1 Booléen, caractères et nombres entiers 2)
Les éléments suivants peuvent être utilisés dans une expression partout où un int ou un unsigned int peut être utilisé:
Un object ou une expression de type entier (autre que int ou unsigned int) dont le rang de conversion d’entier est inférieur ou égal au rang d’int et de non signé.
Si un int peut représenter toutes les valeurs du type d’origine (comme le limite la largeur, pour un champ de bits), la valeur est convertie en un entier; sinon, il est converti en un unsigned int. Celles-ci sont appelées promotions entières. Tous les autres types ne sont pas modifiés par les promotions entières.

Cela dépend, puisque size_t est un type entier non signé défini par l’implémentation.

Les opérations impliquant un size_t introduiront donc des promotions, mais celles-ci dépendent de ce que size_t est réellement et des autres types impliqués dans l’expression.

Si size_t était équivalent à un unsigned short (par exemple un type 16 bits), alors

 size_t foo = 1; foo += 1; 

(sémantiquement) favoriserait foo en un int , appendait 1 , puis reconvertirait le résultat en size_t pour le stocker dans foo . (Je dis “sémantiquement”, car tel est le sens du code selon la norme. Un compilateur est libre d’appliquer la règle “comme si” – c.-à-d. Faire ce qu’il veut, à condition qu’il produise le même effet net).

D’autre part, si size_t était équivalent à un long long unsigned (par exemple, un type signé de 64 bits), alors le même code ferait en sorte que 1 soit du type long long unsigned , l’ajoute à la valeur de foo et stocke le résultat. retour dans foo .

Dans les deux cas, le résultat net est le même sauf en cas de débordement. Dans ce cas, il n’y a pas de dépassement de size_t , puisqu’un int et size_t sont garantis capables de représenter les valeurs 1 et 2 .

Si un débordement se produit (par exemple, en ajoutant une valeur intégrale plus grande), le comportement peut varier. Un débordement d’un type intégral signé (par exemple, int ) entraîne un comportement non défini. Le débordement d’un type intégral unsigned utilise l’arithmétique modulo.

Quant au code

 size_t foo = SIZE_MAX; foo += 1; 

il est possible de faire le même type d’parsing.

Si size_t est équivalent à un unsigned short alors foo serait converti en int . Si int est équivalent à un signed short , il ne peut pas représenter la valeur de SIZE_MAX , la conversion débordera donc et le résultat sera un comportement indéfini. Si int est capable de représenter une plage plus grande que short int (par exemple, cela équivaut à long ), alors la conversion de foo en int réussira, incrémentant cette valeur et le stockage dans size_t utilisera modulo arithmetic et produira le résultat. de 0 .

Si size_t est équivalent à unsigned long , la valeur 1 sera convertie en unsigned long , en ajoutant que to foo utilisera l’arithmétique modulo (en d’autres termes, produira un résultat égal à zéro), qui sera stockée dans foo .

Il est possible d’effectuer des parsings similaires en supposant que size_t est en réalité un autre type d’intégrale non signée.

Remarque: Dans les systèmes modernes, une taille égale ou inférieure à un int est inhabituelle. Cependant, de tels systèmes existaient déjà (par exemple, des compilateurs Microsoft et Borland C ciblant MS-DOS 16 bits sur du matériel avec un processeur 80286). Il existe également des microprocesseurs 16 bits toujours en production, principalement utilisés dans des systèmes embarqués consommant moins d’énergie et nécessitant peu de débit, et des compilateurs C qui les ciblent (par exemple, le compilateur Keil C166 qui cible la famille de microprocesseurs Infeon XE166). [Remarque: je n’ai jamais eu de raison d’utiliser le compilateur Keil mais, compte tenu de sa plate-forme cible, il ne serait pas étonnant qu’il size_t charge une taille de 16 bits de même taille ou plus petite que le type int natif de cette plate-forme. ].

foo += 1 signifie foo = foo + 1 . Si size_t est plus étroit que int ( size_t dit, int peut représenter toutes les valeurs de size_t ), foo est alors promu en int dans l’expression foo + 1 .

Le seul moyen de INT_MAX débordement est si INT_MAX == SIZE_MAX . Théoriquement, c’est possible, par exemple int. 16 bits et size_t 15 bits. (Ce dernier aurait probablement 1 bit de remplissage).

Plus probablement, SIZE_MAX sera inférieur à INT_MAX . Le code sera donc défini par l’ implémentation en raison d’une affectation hors plage. Normalement, la définition de l’implémentation est “évidente”, les bits les plus élevés étant ignorés, le résultat est 0 .

En tant que décision pratique, je ne recommanderais pas de modifier votre code pour tenir compte de ces cas (définition de la mise en œuvre de 15 bits ou définition non évidente) qui ne se sont probablement jamais produits et ne se produiront jamais. Au lieu de cela, vous pouvez effectuer des tests à la compilation qui généreront une erreur si de tels cas se produisent. Une affirmation à la compilation selon laquelle INT_MAX < SIZE_MAX serait pratique de nos jours.