gueza
he estado usando std::memcpy
evitar alias estricto por mucho tiempo.
Por ejemplo, inspeccionar 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, revisé el estándar, no encontré nada que valide esto. Todo lo que encontré es este:
Para cualquier objeto (que no sea un subobjeto potencialmente superpuesto) de tipo T copiable trivialmente, ya sea que el objeto tenga o no un valor válido de tipo T, los bytes subyacentes ([intro.memory]) que forman el objeto se pueden copiar en una matriz de char, char sin firmar o std::byte ([cstddef.syn]).40 Si el contenido de esa matriz se vuelve a copiar en el objeto, el objeto conservará posteriormente su valor original.[Ejemplo:[ Example:
#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 del ejemplo]
y este:
Para cualquier tipo T copiable trivialmente, si dos punteros a T apuntan a objetos T distintos obj1 y obj2, donde ni obj1 ni obj2 son un subobjeto potencialmente superpuesto, si los bytes subyacentes ([intro.memory]) que forman obj1 se copian en obj2,41 obj2 tendrá posteriormente el mismo valor que obj1. [ Example:
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
— end example ]
Asi que, std::memcpy
ing un float
hacia/desde char[]
está permitido, y std::memcpy
También se permite intercambiar entre los mismos tipos triviales.
¿Está bien definido mi primer ejemplo (y la respuesta vinculada)? O la forma correcta de inspeccionar un float
Es para std::memcpy
en un unsigned char[]
tampón y usando shift
arena or
s para construir un uint32_t
¿de eso?
Nota: mirando std::memcpy
Las garantías de pueden no responder a esta pregunta. Hasta donde yo sé, podría reemplazar std::memcpy
con un simple ciclo de copia de bytes, y la pregunta será la misma.
Es posible que el estándar no diga correctamente que esto está permitido, pero es casi seguro que se supone que lo está y, según mi leal saber y entender, todas las implementaciones tratarán esto como un comportamiento definido.
Para facilitar la copia en un formato real char[N]
objeto, los bytes que componen el f
Se puede acceder al objeto como si fuera un char[N]
. Esta parte, creo, no está en disputa.
bytes de un char[N]
que representan un uint32_t
el valor se puede copiar en un uint32_t
objeto. 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 vuelto a leer 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 algunos uint32_t
objeto. Tú pudo han recorrido todo float
valores, usando memcmp
en cada uno hasta que obtuviste la representación que querías, que sabías que sería idéntica a la del uint32_t
valor que está interpretando como. Tú pudo incluso 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 de un código inequívocamente válido, su código debe verse como válido.
-
Separar los pasos individuales involucrados y calificar cada uno como indiscutible es aclarar las cosas.
– Peter – Reincorporar a Mónica
12 de julio de 2018 a las 9:25
-
Sin embargo, lo que observó OP es interesante: mientras que el estándar en 6.9.2 permite explícitamente copiar bytes fuera de un objeto trivialmente copiable del que carece (o parece carecer, solo miré todas las apariciones de
memcpy
en n4659) una regla explícita que permite copiar bytes dentro tal objeto. Probablemente se considere entendido por sí mismo; el ejemplo en 6.9.2 copia los bytes, después de todo.– Peter – Reincorporar a Mónica
12 de julio de 2018 a las 9:42
-
@PeterA.Schneider Correcto. Hay “Si el contenido de esa matriz se vuelve a copiar en el objeto, el objeto mantendrá posteriormente su valor original”. que otorga permiso para copiar de nuevo en un objeto copiable trivialmente, pero el permiso general para copiar en (en lugar de volver a) un objeto copiable trivialmente nunca se otorga explícitamente en el estándar, solo se puede inferir. Esa es la esencia de mi respuesta.
– usuario743382
12 de julio de 2018 a las 10:39
-
¡Buen razonamiento! Aunque tengo una pregunta. A
float
->char[]
la copia está bien. Achar[]
->uint32_t
está bien también. Pero, es un directofloat
->uint32_t
¿Bien también?– geza
12 de julio de 2018 a las 12:17
-
@geza Es dudoso, pero yo diría que desde el tratamiento de los bytes en ese
float
como unchar[]
está permitido, cuando haces un directofloat
->uint32_t
en cierto sentido, usted son copiando de unchar[]
a unuint32_t
.– usuario743382
12 de julio de 2018 a las 12:26
erorika
¿Está bien definido mi primer ejemplo (y la respuesta vinculada)?
El comportamiento no está indefinido (a menos que el tipo de destino tenga representaciones de captura† 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 punto flotante, por lo que no hay forma de extraer la mantisa, etc., del entero de forma portátil; dicho esto, limitarse al uso de sistemas IEEE 754 no lo limita mucho en estos días.
Problemas de portabilidad:
- IEEE 754 no está garantizado por C++
- No se garantiza que el byte endian de float coincida con el entero endian.
- (Sistemas con representaciones trampa†).
Puedes 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. Mediante el uso uint32_t
ya ha descartado la portabilidad a sistemas esotéricos: no se requieren sistemas conformes con el estándar para definir ese alias.
-
@StoryTeller hasta donde yo sé,
unsigned char
es el único tipo garantizado para no tener trampas.– erorika
12 de julio de 2018 a las 9:33
-
@StoryTeller correcto, por lo que los alias de tipos de ancho exacto tienen garantías (más allá de los tipos para los que son un alias, si son alias estándar
int
o tal). Eso es un poco extraño, pero siempre es bueno poder ignorar las trampas, así que lo aceptaré 🙂– erorika
12 de julio de 2018 a las 9:53
-
“El comportamiento no está indefinido”. ¿Por qué? ¿Hay algo en el estándar que lo defina?
– geza
12 de julio de 2018 a las 10:06
-
Sí, dan un ejemplo de lo que han escrito previamente en el texto normativo. Podría copiar bytes con un simple ciclo de copia de bytes, el resultado será el mismo. Entonces el punto no es
memcpy
aquí, pero el principio. ¿Puedo copiar unfloat
auint32_t
byte a byte por algún medio? ¿Está definido por la norma?– geza
12 de julio de 2018 a las 10:55
-
Muchas implementaciones representan ciertos tipos de datos de manera diferente en los registros y en la memoria. Es común en plataformas RISC de 32 bits, por ejemplo, que un
uint16_t
que se coloca en un registro tendrá 16 bits de datos y 16 bits de relleno que se ponen a cero cuando se escribe el valor. Si dicho objeto se lee cuando no está inicializado, puede generar un valor que a veces se comporta como si estuviera fuera del rango 0-65535 pero a veces se comporta como si estuviera dentro de ese rango. Tal comportamiento se remonta a décadas antes de Itanium.– Super gato
12 de julio de 2018 a las 20:24
Gallo con sombrero
Su ejemplo está bien definido y no rompe el alias estricto. std::memcpy
dice claramente:
copias
count
bytes del objeto apuntado por src al objeto apuntado por dest. Ambos objetos se reinterpretan como matrices de
unsigned char
.
El estándar permite aliasing de cualquier tipo a través de un (signed/unsigned) char*
o std::byte
y por lo tanto su ejemplo no exhibe UB. Sin embargo, si el número entero resultante tiene algún valor, es otra cuestión.
use i to extract f's sign, exponent & significand
Sin embargo, esto no está garantizado por la norma ya que el valor de un float
está definido por la implementación (aunque en el caso de IEEE 754 funcionará).
-
Si podemos deducir esto de la descripción de
memcpy
entonces, ¿por qué el estándar destaca los dos casos que mencioné en mi pregunta?– geza
12 de julio de 2018 a las 10:04
-
@geza C++ se basará en C para la definición de memcpy y, como dije aquí, dice que copia n bytes “sin condición” entre dos objetos.
– Shafik Yaghmour
12 de julio de 2018 a las 13:08
-
Nota bit_cast utiliza memcpy como el mecanismo de juego de palabras subyacente.
– Shafik Yaghmour
12 de julio de 2018 a las 13:11
-
@ShafikYaghmour: sí, pero el problema no está ahí. Por ejemplo, si usted
memcpy
un tipo copiable no trivial, es UB, porque el estándar no lo permite. Al igual que no permite mi ejemplo explícitamente.– geza
12 de julio de 2018 a las 13:15
-
@ShafikYaghmour: no es un problema. Una biblioteca proporcionada por el compilador puede usar cualquier UB que desee, vea mi comentario debajo de la pregunta.
– geza
12 de julio de 2018 a las 13:15
Mientras sean del mismo tamaño no debería haber problema. Sin embargo, si sólo necesita interpretar
f
comouint32_t
solo puedes escribir(uint32_t&)f
. Interpretará la ubicación de la memoria delfloat
como si fuerauint32_t
.– SIN NOMBRE
12 de julio de 2018 a las 8:27
@NO_NAME Mi experimento muestra que su sugerencia infringe las estrictas reglas de creación de alias. coliru.stacked-crooked.com/a/bb54317049f5c8fc
– usuario2486888
12 de julio de 2018 a las 8:35
Relacionado: stackoverflow.com/questions/3275353/c-aliasing-rules-and-memcpy
– xskxzr
12 de julio de 2018 a las 8:50
Relacionado: stackoverflow.com/questions/17789928/…
– plasmacel
12 de julio de 2018 a las 9:00
@NO_NAME Todavía viola las estrictas reglas de alias. La sintaxis válida no implica una operación válida. Al igual que la oración en inglés “Ideas verdes incoloras duermen furiosamente” es gramaticalmente correcto pero sin sentido.
– usuario2486888
12 de julio de 2018 a las 9:01