2009-04-11 14 views
34

¿Cuál es el tamaño de la unión en C/C++? ¿Es el tamaño del tipo de datos más grande dentro de él? Si es así, ¿cómo calcula el compilador cómo mover el puntero de pila si uno de los tipos de datos más pequeños de la unión está activo?sizeof a union en C/C++

Respuesta

25

El estándar responde todas las preguntas en la sección 9.5 del estándar C++, o en la sección 6.5.2.3 párrafo 5 del estándar C99 (o en el párrafo 6 de la norma C11):

en una unión, a lo sumo uno de los miembros de datos puede estar activo en cualquier momento, es decir, el valor de a lo sumo uno de los miembros de datos se puede almacenar en una unión en cualquier momento. [Nota: se hace una garantía especial para simplificar el uso de uniones: si un POD-union contiene varias POD-struct que comparten una secuencia inicial común (9.2), y si un objeto de este tipo de POD-union contiene uno de las estructuras POD, se permite inspeccionar la secuencia inicial común de cualquiera de los miembros POD-struct; ver 9.2. ] El tamaño de una unión es suficiente para contener el mayor de sus miembros de datos. Cada miembro de datos se asigna como si fuera el único miembro de una estructura.

Eso significa que cada miembro comparte la misma región de memoria. Hay es como máximo un miembro activo, pero no puede encontrar cuál. Tendrá que almacenar esa información sobre el miembro activo actualmente en otro lugar. El almacenamiento de dicha bandera además de la unión (por ejemplo, tener una estructura con un número entero como indicador de tipo y una unión como almacén de datos) le dará una llamada "unión discriminada": una unión que sabe de qué tipo actualmente es el "activo".

Un uso común es en lexers, donde se puede tener diferentes fichas, pero dependiendo de la clave, tener diferentes informaciones para almacenar (poniendo line en cada estructura para mostrar lo que una secuencia inicial común es):

struct tokeni { 
    int token; /* type tag */ 
    union { 
     struct { int line; } noVal; 
     struct { int line; int val; } intVal; 
     struct { int line; struct string val; } stringVal; 
    } data; 
}; 

El estándar le permite acceder a line de cada miembro, porque esa es la secuencia inicial común de cada uno.

Existen extensiones de compilador que permiten acceder a todos los miembros sin tener en cuenta cuál tiene actualmente su valor almacenado. Eso permite la reinterpretación eficiente de los bits almacenados con diferentes tipos entre cada uno de los miembros. Por ejemplo, lo siguiente puede ser utilizado para diseccionar una variable del flotador en 2 unsignedshorts:

union float_cast { unsigned short s[2]; float f; }; 

que puede venir muy útil al escribir código de bajo nivel. Si el compilador no admite esa extensión, pero lo hace de todos modos, escriba el código cuyos resultados no están definidos. Así que asegúrese de que su compilador tenga soporte si usa ese truco.

+3

Un horrible ejemplo de lenguaje estándar malo en mi humilde opinión - de hecho, toda la sección sobre sindicatos parece un poco escasa. ¿Por qué introducir el concepto de "activo" en absoluto? –

+2

GCC al menos explícitamente admite la lectura cruzada de miembros de la unión. y si los miembros están relacionados de alguna manera de acuerdo con 3.10/15 o son compatibles con el diseño, estoy bastante seguro de que todavía puede leer el otro miembro aunque no sea el "activo". –

+0

Es el bit "activo" que me atrapa. si 9.5 \ 1 comenzara con "el valor máximo", entonces no habría necesidad de introducir este nebuloso concepto de "activo". Pero esto debería (si está en cualquier parte) estar en comp.lang.C++. Std, ¡y no en un cuadro de comentarios SO tan perverso! Así que voy a cerrar la sesión en este tema. –

50

A union siempre ocupa tanto espacio como el miembro más grande. No importa lo que está actualmente en uso.

union { 
    short x; 
    int y; 
    long long z; 
} 

Un ejemplo de lo anterior union siempre tendrá al menos una long long para el almacenamiento.

Nota al margen: Como se ha señalado por Stefano, el espacio real cualquier tipo (union, struct, class) se llevará a no depender de otras cuestiones, como la alineación por el compilador. No revisé esto por simplicidad ya que solo quería decir que un sindicato tiene en cuenta el artículo más grande. Es importante saber que el tamaño real hace depende de la alineación.

+0

