funcion c undefined unions

funcion union c++



Una pregunta sobre la uniĆ³n en C (6)

Estaba leyendo acerca de la unión en C de K & R, hasta donde entiendo, una sola variable en unión puede contener cualquiera de los varios tipos y si algo se almacena como un tipo y se extrae como otro, el resultado es puramente implementación definida.

Ahora compruebe este fragmento de código:

#include<stdio.h> int main(void){ union a{ int i; char ch[2]; }; union a u; u.ch[0] = 3; u.ch[1] = 2; printf("%d %d %d/n",u.ch[0],u.ch[1],u.i); return 0; }

Salida:

3 2 515

Aquí estoy asignando valores en el u.ch pero recuperando tanto de u.ch como de ui , ¿está definida la implementación? o estoy haciendo algo realmente tonto?

Sé que puede parecer muy principiante para la mayoría de las otras personas, pero no puedo descubrir la razón detrás de esa salida.

Gracias,


Depende de la implementación y los resultados pueden variar en una plataforma / compilador diferente, pero parece que esto es lo que está sucediendo:

515 en binario es

1000000011

Rellenar ceros para convertirlo en dos bytes (suponiendo 16 bits int):

0000001000000011

Los dos bytes son:

00000010 and 00000011

Que es 2 y 3

Espero que alguien explique por qué se invierten, creo que los caracteres no se invierten, pero el int es poco endian.

La cantidad de memoria asignada a una unión es igual a la memoria requerida para almacenar el miembro más grande. En este caso, tiene una matriz int y una matriz char de longitud 2. Suponiendo que int es 16 bit y char es 8 bit, ambos requieren el mismo espacio y, por lo tanto, a la unión se les asignan dos bytes.

Cuando asigna tres (00000011) y dos (00000010) a la matriz de caracteres, el estado de la unión es 0000001100000010 . Cuando lees el int de esta unión, convierte todo en un entero. Suponiendo little-endian representación little-endian donde LSB se almacena en la dirección más baja, la lectura int de la unión sería 0000001000000011 que es el binario para 515.

NOTA: Esto es válido incluso si el int. Fue de 32 bits - Verifique la respuesta de Amnon


Este es un comportamiento indefinido. ui y u.ch se encuentran en la misma dirección de memoria. Entonces, el resultado de escribir en uno y leer del otro depende del compilador, la plataforma, la arquitectura y, a veces, del nivel de optimización del compilador. Por lo tanto, la salida para ui puede no ser siempre 515 .

Ejemplo

Por ejemplo, gcc en mi máquina produce dos respuestas diferentes para -O0 y -O2 .

  1. Como mi máquina tiene una arquitectura little-endian de 32 bits, con -O0 , termino con dos bytes menos significativos inicializados en 2 y 3, dos bytes más significativos no están inicializados. Entonces la memoria del sindicato se ve así: {3, 2, garbage, garbage}

    Por lo tanto, obtengo una salida similar a 3 2 -1216937469 .

  2. Con -O2 , obtengo la salida de 3 2 515 como lo hace, lo que hace que la memoria de unión sea {3, 2, 0, 0} . Lo que sucede es que gcc optimiza la llamada a printf con los valores reales, por lo que el resultado del ensamblado se ve como un equivalente de:

    #include <stdio.h> int main() { printf("%d %d %d/n", 3, 2, 515); return 0; }

    El valor 515 se puede obtener como otro explicado en otras respuestas a esta pregunta. En esencia, significa que cuando gcc optimizó la llamada, ha elegido ceros como el valor aleatorio de una unión potencial no inicializada.

Escribir a un miembro de un sindicato y leer de otro generalmente no tiene mucho sentido, pero a veces puede ser útil para programas compilados con un estricto aliasing .


La razón detrás de la salida es que en su máquina los enteros se almacenan en formato little-endian : los bytes menos significativos se almacenan primero. Por lo tanto, la secuencia de bytes [3,2,0,0] representa el número entero 3 + 2 * 256 = 515.

Este resultado depende de la implementación específica y la plataforma.


La respuesta a esta pregunta depende del contexto histórico, ya que la especificación del idioma cambió con el tiempo. Y este asunto es el afectado por los cambios.

Dijiste que estabas leyendo K & R. La última edición de ese libro (a partir de ahora) describe la primera versión estandarizada del lenguaje C - C89 / 90. En esa versión del lenguaje C escribir a un miembro de la unión y leer a otro miembro es un comportamiento indefinido . No se definió la implementación (que es algo diferente), sino un comportamiento indefinido . La parte relevante del estándar de idioma en este caso es 6.5 / 7.

Ahora, en algún momento posterior en la evolución de C (versión C99 de la especificación del lenguaje con el Corrigendum técnico 3 aplicado), de repente se hizo legal usar la unión para el tipo de juego de palabras, es decir, escribir a un miembro de la unión y luego leer otro.

Tenga en cuenta que intentar hacer eso todavía puede conducir a un comportamiento indefinido. Si el valor que lee no es válido (lo que se denomina "representación de captura") para el tipo en que lo leyó, entonces el comportamiento aún no está definido. De lo contrario, el valor que lee es la implementación definida.

Su ejemplo específico es relativamente seguro para el tipo de juego de palabras de int a char[2] array. Siempre es legal en el lenguaje C reinterpretar el contenido de cualquier objeto como una matriz de caracteres (nuevamente, 6.5 / 7).

Sin embargo, lo contrario no es verdad. Escribir datos en el miembro de matriz char[2] de su unión y luego leerlo como int puede potencialmente crear una representación de captura y conducir a un comportamiento indefinido . El peligro potencial existe incluso si su matriz de caracteres tiene la longitud suficiente para cubrir la totalidad del int .

Pero en su caso específico, si int resulta ser más grande que char[2] , el int que lee cubrirá el área no inicializada más allá del final de la matriz, lo que nuevamente conduce a un comportamiento indefinido.


La salida de dicho código dependerá de su plataforma y la implementación del compilador de C. Tu salida me hace pensar que estás ejecutando este código en un sistema litte-endian (probablemente x86). Si pusiera 515 en i y lo mirara en un depurador, vería que el byte de orden más bajo sería un 3 y el siguiente byte en la memoria sería un 2, que se correlaciona exactamente con lo que pone en ch.

Si hicieras esto en un sistema big-endian, habrías (probablemente) obtenido 770 (suponiendo entradas de 16 bits) o 50462720 (suponiendo entradas de 32 bits).


Si está en un sistema de 32 bits, un int es de 4 bytes, pero solo inicializa solo 2 bytes. El acceso a datos no inicializados es un comportamiento indefinido.

Suponiendo que está en un sistema con entradas de 16 bits, entonces lo que está haciendo todavía está definido por la implementación. Si su sistema es poco endian, entonces u.ch [0] se corresponderá con el byte menos significativo de ui y u.ch 1 será el byte más significativo. En un gran sistema endian, es al revés. Además, el estándar C no obliga a la implementación a usar 1 a 1 para representar valores enteros con signo, aunque el complemento a dos es el más común. Obviamente, el tamaño de un entero también está definido por la implementación.

Sugerencia: es más fácil ver lo que sucede si usa valores hexadecimales. En un pequeño sistema endian, el resultado en hex sería 0x0203.