2016-10-22 2 views
9

En C, je vois beaucoup de code qui ajoute ou affecte un littéral entier à une variable size_t.Ajout ou affectation d'un littéral entier à size_t

size_t foo = 1; 
foo += 1; 

Quelle conversion a lieu ici, et peut-il jamais arriver qu'un size_t est « mis à jour » à un int puis reconverti en un size_t? Serait-ce encore enveloppant si j'étais au maximum?

size_t foo = SIZE_MAX; 
foo += 1; 

Est-ce que ce comportement est défini? Il s'agit d'un type non signé size_t qui a un int signé ajouté (qui peut être un type plus grand?) Et le converti en size_t. Y a-t-il un risque de débordement 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 de code comme ça, mais je me demande si c'est nécessaire si les promotions entières sont gênantes.

C89 ne dit pas comment un size_t sera Ranked ou exactement ce qu'il est:

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

+1

Bonne question. Par expérience, 'size_t' est de la même taille ou plus grand que' int' sur toutes les implémentations que j'ai jamais utilisées. Si nous étions autorisés à appliquer le bon sens (ce que bien sûr nous ne sommes pas), nous noterions qu'un int est la taille "naturelle" pour un type entier sur une implémentation donnée, et donc cela n'aurait aucun sens pour que size_t soit plus petit que cette taille naturelle. – user3386109

+2

Notez que 'SIZE_MAX' n'a pas été défini par la norme C89. –

+2

S'agit-il de la norme actuelle ou C89? – 2501

Répondre

4

La norme actuelle de C permet une possibilité d'une mise en œuvre qui provoquerait un comportement non défini lors de l'exécution du code suivant, mais cette mise en œuvre n'existe pas, et ne sera probablement jamais:

size_t foo = SIZE_MAX; 
foo += 1; 

Le size_t type est aussi type non signé , avec une plage minimum: [0,65535].

Le type size_t peut être défini comme un synonyme du type unsigned short. Le type unsigned short 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), une représentation complémentaire à deux et une plage: [-65536,65535].

L'expression foo + = 1 équivaut à foo = foo + 1 (sauf que foo est évalué une seule fois mais cela n'a pas d'importance ici). La variable foo sera promue en utilisant des promotions entières . Il sera promu au type int parce que le type int peut représenter toutes les valeurs de type size_t et le rang de size_t, étant synonyme de short non signé, est inférieur au rang de int. Comme les valeurs maximales de size_t et int sont les mêmes, le calcul provoque un dépassement de capacité signé, ce qui provoque un comportement indéfini.

Ceci est valable pour la norme actuelle, et devrait également s'appliquer à C89 car il n'y a pas de restrictions plus strictes sur les types.

Solution pour éviter le débordement signé pour toute mise en œuvre imaginable est d'utiliser une constante entier non signé int:

foo += 1u; 

Dans ce cas, si foo a un rang inférieur int, il sera promu unsigned int en utilisant d'habitude conversions arithmétiques.


(cité de la norme ISO/IEC 9899/201x 7,19 définitions communes 2)

de size_t qui est le type entier non signé du résultat de l'opérateur sizeof;

(Cité de la norme ISO/IEC 9899/201x 7.20.3 Limites des autres types entiers 2)
limites de size_t
SIZE_MAX 65535

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

+0

Il y avait une erreur en fait, qui est maintenant corrigée. L'int devrait avoir une précision de 16 bits, car le bit de signe n'est pas compté comme un bit de précision. La largeur du type int est donc de 17 bits plus tous les bits de remplissage que cette implémentation abstraite contient. – 2501

+0

Une bonne réponse au scénario donné par OP. Je suis certain qu'aucune plate-forme aujourd'hui n'aurait 'size_t' comme' short non signé 'avec la même plage positive que 'int' comme vous l'avez également suggéré. Et j'espère qu'aucun futur appareil ne le fera non plus. – chux

+0

Si la valeur maximale de 'int' est plus grande que' size_t' et que 'size_t' est converti en int et que la valeur est ajoutée, et que le résultat ne dépasse pas INT_MAX', alors le comportement est défini lorsque ce résultat entier est reconverti en taille_t? Par exemple 'INT_MAX == 6' et' SIZE_MAX == 4' et 'size_t foo = 4; foo + = 2; 'alors est-il défini que lorsque ce résultat 6 est assigné à foo, il n'y a pas de comportement indéfini, et à la place il revient à 1? – newguy

3

Cela dépend, car size_t est un type intégral 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 sont effectivement size_t et des autres types impliqués dans l'expression.

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

size_t foo = 1; 
foo += 1; 

