c++ - ¿Accediendo al miembro de la unión inactiva y al comportamiento indefinido?
undefined-behavior language-lawyer (5)
Algo que aún no se menciona en las respuestas disponibles es la nota al pie 37 en el párrafo 21 de la sección 6.2.5:
Tenga en cuenta que el tipo agregado no incluye el tipo de unión porque un objeto con tipo de unión solo puede contener un miembro a la vez.
Este requisito parece implicar claramente que no debe escribir en un miembro y leer en otro. En este caso, podría ser un comportamiento indefinido por falta de especificación.
Tenía la impresión de que el acceso a un miembro del union
no fuera el último conjunto es UB, pero parece que no puedo encontrar una referencia sólida (aparte de las respuestas que afirman que es UB pero sin ningún apoyo del estándar).
Entonces, ¿es un comportamiento indefinido?
Bien explico esto con un ejemplo.
supongamos que tenemos la siguiente unión:
union A{
int x;
short y[2];
};
Supongo que sizeof(int)
da 4 y que sizeof(short)
da 2.
cuando escribas la union A a = {10}
crea una nueva var de tipo A ponle el valor 10.
su memoria debería verse así: (recuerde que todos los miembros de la unión obtienen la misma ubicación)
| x | | y[0] | y[1] | ----------------------------------------- a-> |0000 0000|0000 0000|0000 0000|0000 1010| -----------------------------------------
como se puede ver, el valor de ax es 10, el valor de ay 1 es 10 y el valor de ay [0] es 0.
ahora, ¿qué pasará si hago esto?
a.y[0] = 37;
nuestra memoria se verá así:
| x | | y[0] | y[1] | ----------------------------------------- a-> |0000 0000|0010 0101|0000 0000|0000 1010| -----------------------------------------
esto convertirá el valor de ax a 2424842 (en decimal).
ahora, si su sindicato tiene un flotador, o el doble, su mapa de memoria será un desastre, debido a la manera en que almacena los números exactos. más información podría obtener 1 .
Creo que lo más parecido a la norma al decir que es un comportamiento indefinido es cuando define el comportamiento de una unión que contiene una secuencia inicial común (C99, §6.5.2.3 / 5):
Se hace una garantía especial para simplificar el uso de las uniones: si una unión contiene varias estructuras que comparten una secuencia inicial común (ver a continuación), y si el objeto de unión contiene actualmente una de estas estructuras, se permite inspeccionar el común parte inicial de cualquiera de ellos en cualquier parte que sea visible una declaración del tipo completo de la unión. Dos estructuras comparten una secuencia inicial común si los miembros correspondientes tienen tipos compatibles (y, para los campos de bits, los mismos anchos) para una secuencia de uno o más miembros iniciales.
C ++ 11 da requisitos / permisos similares en §9.2 / 19:
Si una unión de disposición estándar contiene dos o más estructuras de disposición estándar que comparten una secuencia inicial común, y si el objeto de unión de disposición estándar contiene actualmente una de estas estructuras de disposición estándar, se le permite inspeccionar la parte inicial común de cualquier de ellos. Dos estructuras de disposición estándar comparten una secuencia inicial común si los miembros correspondientes tienen tipos compatibles con el diseño y ninguno de los dos es un campo de bits o ambos son campos de bits con el mismo ancho para una secuencia de uno o más miembros iniciales.
Aunque ninguno lo menciona directamente, ambos tienen una fuerte implicación de que "inspeccionar" (leer) a un miembro está "permitido" solo si 1) es (parte de) el miembro escrito más recientemente, o 2) es parte de una inicial común secuencia.
Esa no es una declaración directa de que, de lo contrario, se trata de un comportamiento indefinido, pero es el más cercano del que tengo conocimiento.
El estándar C ++ 11 lo dice de esta manera
9.5 Uniones
En una unión, como máximo uno de los miembros de datos no estáticos puede estar activo en cualquier momento, es decir, el valor de como máximo uno de los miembros de datos no estáticos se puede almacenar en una unión en cualquier momento.
Si solo se almacena un valor, ¿cómo se puede leer otro? Simplemente no está allí.
La documentación de gcc enumera esto bajo el comportamiento definido en Implementación
- Se accede a un miembro de un objeto de unión utilizando un miembro de un tipo diferente (C90 6.3.2.3).
Los bytes relevantes de la representación del objeto se tratan como un objeto del tipo utilizado para el acceso. Vea Tipo-juego de palabras. Esto puede ser una representación de trampa.
indicando que esto no es requerido por el estándar C.
01-01-2016: A través de los comentarios, me vincularon con C99 Defect Report # 283, que agrega un texto similar al pie de página del documento estándar de C:
78a) Si el miembro utilizado para acceder al contenido de un objeto de unión no es el mismo que el miembro utilizado por última vez para almacenar un valor en el objeto, la parte apropiada de la representación de objeto del valor se reinterpreta como una representación de objeto en el nuevo escriba como se describe en 6.2.6 (un proceso a veces llamado "tipo de juego de palabras"). Esto podría ser una representación de trampa.
Sin embargo, no estoy seguro si aclara mucho, teniendo en cuenta que una nota al pie no es normativa para el estándar.
La confusión es que C permite explícitamente el juego de palabras a través de una unión, mientras que C ++ ( c ++ 11 ) no tiene tal permiso.
c11
6.5.2.3 Estructura y miembros del sindicato
95) Si el miembro utilizado para leer el contenido de un objeto de unión no es el mismo que el miembro utilizado por última vez para almacenar un valor en el objeto, la parte apropiada de la representación de objeto del valor se reinterpreta como una representación de objeto en el nuevo escriba como se describe en 6.2.6 (un proceso a veces llamado '''' tipo de juego de palabras ''''). Esto podría ser una representación de trampa.
La situación con C ++:
c ++ 11
9.5 Unions [class.union]
En una unión, como máximo uno de los miembros de datos no estáticos puede estar activo en cualquier momento, es decir, el valor de como máximo uno de los miembros de datos no estáticos se puede almacenar en una unión en cualquier momento.
C ++ luego tiene un lenguaje que permite el uso de uniones que contienen struct
con secuencias iniciales comunes; sin embargo, esto no permite el tipo de juego de palabras.
Para determinar si la unión de tipo de unión está permitida en C ++, debemos buscar más. Recuerde que c99 es una referencia normativa para C ++ 11 (y C99 tiene un lenguaje similar al C11 que permite el tipo de juego de unión):
3.9 Tipos [tipos básicos]
4 - La representación del objeto de un objeto de tipo T es la secuencia de N objetos de signo sin signo tomados por el objeto de tipo T, donde N es igual a sizeof (T). La representación del valor de un objeto es el conjunto de bits que contiene el valor del tipo T. Para los tipos que se pueden copiar trivialmente, la representación del valor es un conjunto de bits en la representación del objeto que determina un valor, que es un elemento discreto de una implementación. conjunto definido de valores. 42
42) La intención es que el modelo de memoria de C ++ sea compatible con el lenguaje de programación ISO / IEC 9899 C.
Se vuelve particularmente interesante cuando leemos
3.8 Vida del objeto [basic.life]
La vida útil de un objeto de tipo T comienza cuando: - se obtiene el almacenamiento con la alineación y el tamaño adecuados para el tipo T, y - si el objeto tiene una inicialización no trivial, su inicialización está completa.
Entonces, para un tipo primitivo (que ipso facto tiene inicialización trivial) contenido en una unión, el tiempo de vida del objeto abarca al menos la vida de la unión misma. Esto nos permite invocar
3.9.2 Tipos de compuestos [basic.compound]
Si un objeto de tipo T está ubicado en una dirección A, se dice que un puntero de tipo cv T * cuyo valor es la dirección A apunta a ese objeto, independientemente de cómo se obtuvo el valor.
Suponiendo que la operación en la que estamos interesados es el tipo de juego, es decir, tomando el valor de un miembro de la unión no activo, y dado por lo anterior que tenemos una referencia válida al objeto al que hace referencia ese miembro, esa operación es lvalue-to -versión de valor:
4.1 Conversión devalor avalor [conv.lval]
Un glvalue de un tipo
T
no funcional, no de matriz se puede convertir a un prvalue. SiT
es un tipo incompleto, un programa que necesita esta conversión está mal formado. Si el objeto al que se refiere glvalue no es un objeto de tipoT
y no es un objeto de un tipo derivado deT
, o si el objeto no está inicializado, un programa que necesita esta conversión tiene un comportamiento indefinido.
La pregunta entonces es si un objeto que es un miembro de la unión no activa se inicializa mediante el almacenamiento en el miembro activo de la unión. Por lo que puedo decir, este no es el caso, aunque si:
- una unión se copia en el almacenamiento de matriz
char
y viceversa (3.9: 2), o - una unión se copia por byte a otra unión del mismo tipo (3.9: 3), o
- se accede a una unión a través de los límites del idioma mediante un elemento de programa conforme a ISO / IEC 9899 (en la medida en que se define) (3.9: 4 nota 42), luego
el acceso a una unión por un miembro no activo se define y se define para seguir el objeto y la representación del valor, el acceso sin una de las interposiciones anteriores es un comportamiento indefinido. Esto tiene implicaciones para las optimizaciones permitidas para dicho programa, ya que la implementación puede suponer, por supuesto, que no se produce un comportamiento indefinido.
Es decir, aunque podemos legítimamente formar un lvalue para un miembro de unión no activo (que es la razón por la cual asignarlo a un miembro no activo sin construcción está bien) se considera que no está inicializado.