J'utilise std::memcpy
Pour contourner l'aliasing strict depuis longtemps.
Par exemple, inspecter un float
, comme this :
float f = ...;
uint32_t i;
static_assert(sizeof(f)==sizeof(i));
std::memcpy(&i, &f, sizeof(i));
// use i to extract f's sign, exponent & significand
Cependant, cette fois-ci, j'ai vérifié le standard, je n'ai rien trouvé qui puisse le valider. Tout ce que j'ai trouvé c'est this :
Pour tout objet (autre qu'un sous-objet potentiellement chevauchant) de type trivialement copiable T, que l'objet contienne ou non une valeur valide de type T, les octets sous-jacents ([intro.memory]) constituant l'objet peuvent être copiés dans un fichier. tableau de char, char non signé ou std :: byte ([cstddef.syn]).40 Si le contenu de ce tableau est recopié dans l'objet, celui-ci conservera ensuite sa valeur d'origine. [ Exemple:
#define N sizeof(T) char buf[N]; T obj; // obj initialized to its original value std::memcpy(buf, &obj, N); // between these two calls to std::memcpy, obj might be modified std::memcpy(&obj, buf, N); // at this point, each subobject of obj of scalar type holds its original value
- fin exemple]
et this :
Pour tout type trivialement copiable T, si deux pointeurs vers T pointent vers des objets T distincts obj1 et obj2, où ni obj1 ni obj2 ne sont des sous-objets susceptibles de se chevaucher, si les octets sous-jacents ([intro.memory]) constituant obj1 sont copiés dans obj2,41 obj2 doit par la suite avoir la même valeur que obj1. [ Exemple:
T* t1p; T* t2p; // provided that t2p points to an initialized object ... std::memcpy(t1p, t2p, sizeof(T)); // at this point, every subobject of trivially copyable type in *t1p contains // the same value as the corresponding subobject in *t2p
- fin exemple]
Ainsi, std::memcpy
Une float
to/from char[]
Est autorisée, et std::memcpy
Entre les mêmes types triviaux est également autorisée.
Mon premier exemple (et la réponse associée) sont-ils bien définis? Ou bien, la méthode correcte pour inspecter un float
consiste à std::memcpy
Dans un tampon unsigned char[]
, Et en utilisant shift
s et or
s pour créer un uint32_t
À partir de cela?
Remarque: consulter les garanties de std::memcpy
Peut ne pas répondre à cette question. Autant que je sache, je pourrais remplacer std::memcpy
Par une simple boucle de copie d'octets et la question sera la même.
La norme peut échouer à dire correctement que cela est autorisé, mais c'est probablement supposé l'être, et à ma connaissance, toutes les implémentations traiteront cela comme un comportement défini.
Afin de faciliter la copie dans un fichier char[N]
objet, les octets constituant l’objet f
sont accessibles comme s’il s’agissait d’un objet char[N]
. Je pense que cette partie n’est pas contestée.
Octets d'un char[N]
qui représente un uint32_t
La valeur peut être copiée dans un uint32_t
objet. Je pense que cette partie n’est pas non plus contestée.
Je crois également que le fait, par exemple, de fwrite
peut avoir écrit les octets dans une exécution du programme et fread
peut les avoir lus lors d'une autre exécution, voire même d'un autre programme.
En raison de cette dernière partie, je pense que la provenance des octets importe peu, tant qu’ils constituent une représentation valide de certains uint32_t
objet. Vous auriez p avoir parcouru toutes les valeurs float
, en utilisant memcmp
sur chacune jusqu'à ce que vous obteniez la représentation souhaitée, que vous saviez identique à celle du paramètre uint32_t
valeur que vous interprétez comme. Vous pourriez même l'avoir fait dans un autre programme, un programme que le compilateur n'a jamais vu. Cela aurait été valide.
Si, du point de vue de l'implémentation, votre code est impossible à distinguer du code valide sans ambiguïté, votre code doit être considéré comme valide.
Mon premier exemple (et la réponse associée) sont-ils bien définis?
Le comportement n'est pas indéfini (à moins que le type de cible ait des représentations d'interruption† qui ne sont pas partagés par le type de source), mais la valeur résultante de l’entier est définie par l’implémentation. Standard ne donne aucune garantie quant à la représentation des nombres en virgule flottante. Il est donc impossible d'extraire de manière portable la mantisse, etc. de l'entier. Cela dit, vous limiter à IEEE 754 à l'aide de systèmes ne vous limite pas beaucoup ces jours-ci.
Problèmes de portabilité:
Vous pouvez utiliser std::numeric_limits::is_iec559
pour vérifier si votre hypothèse concernant la représentation est correcte.
† Bien que, il semble que uint32_t
ne peut pas avoir de pièges (voir commentaires), vous n'avez donc pas à vous en soucier. En utilisant uint32_t
, vous avez déjà exclu la portabilité vers des systèmes ésotériques - les systèmes conformes à la norme ne sont pas tenus de définir cet alias.
Votre exemple est bien défini et ne supprime pas le crénelage strict. std::memcpy
indique clairement:
Copie
count
octets de l'objet pointé par src à l'objet pointé par dest. Les deux objets sont réinterprétés en tant que tableaux deunsigned char
.
La norme permet l’aliasing de tout type via un (signed/unsigned) char*
ou std::byte
et votre exemple ne montre donc pas UB. Si le nombre résultant est de n'importe quelle valeur, c'est une autre question.
use i to extract f's sign, exponent & significand
Cela n’est cependant pas garanti par la norme car la valeur de float
est définie par l’implémentation (dans le cas de IEEE 754, cela fonctionnera).