Publicidade

Unions em C

Vários membros compartilhando um único espaço de memória. Melhor aprender junto com struct.

📖 O que você vai aprender nesta página
✅ Essenciais que você precisa saber
  • Todos os membros de uma union compartilham o mesmo espaço de memória
  • sizeof é igual ao tamanho do maior membro
  • Apenas um membro é válido por vez
⭐ Leia se tiver tempo
  • A clássica verificação de endianness
  • Tagged unions (enum + union)
  • Type punning no C11
💪 Não se preocupe se não entender de primeira
Unions sobrepõem várias visões em um mesmo espaço de memória — abstrato e raramente necessário no dia a dia. Sinta-se à vontade para passar por cima.
Como revisar
  1. Primeiro fique confortável com structs
  2. Confirme com sizeof que o tamanho corresponde ao maior membro
  3. A verificação de endianness é o aprendizado mais prático
  4. Se não fizer sentido, siga em frente — volte quando encontrar um caso real
💡 Dica: Usos reais são tagged unions e type punning. Para iniciantes, dominar struct já basta.

O que é uma union

Uma union é um tipo cujos membros compartilham a mesma memória. Seu tamanho é igual ao do maior membro, e apenas um membro guarda um valor significativo por vez.
#include <stdio.h>

union Data {
    int    i;
    float  f;
    char   s[8];
};

int main(void) {
    union Data d;

    d.i = 42;
    printf("d.i = %d\n", d.i);        // 42

    d.f = 3.14f;                    // i fica inválido agora
    printf("d.f = %f\n", d.f);
    printf("d.i = %d\n", d.i);        // padrão de bits do float

    printf("sizeof = %zu\n", sizeof(d));// 8 (maior membro)
    return 0;
}
Como difere de struct: Uma struct coloca os membros lado a lado (o total é a soma dos tamanhos); uma union sobrepõe eles (o total é o tamanho do maior).

struct vs union (visão de memória)

struct S { int a; int b; }

a
a
a
a
a
0-3
b
b
b
b
b
4-7
sizeof = 8 bytes. a e b são armazenados de forma independente.

union U { int a; int b; }

a = b
a/b
a/b
a/b
a/b
0-3
sizeof = 4 bytes. Escrever a também altera b — eles compartilham a mesma posição.

Experimento

#include <stdio.h>

struct S { int a; int b; };
union  U { int a; int b; };

int main(void) {
    struct S s = {10, 20};
    printf("struct: a=%d b=%d size=%zu\n", s.a, s.b, sizeof(s));
    // → a=10 b=20 size=8

    union U u;
    u.a = 10;
    u.b = 20;
    printf("union:  a=%d b=%d size=%zu\n", u.a, u.b, sizeof(u));
    // → a=20 b=20 size=4   (mesmo espaço!)
}

Tamanho & alinhamento

Uma union tem pelo menos o tamanho do maior membro, e as exigências de alinhamento podem adicionar padding no final.
union Mix {
    char   c;       // 1 byte
    int    i;       // 4 bytes
    double d;       // 8 bytes
};
// sizeof(union Mix) é pelo menos 8, alinhado para double

Vendo o mesmo espaço de duas formas

union Split {
    int           n;
    struct { char b0, b1, b2, b3; } bytes;
};

union Split s;
s.n = 0x12345678;
printf("%02x %02x %02x %02x\n",
       (unsigned)s.bytes.b0, (unsigned)s.bytes.b1,
       (unsigned)s.bytes.b2, (unsigned)s.bytes.b3);
// little-endian: 78 56 34 12
// big-endian:    12 34 56 78

Verificação de endianness

Unions oferecem uma forma limpa de verificar se a CPU atual é little- ou big-endian. É um exemplo clássico de livro-texto.
#include <stdio.h>

int is_little_endian(void) {
    union { int i; char c[sizeof(int)]; } u;
    u.i = 1;
    return u.c[0] == 1;
}

int main(void) {
    printf("%s-endian\n",
           is_little_endian() ? "little" : "big");
}

Por que funciona