Un lugar donde sizeof podría devolver algo más grande es cuando se usa un doble largo. Un doble largo es de 10 bytes, pero Intel recomienda alinearlo a 16 bytes. – dreamlax

+0

long double es ... bueno, depende del compilador. Creo que * los compiladores de PowerPC usan dobles de 128 bits –

+0

Sí, quise decir un doble largo en x86. – dreamlax

10

No existe la noción de tipo de datos activos para una unión. Usted es libre de leer y escribir cualquier 'miembro' de la unión: esto depende de usted para interpretar lo que obtiene.

Por lo tanto, el tamaño de una unión es siempre el tamaño de su tipo de datos más grande.

+2

Está, por supuesto, mal ... el lenguaje del estándar se refiere explícitamente al tipo de datos activo. Sin embargo, sizeof es una operación en tiempo de compilación y, por supuesto, no depende del tipo de datos activo. –

+1

@JimBalter - Tiene razón sobre el estándar. Lo que quiero decir es que en C no se puede consultar a un sindicato sobre su _tipo de datos activo_. Nada impide que el codificador escriba un flotador y lea un int (y obtenga basura). – mouviciel

+2

Usted dijo "No hay ninguna noción de tipo de datos activo para una unión". Te equivocaste; me pertenece. No servirá afirmar que usted quiso decir algo muy diferente de lo que escribió solo para tratar de evitar haber estado equivocado. "Nada impide que el codificador escriba un flotador y lea un int (y obtenga basura)". - Por supuesto, nada lo impide ... el estándar C no * previene * nada; solo te dice si ese comportamiento está definido; no lo es. Como se ha observado repetidamente, UB incluye cualquier cosa, incluso la detonación de armas nucleares. Para algunas personas, eso les impide codificar UB. –

3

El tamaño será al menos el del tipo de composición más grande. No hay concepto de tipo "activo".

+0

Excepto que sí, que hay. –

2

Realmente debe mirar a una unión como un contenedor para el tipo de datos más grande dentro de él combinado con un acceso directo para un elenco. Cuando usa uno de los miembros más pequeños, el espacio no utilizado aún está allí, pero simplemente no se utiliza.

Suele ver esto usado en combinación con llamadas ioctl() en Unix, todas las llamadas ioctl() pasarán la misma estructura, que contiene una unión de todas las respuestas posibles. P.ej. este ejemplo proviene de /usr/include/linux/if.h y esta estructura se usa en ioctl() para configurar/consultar el estado de una interfaz de ethernet, los parámetros de solicitud definen qué parte de la unión está realmente en uso :

struct ifreq 
{ 
#define IFHWADDRLEN 6 
    union 
    { 
     char ifrn_name[IFNAMSIZ];  /* if name, e.g. "en0" */ 
    } ifr_ifrn; 

    union { 
     struct sockaddr ifru_addr; 
     struct sockaddr ifru_dstaddr; 
     struct sockaddr ifru_broadaddr; 
     struct sockaddr ifru_netmask; 
     struct sockaddr ifru_hwaddr; 
     short ifru_flags; 
     int ifru_ivalue; 
     int ifru_mtu; 
     struct ifmap ifru_map; 
     char ifru_slave[IFNAMSIZ]; /* Just fits the size */ 
     char ifru_newname[IFNAMSIZ]; 
     void * ifru_data; 
     struct if_settings ifru_settings; 
    } ifr_ifru; 
}; 
0
  1. el tamaño de la pieza más grande.

  2. Es por eso que las uniones generalmente tienen sentido dentro de una estructura que tiene una bandera que indica cuál es el miembro "activo".

Ejemplo:

struct ONE_OF_MANY { 
    enum FLAG { FLAG_SHORT, FLAG_INT, FLAG_LONG_LONG } flag; 
    union { short x; int y; long long z; }; 
}; 
+1

No es cierto. Un uso común es acceder a partes más pequeñas de un tipo más grande. Ejemplo: unión U {int i; char c [4]; }; se puede usar para otorgar acceso (específico a la implementación) a bytes de un entero de 4 bytes. –

+0

Oh, es cierto ... No he notado esa posibilidad. Siempre he accedido a partes de un tipo más grande usando cambios de byte y ese tipo de cosas. – pyon

+0

@anon: implementación específica o simplemente UB, según tu compilador. Confiar incluso en el primero es una mala práctica si se puede evitar. –

15

Depende del compilador, y en las opciones.

