Acceder a miembro inactivo del sindicato y comportamiento indefinido?


Tenía la impresión de que acceder a un miembro union que no sea 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 soporte del estándar).

Entonces, ¿es un comportamiento indefinido?

Author: jww, 2012-07-07

5 answers

La confusión es que C permite explícitamente el juego de tipo a través de una unión, mientras que C++ ( c++11) no tiene dicho permiso.

C11

6.5.2.3 Estructura y miembros del sindicato

95) Si el miembro utilizado para leer el contenido de un objeto union 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 del objeto del valor se reinterpreta como representación de objetos en el nuevo tipo como se describe en 6.2.6 (un proceso a veces llamado " tipo punning"). Esto podría ser una representación trampa.

La situación con C++:

C++11

9.5 Uniones [clase.unión]

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 la mayoría de los miembros de datos no estáticos pueden almacenarse en un sindicato en cualquier momento.

C++ más tarde tiene lenguaje que permite el uso de uniones que contienen struct s con secuencias iniciales comunes; sin embargo, esto no permite el juego de tipo.

Para determinar si union type-punning está permitido en C++, tenemos que buscar más. Recuerde que c99 es una referencia normativa para C++11 (y C99 tiene un lenguaje similar al C11 que permite el juego de palabras de tipo unión):

3.9 Tipos [básico.tipos]

4 - La representación de un objeto de tipo T es la secuencia de N objetos char sin signo asumida por el objeto de tipo T, donde N es igual a sizeof (T). La representación de valor de un objeto es el conjunto de bits que mantenga el valor del tipo T. Para los tipos que se pueden copiar trivialmente, la representación del valor es un conjunto de bits en el objeto representación que determina un valor, que es un elemento discreto de un conjunto definido por la implementación de valor. 42
42) La intención es que el modelo de memoria de C++ sea compatible con el del Lenguaje de programación ISO/IEC 9899 C.

Se pone particularmente interesante cuando leemos

3.8 Duración del objeto [básico.vida]

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 inicialización no trivial, su inicialización es completa.

Así que para un tipo primitivo (que ipso facto tiene inicialización trivial) contenido en una unión, la vida útil del objeto abarca al menos la vida útil de la propia unión. Esto nos permite invocar

3.9.2 Tipos compuestos [básico.compuesto]

Si un objeto de tipo T se encuentra en una dirección A, un puntero de tipo cv T * cuyo valor es el se dice que 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 un tipo de juego, es decir, tomando el valor de un miembro del sindicato no activo, y dado por lo anterior que tenemos una referencia válida al objeto referido por ese miembro, esa operación es conversión lvalue-to-rvalue:

4.1 Conversión Lvalue a rvalue [conv.lval]

Un glvalue de un tipo no-función, no-array T se puede convertir en un prvalue. Si T es un tipo incompleto, un programa que necesita esta conversión está mal formado. Si el objeto al que se refiere el glvalue no es un objeto de tipo T y no es un objeto de un tipo derivado de T, 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 unión no activo es inicializado por almacenamiento al miembro de unión activo. Por lo que puedo decir, este no es el caso y así aunque si:

  • una unión se copia en char array storage y viceversa (3.9:2), o
  • una unión se copia bytewise a otra unión del mismo tipo (3.9: 3), o
  • se accede a una unión a través de los límites lingüísticos mediante un elemento de programa conforme a ISO / IEC 9899 (en la medida en que se defina) (3.9:4 nota 42), entonces

El acceso a una unión por un miembro inactivo se define y se define para seguir la representación de objeto y valor, el acceso sin una de las interposiciones anteriores es un comportamiento indefinido. Esto tiene implicaciones para las optimizaciones que se permiten realizar en un programa de este tipo, ya que la implementación puede supuesto asumir que no se produce un comportamiento indefinido.

Es decir, aunque podemos legítimamente formar un lvalue a un miembro del sindicato no activo (por lo que asignar a un miembro no activo sin construcción está bien) se considera que no está iniciado.

 106
Author: ecatmur,
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
2013-07-24 00:58:32

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 puede almacenarse en una unión en cualquier momento.

Si solo se almacena un valor, ¿cómo se puede leer otro? Simplemente no está ahí.


La documentación de gcc enumera esto bajo Comportamiento definido por la implementación

  • Se accede a un miembro de un objeto union 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. Ver Tipo-juego de palabras. Esto puede ser una representación trampa.

Indicando que esto no es requerido por el estándar C.


2016-01-05: A través de los comentarios que estaba vinculado a C99 Informe de Defectos #283 que añade un similar texto como nota al pie del documento estándar C:

78a) Si el miembro utilizado para acceder al contenido de un objeto union 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 del objeto del valor se reinterpreta como una representación de objeto en el nuevo tipo como se describe en 6.2.6 (un proceso a veces llamado "tipo punning"). Esto podría ser una representación trampa.

No estoy seguro de si aclara mucho, sin embargo, considerando que una nota de pie de página no es normativa para la norma.

 22
Author: Bo Persson,
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
2016-01-05 11:48:37

Creo que lo más cerca que el estándar llega a decir que es un comportamiento indefinido es donde define el comportamiento para 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 los sindicatos: si un sindicato contiene varias estructuras que comparten una secuencia inicial común (ver más abajo), y si la unión objeto actualmente contiene una de estas estructuras, se permite inspeccionar el común parte inicial de cualquiera de ellos en todos los casos en que una declaración del tipo completo de la unión es visible. 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 diseño estándar contiene dos o más estructuras de diseño estándar que comparten una secuencia inicial común, y si la unión de diseño estándar objeto actualmente contiene una de estas estructuras de diseño estándar, se permite para inspeccionar la parte inicial común de cualquiera de ellos. Dos estructuras de diseño estándar comparten una inicial común secuencia si los miembros correspondientes tienen tipos compatibles con el diseño y ninguno de los miembros 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 declara directamente, ambos llevan una fuerte implicación de que " inspeccionar" (lectura) un miembro está "permitido" solo si 1) es (parte de) el miembro escrito más recientemente, o 2) es parte de una secuencia inicial común.

Eso no es una declaración directa de que hacer lo contrario es un comportamiento indefinido, pero es lo más cercano de lo que estoy consciente.

 16
Author: Jerry Coffin,
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
2012-08-10 18:06:46

Algo que aún no se menciona en las respuestas disponibles es la nota 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.

 10
Author: mpu,
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
2012-08-16 22:00:52

Lo explico bien con un ejemplo.
supongamos que tenemos la siguiente unión:

union A{
   int x;
   short y[2];
};

Asumo bien que sizeof(int) da 4, y que sizeof(short) da 2.
cuando escribes union A a = {10} que bien crear un nuevo var de tipo A en poner en él el valor 10.

Su memoria debe verse así: (recuerde que todos los miembros del sindicato 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 a.x es 10, el valor de a. y1 es 10, y el valor de a.y[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 a.x a 2424842 (en decimal).

Ahora, si su unión tiene un flotador, o doble, su mapa de memoria será más un desastre, debido a la forma en que almacena los números exactos. puede obtener más información en aquí.

 -2
Author: elyashiv,
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
2014-09-14 09:20:19