Os 4 bytes de int i = 1:
little-endian (x86 etc.): 01 00 00 00c[0] == 1
big-endian (alguns ARM etc.): 00 00 00 01c[0] == 0
Sem union: int n = 1; char *p = (char*)&n; também funciona, mas a versão com union geralmente é mais amigável às regras de strict-aliasing do compilador.

Tagged union (padrão variant)

Quando um valor pode ser de vários tipos, combine uma tag de tipo (um enum) com uma union de dados dentro de uma struct. É a versão em C de um tipo soma, similar ao enum do Rust.
#include <stdio.h>

enum ValueKind { V_INT, V_FLOAT, V_STRING };

struct Value {
    enum ValueKind kind;        // "qual está ativo"
    union {
        int   i;
        float f;
        char  s[32];
    } data;
};

void print_value(struct Value v) {
    switch (v.kind) {
        case V_INT:    printf("int: %d\n", v.data.i);    break;
        case V_FLOAT:  printf("float: %f\n", v.data.f); break;
        case V_STRING: printf("str: %s\n", v.data.s);   break;
    }
}

int main(void) {
    struct Value a = {V_INT, .data.i = 42};
    struct Value b = {V_FLOAT, .data.f = 3.14f};
    struct Value c;
    c.kind = V_STRING;
    snprintf(c.data.s, sizeof(c.data.s), "hello");

    print_value(a);  // int: 42
    print_value(b);  // float: 3.14
    print_value(c);  // str: hello
}
Comum em parsers de JSON, ASTs e mensagens de protocolo. Envolva leituras e escritas em funções para que os chamadores não esqueçam de verificar kind.

Considerações de tamanho

// sizeof(struct Value) = enum(4) + maior membro(32) + padding
// mesmo um int pequeno paga pelo espaço de 32 bytes da string

Armadilhas & quando usar

Pegadinhas

Quando unions brilham

  1. Economizar memória quando você sabe que só um campo está ativo (comum em sistemas embarcados)
  2. Tipos variant: valores JSON, nós de AST e mensagens de protocolo (combinados com uma tag)
  3. Inspecionar a representação em bits: verificações de endianness e padrões de bits
  4. Mapas de registradores de hardware: o mesmo registrador visto como bits e como palavra
Alternativas modernas: Se você só precisa economizar memória, um par void* + tamanho, o std::variant do C++ ou o enum do Rust é mais seguro. Em C, uma tagged union com funções wrapper é a escolha pragmática.

Desafios

Desafio 1: padrão de bits de um float
Use union { float f; uint32_t u; } para imprimir a representação hex de 32 bits de f = 1.0f (IEEE-754 dá 0x3F800000).
Desafio 2: is_little_endian()
Implemente a função do texto na sua máquina e verifique o resultado. A maioria dos desktops Mac, Linux e Windows é little-endian.
Desafio 3: Tipo de valor minúsculo
Construa uma tagged union struct Value que guarde int, float ou bool. Implemente Value add(Value a, Value b) de forma que tipos numéricos somem, dois bools façam OR e tipos incompatíveis exibam um erro.
Desafio 4: Cor RGBA
Crie uma union de um inteiro de 32 bits e quatro bytes de 8 bits (R, G, B, A). Empacote e desempacote 0xRRGGBBAA usando as duas visões.

Veja também

Teste de Revisão

Confira seu entendimento desta aula.

Q1. Qual afirmação sobre uma union está correta?

Todos os membros compartilham a mesma região de memória
Cada membro tem sua própria memória independente
Acessar qualquer membro diferente do último atribuído gera um erro

Uma union permite ver uma única região de memória como qualquer um de vários tipos. Apenas um membro é válido por vez.

Q2. Qual o tamanho típico de union U { int i; char c; };?

O tamanho do maior membro (int)
A soma de todos os membros
Sempre o mesmo tamanho de um ponteiro

O tamanho de uma union corresponde ao seu maior membro, podendo ter um pequeno padding para alinhamento.

Q3. Qual é um uso típico de uma union?

Uma tagged union que combina uma tag de tipo com os dados para guardar valores de múltiplos tipos
Implementar um array dinâmico
Sobrecarga de função

C não tem sobrecarga, então enum + union é o padrão comum para alternar entre tipos como int e float.