int main() { 
    union { 
    char all[13]; 
    int foo; 
    } record; 

printf("%d\n",sizeof(record.all)); 
printf("%d\n",sizeof(record.foo)); 
printf("%d\n",sizeof(record)); 

} 

Este salidas:

Si no recuerdo mal, que depende de la alineación que el compilador pone en el espacio asignado.Entonces, a menos que use alguna opción especial, el compilador colocará relleno en su espacio sindical.

edición: con gcc es necesario utilizar una directiva pragma

int main() { 
#pragma pack(push, 1) 
     union { 
      char all[13]; 
      int foo; 
     } record; 
#pragma pack(pop) 

     printf("%d\n",sizeof(record.all)); 
     printf("%d\n",sizeof(record.foo)); 
     printf("%d\n",sizeof(record)); 

} 

esta salida a

También se puede ver desde el desmonte (eliminado algunas printf, para mayor claridad)

0x00001fd2 <main+0>: push %ebp    | 0x00001fd2 <main+0>: push %ebp 
    0x00001fd3 <main+1>: mov %esp,%ebp  | 0x00001fd3 <main+1>: mov %esp,%ebp 
    0x00001fd5 <main+3>: push %ebx    | 0x00001fd5 <main+3>: push %ebx 
    0x00001fd6 <main+4>: sub $0x24,%esp  | 0x00001fd6 <main+4>: sub $0x24,%esp 
    0x00001fd9 <main+7>: call 0x1fde <main+12> | 0x00001fd9 <main+7>: call 0x1fde <main+12> 
    0x00001fde <main+12>: pop %ebx    | 0x00001fde <main+12>: pop %ebx 
    0x00001fdf <main+13>: movl $0xd,0x4(%esp) | 0x00001fdf <main+13>: movl $0x10,0x4(%esp)           
    0x00001fe7 <main+21>: lea 0x1d(%ebx),%eax | 0x00001fe7 <main+21>: lea 0x1d(%ebx),%eax 
    0x00001fed <main+27>: mov %eax,(%esp)  | 0x00001fed <main+27>: mov %eax,(%esp) 
    0x00001ff0 <main+30>: call 0x3005 <printf> | 0x00001ff0 <main+30>: call 0x3005 <printf> 
    0x00001ff5 <main+35>: add $0x24,%esp  | 0x00001ff5 <main+35>: add $0x24,%esp 
    0x00001ff8 <main+38>: pop %ebx    | 0x00001ff8 <main+38>: pop %ebx 
    0x00001ff9 <main+39>: leave     | 0x00001ff9 <main+39>: leave 
    0x00001ffa <main+40>: ret      | 0x00001ffa <main+40>: ret  

Donde la única diferencia está en main + 13, donde el compilador asigna en la pila 0xd en lugar de 0x10

+0

¡Gracias - me ahorra tener que armar esta respuesta! –

+3

Sí, supongo que todos deberíamos haber dicho "Al menos_ tan grande como el tipo más grande". –

+1

@Neil: la alineación del compilador es un problema totalmente diferente. Sucede también en las estructuras y también depende del lugar donde coloque la unión en la estructura. Si bien esto es cierto, creo que simplemente complica la respuesta a * esta * pregunta.Por cierto, tuve cuidado de alinear mi unión de muestra a un límite de 8 bytes: p –

0

¿Cuál es el tamaño de la unión en C/C++? ¿Es el tamaño del tipo de datos más grande dentro de él?

, El tamaño de la unión es el tamaño de su miembro más grande.

Por ejemplo:

#include<stdio.h> 

union un 
{ 
    char c; 
    int i; 
    float f; 
    double d; 
}; 

int main() 
{ 
    union un u1; 
    printf("sizeof union u1 : %ld\n",sizeof(u1)); 
    return 0; 
} 

de salida:

sizeof union u1 : 8 
sizeof double d : 8 

Aquí miembro más grande es double. Ambos tienen el tamaño 8. Entonces, como sizeof correctamente le dijo, el tamaño de la unión es de hecho 8.

¿cómo calcula el compilador cómo mover el puntero de pila si está activo uno del tipo de datos más pequeño de la unión?

Se maneja internamente por el compilador. Supongamos que estamos accediendo a uno de los miembros de datos de la unión, entonces no podemos acceder a otros miembros de datos ya que podemos acceder a un único miembro de datos de unión porque cada miembro de datos comparte la misma memoria. Al usar Union podemos ahorrar mucho espacio valioso.