Le transtypage entre int et signé ne permet-il pas de conserver le modèle de bits exact de la variable en mémoire?

Je veux passer un entier signé 32 bits x travers un socket. Pour que le destinataire sache quel ordre d’octet il faut attendre, htonl(x) avant l’envoi. htonl attend cependant un uint32_t et je veux être sûr de ce qui se passe lorsque je lance mon int32_t en uint32_t .

 int32_t x = something; uint32_t u = (uint32_t) x; 

Est-ce toujours le cas que les octets dans x et u seront exactement les mêmes? Qu’en est-il de la reprise:

 uint32_t u = something; int32_t x = (int32_t) u; 

Je me rends compte que les valeurs négatives font appel à de grandes valeurs non signées, mais cela n’a pas d’importance, car je ne fais que répliquer de l’autre côté. Cependant, si la conversion dérange les octets réels, je ne peux pas être sûr que renvoyer en arrière renverra la même valeur.

En général, la conversion en C est spécifiée en termes de valeurs et non de modèles de bits – les premiers seront préservés (si possible), mais les derniers ne le seront pas nécessairement. Dans le cas de représentations de complément à deux sans remplissage – ce qui est obligatoire pour les types entier fixe-avec – cette distinction importe peu et la dissortingbution sera en réalité un noop.

Mais même si la conversion de signé en non signé aurait modifié le modèle de bits, la reconvertir aurait rétabli la valeur d’origine – avec la mise en garde que la conversion non signée en signature hors plage est définie par l’implémentation et peut générer un signal. débordement.

Pour une portabilité totale (qui sera probablement excessive), vous devrez utiliser le type de frappe au lieu de la conversion. Cela peut être fait de deux manières:

Par l’intermédiaire de lancers de pointeur, c.-à-d.

 uint32_t u = *(uint32_t*)&x; 

vous devez faire attention car cela peut enfreindre les règles de dactylographie en vigueur (mais convient pour les variantes signées / non signées de types entiers) ou via des unions, c’est-à-dire

 uint32_t u = ((union { int32_t i; uint32_t u; }){ .i = x }).u; 

qui peut également être utilisé pour convertir par exemple de double en uint64_t , ce que vous ne pouvez pas faire avec des conversions de pointeur si vous voulez éviter un comportement indéfini.

Les cast sont utilisés en C pour signifier à la fois “conversion de type” et “désambiguïsation de type”. Si vous avez quelque chose comme

 (float) 3 

Ensuite, il s’agit d’une conversion de type et les bits réels changent. Si tu le dis

 (float) 3.0 

c’est une homonymie de type.

En supposant une représentation du complément à 2 (voir les commentaires ci-dessous), lorsque vous convertissez un int en unsigned int , le modèle de bits n’est pas modifié, mais uniquement sa signification sémantique. si vous le repoussez, le résultat sera toujours correct. Cela tombe dans le cas de la désambiguïsation du type car aucun bit n’est modifié, seulement la façon dont l’ordinateur les interprète.

Notez qu’en théorie, le complément à 2 ne peut pas être utilisé, les signed unsigned et signed peuvent avoir des représentations très différentes, et le modèle de bits réel peut changer dans ce cas.

Cependant, à partir de C11 (le standard C actuel), vous avez réellement la garantie que sizeof(int) == sizeof(unsigned int) :

(§6.2.5 / 6) Pour chacun des types entiers signés, il existe un type entier non signé correspondant (mais différent) (désigné par le mot clé unsigned) qui utilise la même quantité de mémoire (informations de signe comsockets) et a le même exigences d’alignement […]

Je dirais qu’en pratique, vous pouvez supposer que c’est sécuritaire.

Cela devrait toujours être sûr, car les types intXX_t ont la garantie d’être au complément de deux s’ils existent:

7.20.1.1 Types d’entiers de largeur exacte Le nom typedef intN_t désigne un type d’entier signé de largeur N, sans bits de remplissage et une représentation en complément à deux. Ainsi, int8_t désigne un tel type entier signé avec une largeur d’exactement 8 bits.

Théoriquement, la conversion arrière de uint32_t en int32_t est définie par l’implémentation, comme pour toutes les conversions unsigned en signed . Mais je ne peux pas imaginer qu’une plate-forme ferait différemment de ce que vous attendez.

Si vous voulez être vraiment sûr de cela, vous pouvez toujours effectuer cette conversion manuellement. Il vous suffira de tester une valeur pour > INT32_MAX , puis de faire un peu de calcul. Même si vous le faites systématiquement, un compilateur décent devrait pouvoir le détecter et l’optimiser.