serait (sémantiquement) promouvoir foo à un int, ajouter 1, puis convertir le résultat à size_t pour le stockage au foo. (Je dis "sémantiquement", parce que c'est la signification du code selon la norme.) Un compilateur est libre d'appliquer la règle "comme si" - c'est-à-dire faire ce qu'il veut, pourvu qu'il produise le même effet net).

D'un autre côté, si size_t était équivalent à un long long unsigned (par exemple un type signé 64 bits), le même code favoriserait 1 être de type long long unsigned, l'ajouter à la valeur de foo, et stocker le résultat retour au foo. Dans les deux cas, le résultat net est le même sauf si un débordement se produit. Dans ce cas, il n'y a pas de débordement, étant donné qu'un int et size_t sont garantis capables de représenter les valeurs 1 et 2.

Si un dépassement survient (par exemple en ajoutant une valeur intégrale plus grande), alors le comportement peut varier. Le dépassement d'un type intégral signé (par exemple int) entraîne un comportement indéfini. Le dépassement d'un type intégral unsigned utilise l'arithmétique modulo.

En ce qui concerne le code

size_t foo = SIZE_MAX; 
foo += 1; 

il est possible de faire le même genre d'analyse.

Si size_t est équivalent à unsigned short, alors foo sera converti en int. Si int est équivalent à signed short, il ne peut pas représenter la valeur SIZE_MAX, de sorte que la conversion déborde et le résultat est un comportement indéfini. Si int est en mesure de représenter une plus grande plage que short int (par exemple, il est équivalent à long), puis la conversion de foo à int réussira, incrémenter cette valeur réussira, et le stockage de retour à size_t utilisera l'arithmétique modulo et produire le résultat de 0.

Si size_t est équivalent à unsigned long, alors la valeur 1 seront automatiquement converties en unsigned long, ajoutant à foo utilisera arithmétique modulo (à savoir produire un résultat égal à zéro), et qui seront stockées dans foo.

Il est possible de faire des analyses similaires en supposant que size_t est réellement d'autres types intégraux non signés.

Remarque: Dans les systèmes modernes, un size_t de même taille ou plus petit qu'un int est inhabituel. Toutefois, de tels systèmes ont existé (par exemple, les compilateurs Microsoft et Borland C qui ciblent MS-DOS 16 bits sur du matériel avec un processeur 80286). Des microprocesseurs 16 bits sont encore en production, principalement pour les systèmes embarqués à faible consommation d'énergie et à faible débit, et les compilateurs C qui les ciblent (par exemple le compilateur Keil C166 qui cible la famille de microprocesseurs Infeon XE166). [Note: Je n'ai jamais eu de raison d'utiliser le compilateur Keil mais, étant donné sa plate-forme cible, il ne serait pas surprenant qu'il prenne en charge un size_t 16 bits de même taille ou plus petit que le type natif int sur cette plate-forme ]

+1

"size_t était équivalent à un short non signé" et ... "aurait (sémantiquement) promu' foo' à un int ". Peut être. Dans un tel cas, plus _likely_, "int" est également 16 bits. et 'foo' devient un' unsigned'. Le "If size_t" est équivalent à un "short non signé" .. est impacté de la même manière. Pas d'UB. – chux

+0

DOS utilisait 16 bits 'int',' unsigned', 'short',' unsinged short' et 'size_t'. 'size_t' n'était pas _smaller_, pourtant de la même taille. En particulier, 'size_t' ne peut pas être 16 bits' signed int'. Le plus étroit qu'il peut être est un type non signé de 16 bits/ – chux

+0

'size_t' est un type * quasi-non signé * qui est défini sur les valeurs positives pour le type' int' (par exemple entre '0' et' (1U << 31) - 1' ('2147483648') pour x86/x86_64) –

2

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

La seule façon dont cela pourrait déborder est si INT_MAX == SIZE_MAX. Théoriquement, cela est possible, par ex. Int 16 bits et 15 bits size_t. (Ce dernier aurait probablement 1 bit de remplissage).

Plus probablement, SIZE_MAX sera inférieur à INT_MAX, de sorte que le code sera défini par l'implémentation en raison de la cession hors de portée. Normalement, la définition d'implémentation est la plus "évidente", les bits élevés sont ignorés, donc le résultat sera 0. Comme décision pratique, je ne recommanderais pas mangling votre code pour répondre à ces cas (15 bits size_t, ou définition d'implémentation non évidente) qui ne sont probablement jamais arrivé et ne le sera jamais. Au lieu de cela, vous pourriez faire des tests de compilation qui donneront une erreur si ces cas se produisent. Une affirmation au moment de la compilation que INT_MAX < SIZE_MAX serait pratique à ce jour et l'âge.

+0

size_t ne peut pas avoir une précision de 15 bits. S'il vous plaît voir ma réponse où j'explique comment c'est possible avec une configuration légèrement différente. – 2501

+0

@ 2501 - Basé sur quoi? Standard, ou les implémentations connues actuelles? – enhzflep

+0

@ 2501 bon point. Je vais devoir augmenter ma réponse d'un bit: P Bien qu'apparemment cette limite n'apparaisse pas en C89, ce que demande OP à propos de –