web-dev-qa-db-fra.com

Le cast entre int signé et non signé conserve-t-il un modèle binaire exact de variable en mémoire?

Je veux passer un entier signé 32 bits x via un socket. Pour que le destinataire sache à quel ordre d'octets s'attendre, j'appelle htonl(x) avant d'envoyer. htonl attend un uint32_t cependant, et je veux être sûr de ce qui se passe quand je jette mon int32_t à un uint32_t.

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

Est-il toujours vrai que les octets de x et u seront exactement les mêmes? Qu'en est-il du retour:

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

Je me rends compte que les valeurs négatives sont converties en grandes valeurs non signées, mais cela n'a pas d'importance puisque je rejoue simplement à l'autre extrémité. Cependant, si le cast gâche les octets réels, je ne peux pas être sûr que le retour en arrière retournera la même valeur.

27
Flash

En général, la conversion en C est spécifiée en termes de valeurs, pas de motifs binaires - les premiers seront préservés (si possible), mais les seconds pas nécessairement. Dans le cas de représentations de complément à deux sans remplissage - ce qui est obligatoire pour les types entiers fixes - cette distinction n'a pas d'importance et la distribution sera en effet un noop.

Mais même si la conversion de signée en non signée aurait changé le modèle de bits, la reconvertir aurait restauré la valeur d'origine - avec la mise en garde que la conversion hors signature non signée en signature est définie par l'implémentation et peut élever un signal sur débordement.

Pour une portabilité complète (qui sera probablement exagérée), vous devrez utiliser le type punning au lieu de la conversion. Cela peut se faire de deux manières:

Via des lanceurs de pointeurs

uint32_t u = *(uint32_t*)&x;

que vous devez faire attention car cela peut violer les règles de frappe efficaces (mais c'est bien pour les variantes signées/non signées des types entiers) ou via les unions, c'est-à-dire

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

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

26
Christoph

Les conversions sont utilisées en C pour signifier à la fois "conversion de type" et "désambiguïsation de type". Si vous avez quelque chose comme

(float) 3

Ensuite, c'est une conversion de type, et les bits réels changent. Si tu le dis

(float) 3.0

c'est une désambiguïsation de type.

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

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

Cependant, à partir de C11 (la norme C actuelle), vous êtes réellement assuré 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 stockage (y compris les informations de signe) et a la même exigences d'alignement [...]

Je dirais qu'en pratique, vous pouvez supposer que c'est sûr.

6
Filipe Gonçalves

Cela devrait toujours être sûr, car le intXX_t les types sont garantis en complément à deux si ils existent:

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

Théoriquement, la conversion inverse de uint32_t à int32_t est défini par l'implémentation, comme pour toutes les conversions unsigned à signed. Mais je ne peux pas vraiment imaginer qu'une plateforme ferait différemment ce que vous attendez.

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

2
Jens Gustedt