Lire le double de l’endianisme de la plate-forme avec l’union et le décalage des bits est-il sûr?

Tous les exemples que j’ai vus de la lecture d’un doublé de finalité connue d’un tampon à la finalité de la plate-forme impliquent de détecter la finesse de la plate-forme actuelle et d’effectuer une permutation d’octets si nécessaire.

D’autre part, j’ai vu une autre façon de faire la même chose, à l’exception des entiers qui utilisent le transfert de bits ( un exemple ).

Cela m’a fait penser qu’il serait peut-être possible d’utiliser une union et la technique de bitshift pour lire les doublons (et les flottants) dans les tampons. Une implémentation de test rapide a semblé fonctionner (au moins avec clang sur x86_64):

#include  #include  #include  double read_double(char * buffer, bool le) { union { double d; uint64_t i; } data; data.i = 0; int off = le ? 0 : 7; int add = le ? 1 : -1; for (int i = 0; i < 8; i++) { data.i |= ((uint64_t)(buffer[off] & 0xFF) << (i * 8)); off += add; } return data.d; } int main() { char buffer_le[] = {0x6E, 0x86, 0x1B, 0xF0, 0xF9, 0x21, 0x09, 0x40}; printf("%f\n", read_double(buffer_le, true)); // 3.141590 char buffer_be[] = {0x40, 0x09, 0x21, 0xF9, 0xF0, 0x1B, 0x86, 0x6E}; printf("%f\n", read_double(buffer_be, false)); // 3.141590 return 0; } 

Ma question est cependant, est-ce un moyen sûr de le faire? Ou y at-il un comportement indéfini impliqué ici? Ou si cette méthode et la méthode d’échange d’octets impliquent un comportement indéfini, l’une est-elle plus sûre que l’autre?

Réinterpréter à travers une union

La construction d’une valeur uint64_t par décalage et octet ORing est bien entendu prise en charge par le standard C. (Il y a un risque lors du décalage en raison de la nécessité de s’assurer que la taille et le type de l’opérande gauche sont corrects afin d’éviter les problèmes de débordement et de largeur de décalage, mais le code de la question est correctement converti en uint64_t avant le décalage.) le code indique si la norme C permet de réinterpréter via une union. La réponse est oui.

C 6.5.2.3 3 dit:

Une expression postfixée suivie du . opérateur et un identifiant désigne un membre d’une structure ou d’un object d’union. La valeur est celle du membre nommé, 99)

et la note 99 dit:

Si le membre utilisé pour lire le contenu d’un object d’union n’est pas identique au dernier membre utilisé pour stocker une valeur dans l’object, la partie appropriée de la représentation de l’object de la valeur est réinterprétée en tant que représentation d’object dans le nouveau type décrit en 6.2.6 (un processus parfois appelé “type punning”)…

Cette réinterprétation repose bien sûr sur les représentations d’object utilisées dans la mise en œuvre du langage C. Notamment, le double doit utiliser le format attendu, correspondant aux octets lus dans le stream d’entrée.

Modification des octets d’un object

Modifier un object en modifiant ses octets (par exemple, en utilisant un pointeur sur un caractère unsigned char ) est autorisé par C. C 2018 6.5 7 dit:

La valeur stockée d’un object ne doit être accessible que par une expression lvalue qui possède l’un des types suivants: [liste de différents types] ou un type de caractère.

Bien qu’un des commentaires indique que vous pouvez «accéder» mais pas «modifier» les octets d’un object de cette manière (apparemment, interpréter «access» comme signifiant uniquement la lecture, pas l’écriture), C 2018 3.1 définit «access» comme suit:

pour lire ou modifier la valeur d’un object.

Ainsi, il est permis de lire ou d’écrire les octets d’un object via des types de caractères.

Lire le double de l’endianisme de la plate-forme avec l’union et le décalage des bits est-il sûr?

Ce genre de chose n’a de sens que lorsqu’il s’agit de données provenant de l’extérieur du programme (par exemple, des données d’un fichier ou d’un réseau); où vous avez un format ssortingct pour les données (défini dans la spécification du format de fichier ou celle du protocole réseau) qui peut ne rien avoir à voir avec le format utilisé par C, peut ne rien avoir à voir avec les utilisations de la CPU et peut ne pas être au format IEEE 754 non plus.

De l’autre côté, C n’offre aucune garantie. Pour un exemple simple, il est parfaitement légal pour le compilateur d’utiliser un format BCD pour float0x12345e78 = 1.2345 * 10**78 , même si le processeur lui-même prend en charge “IEEE 754”.

Le résultat est que vous avez “quel que soit le format spécifié par les spécifications” de l’extérieur du programme et que vous convertissez cela en un format différent “quel que soit le format du compilateur” pour une utilisation dans le programme; et chaque hypothèse que vous avez faite (y compris sizeof(double) ) est potentiellement fausse.