Sobre a union da linguagem C

A união (union) na linguagem C é um tipo especial de dado que permite armazenar diferentes tipos de dados em um mesmo espaço da memória. Embora a union possa ter vários campos em sua definição, apenas um deles pode conter um valor por vez. Esse tipo de funcionamento permite a utilização de um mesmo espaço de memória para múltiplos propósitos. Trago alguns exemplos ao final deste post.

Para saber o básico sobre as unions leia https://www.tutorialspoint.com/cprogramming/c_unions.htm (em inglês, ou http://www.bosontreinamentos.com.br/programacao-em-linguagem-c/unions-em-linguagem-c/ (em português).

A sintaxe para definição de uma union é similar ao de uma struct, então inicio comparando esses dois tipo de dados na linguagem C. No código a seguir é definida uma union e uma struct com os mesmo campos, sendo que o primeiro armazena um valor inteiro e o segundo armazena um valor float. O código apenas mostrará o espaço ocupado (em Bytes) na memória pelos 2 tipos de dados básico (int e float) e pelo 2 tipo definidos no código.

#include <stdio.h>

union teste {
  int a;
  float b;
};

struct teste2 {
  int a;
  float b;
};

int main(void) {
  //4 4 4 8
  printf("%lu %lu %lu %lu\n", sizeof(int), sizeof(float), sizeof(union teste), sizeof(struct teste2));
  
  return 0;
}

Como resultado da execução do código acima é mostrado o resultado a seguir. Isso porque a função sizeof(int) e sizeof(float) retornam o valor 4 (considerando que o computador é 64 bits, o tipo int ocupa 4 Bytes, senão ocuparia 2 Bytes). Observe que a struct teste2 ocupa 8 Bytes, uma vez que ela armazena um dado int (que ocupa 4 Bytes) e um dado float (que também ocupa outros 4 Bytes), ou seja, 4 + 4 = 8. Por outro lado, o espaço ocupado pela union teste ocupa apenas 4 Bytes, mesmo tendo dois campos.

4 4 4 8

A seguir altero o código para definir duas variáveis para cada um dos tipos definidos no código, sendo a variável x definida com base na union e a variável x2 baseada na struct. Em seguida são mostradas as posições da memória (utilizando ponteiros), onde os valores serão armazenados.

#include <stdio.h>

union teste {
  int a;
  float b;
};

struct teste2 {
  int a;
  float b;
};

int main(void) {
  union teste x;
  struct teste2 x2;;
    
  printf("%p %p\n", &x2.a, &x2.b); //0x7fff9bbadfe8 0x7fff9bbadfec
  printf("%p %p\n", &x.a, &x.b); //0x7fff9bbadff0 0x7fff9bbadff0
  
  return 0;
}

Um possível resultado da execução do código acima é mostrado a seguir. Digo possível porque as regiões da memória variam de acordo com a disponibilidade durante a execução do programa. Perceba que o campo “a” da struct é uma região diferente do campo “b” da struct (conforme endereços de memória mostrados na primeira linha). Já os campos “a” e “b” da union estão na mesma área de memória (conforme endereços de memória mostrados na segunda linha).

0x7fff9bbadfe8 0x7fff9bbadfec
0x7fff9bbadff0 0x7fff9bbadff0

Uma vez que ambos os campos da union são armazenados no mesmo espaço de memória, somente um dos campos de ser utilizado efetivamente para armazenar dados. O código a seguir mostra essa utilização.

Na linha 12 é atribuído zero ao valor do campo “a” e em seguida é mostrado o valor dos campos “a” e “b“. Como resultado será mostrado os valores “0 0.000000“. Isso porque o valor zero inteiro e float possuem a mesma representação binária (lembre-se que todos os valores, não importa o seu tipo, são armazenados de forma binária na memória). Na linha 15 o valor atribuído passa a ser o número 2 e com isso já é possível perceber uma diferença em relação aos valores dos campos “a” e “b”.

Possivelmente é com a atribuição na linha 18 que trago a melhor interpretação do que ocorre. O valor 2.0 é atribuído ao campo “b” e com isso é mostrado o valor 1073741824 no print do campo “a“. Esse valor parece um número qualquer, mas não é. O número 2.0 é armazenado como a sequência binária 0100 0000 0000 0000 0000 0000 0000 0000 (existem várias maneiras para representação um número float de forma binária, o padrão aqui mostrado considera o da IEEE 754). Transformando essa sequência binária em um valor inteiro tem-se 1×230 (apenas o bit 30 contribui para o resultado). Por fim, 230 resulta em 1073741824, que é o valor que aparece quando foi realizado o print do campo “a“. Logicamente que, embora o campo “a” tenha um valor, o valor somente é realmente útil para o campo “b“, que foi o campo ao qual o valor foi atribuído.

#include <stdio.h>
#include <math.h>

union teste {
  int a;
  float b;
};

int main(void) {
  union teste x;
  
  x.a = 0;
  printf("%i %f\n", x.a, x.b); //0 0.000000

  x.a = 2;
  printf("%i %f\n", x.a, x.b); //2 0.000000

  x.b = 2.0;
  printf("%i %f\n", x.a, x.b); //1073741824 2.000000

  //1073741824 => 0100 0000 0000 0000 0000 0000 0000 0000 = 2^30
  printf("%i %i %i", x.a, pow(2,30));
  
  return 0;
}

Para exemplificar o uso da union, um uso poderia ser para armazenar valores da altura de uma pessoa, sendo que o sistema é configurado pelo usuário para poder armazenar essa altura em centímetros ou metros, ou seja, como um dado inteiro ou float (como na union especifica aqui nos códigos).

Outro uso, pode ser exemplificado com os campos da union sendo de tipos de dados definidos em structs, uma para armazenar dados de pessoa física e outra para pessoa jurídica. Com isso, dependendo do tipo de pessoa cadastrada, uma union atuará com os dados dos campos.

Share

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.