Es std:: memcpy entre diferentes tipos trivialmente copiables comportamiento indefinido?


He estado usando std::memcpy para eludir el alias estricto durante mucho tiempo.

Por ejemplo, inspeccionando un float, como esto :

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 esto :

Para cualquier objeto (que no sea un subobjeto potencialmente superpuesto) de tipo T, independientemente de que el objeto tenga o no un valor válido de tipo T, el subyacente bytes ([intro.memoria]) que compone el objeto se puede copiar en una matriz de char, char sin signo, o std:: byte ([cstddef.syn]).40 Si el contenido de esa matriz se copia de nuevo en el objeto, el objeto, luego mantenga 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 esto :

Para cualquier tipo trivialmente copiable T, 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.memoria]) que componen obj1 se copian en obj2,41 obj2 tendrá posteriormente 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]

Entonces, std::memcpy ing a float a/desde char[]está permitido, y std::memcpy ing entre los mismos tipos triviales también está permitido.

¿Está bien definido mi primer ejemplo (y la respuesta vinculada)? O la forma correcta de inspeccionar un float es std::memcpy en un unsigned char[]buffer, y usando shifts y or s para construir un uint32_t a partir de él?


Nota: mirar las garantías de std::memcpy puede no responder a esta pregunta. Por lo que sé, podría reemplazar std::memcpy con un simple bucle de copia de bytes, y la pregunta será la misma.

Author: geza, 2018-07-12

3 answers

El estándar puede fallar en decir correctamente que esto está permitido, pero es casi seguro que lo está, y hasta donde yo sé, 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 componen el objeto f como si fueran un char[N]. Creo que esta parte no está en disputa.

Los bytes de un char[N] que representan un valor uint32_t pueden copiarse en un objeto uint32_t. Este 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 vinieron los bytes, siempre y cuando formen una representación válida de algún objeto uint32_t. Usted podría haber ciclado a través de todos los valores float, usando memcmp en cada uno hasta que obtuviera la representación que quería, que sabía que sería idéntica a la del valor uint32_t que está interpretando como. Usted podría incluso haber hecho eso en otro programa, un programa que el compilador nunca ha visto. Eso habría sido válido.

Si desde la perspectiva de la implementación, su código es indistinguible del código inequívocamente válido, su código debe ser visto como válido.

 21
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-12 08:56:55

¿Está bien definido mi primer ejemplo (y la respuesta vinculada)?

El comportamiento no es indefinido (a menos que el tipo de destino tenga representaciones trap que no son compartidos por el tipo de origen), pero el valor resultante del entero está definido por la implementación. El estándar no garantiza cómo se representan los números de coma flotante, por lo que no hay forma de extraer mantissa, etc. del entero de forma portátil, dicho esto, limitándose a IEEE 754 utilizando sistemas no te limita mucho en estos días.

Problemas de portabilidad:

  • IEEE 754 no está garantizado por C++
  • Byte peso del flotador no está garantizado el partido entero "endian".
  • (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 necesita preocuparse. Mediante uint32_t, ya ha descartado la portabilidad a sistemas esotéricos: los sistemas que se ajustan al estándar no requieren definir ese alias.

 18
Author: user2079303,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-12 13:54:18

Su ejemplo está bien definido y no rompe el alias estricto. std::memcpy establece claramente:

Copia count bytes del objeto apuntado por src al objeto señalado por dest. Ambos objetos se reinterpretan como matrices de unsigned char.

El estándar permite aliasing cualquier tipo a través de un (signed/unsigned) char* o std::byte y por lo tanto su ejemplo no exhibe UB. Si el entero resultante es de cualquier valor es otra pregunta sin embargo.


use i to extract f's sign, exponent & significand

Esto sin embargo, 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á).

 13
Author: Sombrero Chicken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-12 09:07:47