Unions e Enums
Uma union é como um cabo de carregamento multiuso — você só pode usar uma interface por vez, mas economiza espaço. Um enum é como um número de cardápio — usar nomes em vez de números puros torna o código mais legível.
Definição de union e Memória Compartilhada
Todos os membros de uma union compartilham a mesma memória. Seu tamanho é igual ao tamanho do maior membro. Apenas um membro pode ser usado por vez.
union Data {
int i;
float f;
char str[4];
};
printf("%zu\n", sizeof(union Data));
4
Após atribuir ao membro int, ler o membro float dá uma interpretação diferente da mesma memória:
union Data d;
d.i = 42;
printf("i = %d\n", d.i);
printf("f = %f\n", d.f);
i = 42
f = 0.000000
union vs. struct
| Característica | struct | union |
|---|---|---|
| Memória | Cada membro tem seu próprio espaço | Todos os membros compartilham o mesmo espaço |
| Tamanho | Soma de todos os tamanhos dos membros (com padding de alinhamento) | Tamanho do maior membro |
| Acesso simultâneo | Todos os membros podem ser acessados ao mesmo tempo | Apenas um membro por vez |
| Modificação | Alterar um não afeta os outros | Alterar um afeta todos |
struct SData {
int i;
float f;
char str[4];
};
union UData {
int i;
float f;
char str[4];
};
printf("struct: %zu\n", sizeof(struct SData));
printf("union: %zu\n", sizeof(union UData));
struct: 12
union: 4
unions com Tag
Na prática, unions são tipicamente pareadas com um campo de tag que indica qual tipo está armazenado no momento:
typedef struct {
int type;
union {
int int_val;
float float_val;
char str_val[32];
} value;
} Variant;
void print_variant(const Variant *v) {
switch (v->type) {
case 0: printf("int: %d\n", v->value.int_val); break;
case 1: printf("float: %f\n", v->value.float_val); break;
case 2: printf("str: %s\n", v->value.str_val); break;
}
}
Exemplo
#include <stdio.h>
typedef struct {
int kind;
union {
int ival;
double dval;
} u;
} Number;
void print_number(const Number *n) {
if (n->kind == 0) {
printf("int: %d\n", n->u.ival);
} else {
printf("double: %.2f\n", n->u.dval);
}
}
int main(void) {
Number a = {0, .u.ival = 42};
Number b = {1, .u.dval = 3.14};
print_number(&a);
print_number(&b);
return 0;
}
int: 42
double: 3.14
Definição e Uso de enum
Enumerações usam nomes em vez de constantes inteiras, melhorando a legibilidade do código.
enum Color {
RED,
GREEN,
BLUE
};
enum Color favorite = GREEN;
if (favorite == GREEN) {
printf("Verde selecionado\n");
}
Verde selecionado
Especificando Valores de enum
enum Weekday {
MON = 1,
TUE,
WED,
THU,
FRI,
SAT = 100,
SUN
};
printf("MON=%d SAT=%d SUN=%d\n", MON, SAT, SUN);
MON=1 SAT=100 SUN=101
enum com switch
enum Direction { UP, DOWN, LEFT, RIGHT };
void move(enum Direction d) {
switch (d) {
case UP: printf("Mover para cima\n"); break;
case DOWN: printf("Mover para baixo\n"); break;
case LEFT: printf("Mover para esquerda\n"); break;
case RIGHT: printf("Mover para direita\n"); break;
}
}
int — você pode atribuir qualquer valor inteiro, e o compilador pode não alertar. Não abuse desse recurso.
Valores int Subjacentes dos Enums
Constantes enum são essencialmente constantes nomeadas do tipo int.
enum Status { OK = 200, NOT_FOUND = 404, ERROR = 500 };
printf("%d %d %d\n", OK, NOT_FOUND, ERROR);
200 404 500
Variáveis enum podem ser atribuídas a partir de inteiros e para inteiros (em C), mas é melhor usar valores enum para consistência de tipo:
enum Status s = OK;
int code = s;
#define para constantes: enums têm informação de tipo, exibem nomes simbólicos em depuradores e têm escopo mais controlável.
Usando union para Detectar Endianess
Endianess refere-se à ordem dos bytes de dados multibyte na memória. Uma union facilita a detecção:
int is_little_endian(void) {
union {
int i;
char c;
} u;
u.i = 1;
return u.c == 1;
}
Se o byte mais baixo é armazenado no endereço mais baixo (u.c == 1), é little-endian; caso contrário, é big-endian.
Exemplo
#include <stdio.h>
typedef union {
unsigned int i;
unsigned char bytes[4];
} EndianTest;
void print_byte_order(void) {
EndianTest t;
t.i = 0x12345678;
printf("Ordem dos bytes na memoria: ");
for (int i = 0; i < 4; i++) {
printf("%02X ", t.bytes[i]);
}
printf("\n");
if (t.bytes[0] == 0x78) {
printf("Little Endian\n");
} else {
printf("Big Endian\n");
}
}
int main(void) {
print_byte_order();
return 0;
}
Ordem dos bytes na memoria: 78 56 34 12
Little Endian
union para Economia de Memória na Prática
Quando uma struct tem múltiplos campos mutuamente exclusivos, use uma union para economizar espaço:
typedef struct {
int type;
union {
struct { int width, height; } rect;
struct { int radius; } circle;
struct { int side; } square;
} shape;
} Figure;
Figure f;
f.type = 1;
f.shape.circle.radius = 10;
Apenas uma das três formas é usada por vez. A union compartilha a memória, e o tamanho da struct é determinado pela maior forma.
❓ Perguntas Frequentes
P: Uma union pode usar múltiplos membros ao mesmo tempo? R: Não. Membros de union compartilham memória — escrever um sobrescreve os outros. Você só deve ler o membro que foi escrito por último.
P: Posso imprimir um nome de enum com %s? R: Não. Valores de enum são inteiros. Para imprimir nomes, você precisa escrever uma função ou array de mapeamento, ex:
const char *names[] = {"RED","GREEN","BLUE"}.
P: Qual a diferença entre enum e #define para constantes? R: enum tem um tipo (int), depuradores podem exibir nomes simbólicos e o escopo é mais controlável. #define é uma substituição do pré-processador sem informação de tipo. Prefira enum.
P: Por que usar union para detectar endianess é melhor que cast de ponteiro? R: São essencialmente a mesma coisa, mas a abordagem com union está mais alinhada com o espírito de segurança de tipos do C, evita problemas com a regra de strict aliasing e é mais legível.
📖 Resumo
- Todos os membros de union compartilham memória; o tamanho é igual ao do maior membro; apenas um membro é usado por vez
- Na prática, unions são pareadas com um campo de tag (tagged union) para distinguir o tipo atualmente válido
- enum substitui números mágicos por constantes nomeadas, melhorando a legibilidade; o tipo subjacente é int
- Valores de enum incrementam a partir de 0 por padrão; você pode especificar manualmente qualquer valor int
- Uma union pode detectar a endianess do sistema — a ordem de armazenamento dos bytes de dados multibyte
📝 Exercícios
- Defina
union IPAddresscontendounsigned int addreunsigned char bytes[4]. Digite um valor inteiro de IP e exiba-o tanto como inteiro quanto como quatro bytes separados - Defina um enum
enum Season { SPRING, SUMMER, AUTUMN, WINTER }e escreva uma função que retorne a string do nome da estação correspondente ao valor do enum - Use uma tagged union para implementar uma struct simples de forma geométrica com uma tag de tipo e union (retângulo/círculo), depois escreva uma função para calcular a área



