static_cast - reinterpret_cast c++
¿Es std:: memcpy entre los diferentes tipos de trivialmente copiables comportamiento indefinido? (3)
¿Mi primer ejemplo (y la respuesta vinculada) está bien definido?
El comportamiento no es indefinido (a menos que el tipo de destino tenga representaciones de captura † que no sean compartidas por el tipo de fuente), pero el valor resultante del entero esté definido por la implementación. Standard no ofrece ninguna garantía sobre cómo se representan los números de punto flotante, por lo que no hay forma de extraer la mantisa, etc., del número entero de forma portátil, es decir, limitarse a IEEE 754 utilizando sistemas no lo limita mucho en estos días.
Problemas para la portabilidad:
- IEEE 754 no está garantizado por C ++
- No se garantiza que el byian endianness de float coincida con el endianness entero.
- (Sistemas con representaciones de trampas † ).
Puede usar
std::numeric_limits::is_iec559
para verificar si su suposición sobre la representación es correcta.
†
Aunque, parece que
uint32_t
no puede tener trampas (ver comentarios), por lo que no debe preocuparse.
Al utilizar
uint32_t
, ya ha descartado la portabilidad a sistemas esotéricos; no se requiere que los sistemas conformes a los estándares definan ese alias.
He estado usando
std::memcpy
para eludir
un alias estricto
durante mucho tiempo.
Por ejemplo, inspeccionando un
float
, como
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
Sin embargo, esta vez, he comprobado el estándar, no he encontrado nada que valide esto. Todo lo que encontré es this :
Para cualquier objeto (que no sea un subobjeto potencialmente superpuesto) de tipo T que se pueda copiar de forma trivial, ya sea que el objeto tenga un valor válido de tipo T, los bytes subyacentes ([intro.memory]) que componen el objeto se pueden copiar en un matriz de char, unsigned char o std :: byte ([cstddef.syn]). 40 Si el contenido de esa matriz se copia de nuevo en el objeto, el objeto mantendrá su valor original. [Ejemplo:
#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
- ejemplo final]
y this :
Para cualquier tipo T trivialmente copiable, si dos punteros a T apuntan a objetos T distintos obj1 y obj2, donde ni obj1 ni obj2 es un subobjeto potencialmente superpuesto, si los bytes subyacentes ([intro.memory]) que forman el obj1 se copian en obj2, 41 obj2 posteriormente tendrá el mismo valor que obj1. [Ejemplo:
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
- ejemplo final]
Por lo tanto,
std::memcpy
ing a
float
to / from
char[]
está permitido, y
std::memcpy
ing entre los mismos tipos triviales también está permitido.
¿Mi primer ejemplo (y la respuesta vinculada) está bien definido?
¿O la forma correcta de inspeccionar un
float
es
std::memcpy
it en un búfer
unsigned char[]
, y usar los
shift
s y
or
s para construir un
uint32_t
partir de él?
Nota: mirar las garantías de
std::memcpy
puede que no responda a esta pregunta.
Que yo sepa, podría reemplazar
std::memcpy
con un simple bucle de copia de bytes, y la pregunta será la misma.
El estándar puede no decir correctamente que esto está permitido, pero es casi seguro que se supone que lo es, y que yo sepa, todas las implementaciones tratarán esto como un comportamiento definido.
Para facilitar la copia en un objeto
char[N]
real, se puede acceder a los bytes que forman el objeto
f
como si fueran un
char[N]
.
Esta parte, creo, no está en disputa.
Los bytes de un
char[N]
que representan un valor
uint32_t
se pueden copiar en un objeto
uint32_t
.
Esta parte, creo, tampoco está en disputa.
Igualmente indiscutible, creo, es que, por ejemplo,
fwrite
puede haber escrito los bytes en una ejecución del programa, y
fread
puede haberlos leído en otra ejecución, o incluso en otro programa por completo.
Debido a esa última parte, creo que no importa de dónde provengan los bytes, siempre que formen una representación válida de algún objeto
uint32_t
.
Podría
haber pasado por todos los valores
float
, utilizando
memcmp
en cada uno hasta obtener la representación que quería, que sabía que sería idéntica a la del valor
uint32_t
como lo interpreta.
Incluso
podrías
haber hecho eso en otro programa, un programa que el compilador nunca ha visto.
Eso hubiera sido válido.
Si desde la perspectiva de la implementación, su código es indistinguible del código válido de manera no ambigua, su código debe ser visto como válido.
Su ejemplo está bien definido y no rompe el alias estricto.
std::memcpy
establece claramente:
Las copias
count
bytes del objeto apuntado por src al objeto apuntado por dest. Ambos objetos se reinterpretan como matrices de caracteresunsigned char
.
El estándar permite crear alias de cualquier tipo a través de un
(signed/unsigned) char*
o
std::byte
y, por lo tanto, su ejemplo no presenta UB.
Sin embargo, si el entero resultante tiene algún valor, hay otra pregunta.
use i to extract f''s sign, exponent & significand
Sin embargo, esto no está garantizado por el estándar, ya que el valor de un
float
está definido por la implementación (en el caso de IEEE 754 funcionará).