Confirme com sizeof que o tamanho corresponde ao maior membro
A verificação de endianness é o aprendizado mais prático
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];
};
intmain(void) {
union Data d;
d.i = 42;
printf("d.i = %d\n", d.i); // 42
d.f = 3.14f; // i fica inválido agoraprintf("d.f = %f\n", d.f);
printf("d.i = %d\n", d.i); // padrão de bits do floatprintf("sizeof = %zu\n", sizeof(d));// 8 (maior membro)return0;
}
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; };
intmain(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=8union 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 byteint i; // 4 bytesdouble d; // 8 bytes
};
// sizeof(union Mix) é pelo menos 8, alinhado para double
Os 4 bytes de int i = 1: little-endian (x86 etc.): 01 00 00 00 → c[0] == 1 big-endian (alguns ARM etc.): 00 00 00 01 → c[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;
};
voidprint_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;
}
}
intmain(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: 42print_value(b); // float: 3.14print_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
Apenas um membro é válido por vez — escrever em outro membro apaga o valor anterior.
Uma union sem tag é perigosa — não há como saber qual membro está ativo. Sempre use uma tagged union.
Type punning: ler um membro diferente do que foi escrito é comportamento definido para unions no C11 em diante. Já o punning via cast de ponteiro pode violar as regras de strict-aliasing.
float ↔ int via union fornece o padrão de bits, não uma conversão numérica.
Quando unions brilham
Economizar memória quando você sabe que só um campo está ativo (comum em sistemas embarcados)
Tipos variant: valores JSON, nós de AST e mensagens de protocolo (combinados com uma tag)
Inspecionar a representação em bits: verificações de endianness e padrões de bits
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.