static_cast reinterpret_cast c++ language-lawyer c++17 undefined-behavior strict-aliasing

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 caracteres unsigned 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á).