C Enums

A lojinha da Ana começou a vender pela internet. Cada pedido passa por quatro etapas: pendente (esperando o pagamento), pago, enviado e entregue. Dá para guardar a etapa num int e combinar que 0 é pendente, 1 é pago, e assim por diante, mas aí o código fica cheio de números que ninguém lembra o que significam. O enum resolve isso: ele dá um nome a cada opção de uma lista. Nesta aula você vai criar o seu primeiro enum, usar com switch, mostrar o nome de cada opção na tela e conhecer as armadilhas, porque por baixo dos nomes ainda existe um número.

Você vai aprender
  • O que é um "número mágico" e por que ele atrapalha.
  • Como declarar um enum (enum Status { PENDENTE, PAGO, … };) e criar variáveis dele.
  • Que valores cada nome recebe, e como escolher outros (JAN = 1).
  • Como o switch e o aviso -Wswitch ajudam a não esquecer nenhuma opção.
  • Como mostrar o nome de cada opção com um array de textos.
  • Por que o C deixa guardar qualquer número num enum, e como se proteger.
  • O tipo escolhido do C23 (enum Status : unsigned char) e o atalho typedef enum.

Todos os programas desta aula foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa. A aula usa switch e arrays; se precisar, reveja antes.


A ideia: as etiquetas coloridas do quadro de pedidos

Na parede da lojinha há um quadro com os pedidos do dia. A Ana podia escrever um número ao lado de cada pedido (0, 1, 2, 3) e deixar uma legenda num canto: "0 = pendente, 1 = pago…". Funciona, até o dia em que alguém esquece a legenda, ou escreve um 7, que não quer dizer nada.

Ela preferiu comprar etiquetas impressas: PENDENTE, PAGO, ENVIADO, ENTREGUE. Só existem essas quatro, todo mundo lê sem legenda, e ninguém inventa uma quinta por engano.

No quadroEm CExemplo
O pacote com as quatro etiquetaso enum (um tipo novo)enum Status { … };
Cada etiquetauma constante do enum (o nome técnico é enumerador)PAGO
O espaço ao lado do pedido, onde vai uma etiquetauma variável do tipo enum Statusenum Status pedido;

O nome vem do inglês enumeration, "enumeração": uma lista em que você enumera, uma por uma, as opções possíveis. Use enum sempre que uma informação tem poucas opções conhecidas: os dias da semana, as cores do semáforo, o naipe de uma carta, o nível de dificuldade de um jogo.


1. O problema: números mágicos

Primeiro, o jeito sem enum. A etapa do pedido fica num int:

#include <stdio.h>

int main(void) {
    int status = 2;   /* 0 = pendente, 1 = pago, 2 = enviado, 3 = entregue */

    if (status == 2) {
        printf("Seu pedido saiu para entrega.\n");
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra magicos.c -o magicos
$ ./magicos
Seu pedido saiu para entrega.

O programa funciona, mas olhe o if (status == 2): quem lê precisa voltar ao comentário para saber o que é o 2. Um número solto no código, cujo significado só existe na cabeça de quem escreveu, é chamado de número mágico. Num programa grande, o mesmo 2 aparece em vinte lugares, e um dia alguém troca a ordem das etapas e esquece de mudar um deles.


2. Declarar um enum

Agora com enum. Cada etapa ganha um nome:

#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE };

int main(void) {
    enum Status s = ENVIADO;

    if (s == ENVIADO) {
        printf("Seu pedido saiu para entrega.\n");
    }
    printf("PENDENTE = %d\n", PENDENTE);
    printf("PAGO     = %d\n", PAGO);
    printf("ENVIADO  = %d\n", ENVIADO);
    printf("ENTREGUE = %d\n", ENTREGUE);
    printf("s        = %d\n", s);
    return 0;
}
enum Status
Começa a declaração de um tipo novo. Status é a etiqueta (tag) do enum, como na struct. Por costume, começa com maiúscula.
{ PENDENTE, PAGO, ENVIADO, ENTREGUE }
A lista de opções, separadas por vírgula (e não por ;, como os campos de uma struct). Por costume, os nomes vão em MAIÚSCULAS, como as constantes do #define, para quem lê saber que não são variáveis.
;
O ponto e vírgula depois da chave é obrigatório, igual ao da struct.
enum Status s = ENVIADO;
Cria a variável s, do tipo enum Status (as duas palavras, como em struct Aluno), e guarda nela a opção ENVIADO.
if (s == ENVIADO)
Compara com o nome. Agora o código se explica sozinho: "se o pedido foi enviado".
printf("%d", PENDENTE)
Cada nome é, por baixo, um número inteiro. O %d mostra esse número.
$ gcc -std=c23 -Wall -Wextra pedido.c -o pedido
$ ./pedido
Seu pedido saiu para entrega.
PENDENTE = 0
PAGO     = 1
ENVIADO  = 2
ENTREGUE = 3
s        = 2

Repare: o C deu os números sozinho, começando em 0 e somando 1 a cada nome. O programa faz exatamente o mesmo que o magicos.c (o ENVIADO vale 2), mas agora ninguém precisa de legenda.

Onde declarar o enum

Como a struct, o enum vai fora das funções, lá em cima, depois dos #include. Assim todas as funções do arquivo conhecem os nomes. Em projetos maiores, ele fica num arquivo .h (veja Arquivos .h e .c).


3. Escolher os valores

Às vezes o número por trás do nome importa. Os meses, por exemplo, deveriam começar em 1, e não em 0. Você pode escolher o valor de qualquer nome com =:

#include <stdio.h>

enum Mes { JAN = 1, FEV, MAR, ABR, MAI, JUN, JUL, AGO, SET, OUT, NOV, DEZ };

enum Tamanho { PEQUENO = 300, MEDIO = 500, GRANDE = 700, GIGANTE = GRANDE + 300 };

enum Resposta { NAO = 0, SIM = 1, TALVEZ = -1, NEGATIVO = NAO };

int main(void) {
    printf("JAN = %d, FEV = %d, DEZ = %d\n", JAN, FEV, DEZ);
    printf("MEDIO = %d ml, GIGANTE = %d ml\n", MEDIO, GIGANTE);
    printf("TALVEZ = %d, NEGATIVO = %d\n", TALVEZ, NEGATIVO);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra valores.c -o valores
$ ./valores
JAN = 1, FEV = 2, DEZ = 12
MEDIO = 500 ml, GIGANTE = 1000 ml
TALVEZ = -1, NEGATIVO = 0
EscritaO que acontece
JAN = 1, FEV, MAR, …Só o primeiro tem valor. Os seguintes continuam contando a partir dele: FEV = 2, MAR = 3… DEZ = 12.
PEQUENO = 300, MEDIO = 500Cada um com o seu valor. Não precisam estar em sequência: são os tamanhos dos copos de suco, em ml.
GIGANTE = GRANDE + 300O valor pode ser uma conta com os nomes que vieram antes, desde que o compilador saiba o resultado na hora de compilar.
TALVEZ = -1Números negativos também valem.
NEGATIVO = NAODois nomes podem ter o mesmo valor. É permitido, mas cuidado: no switch, os dois seriam o mesmo case.

Na dúvida, deixe o C numerar sozinho. Escolha valores só quando o número tem um significado fora do programa: o número do mês, o código de erro de um sistema, o valor gravado num arquivo.


4. enum e switch: o compilador confere a lista

O par mais comum do enum é o switch: um case para cada opção. E aqui o enum ganha um bônus. Como o compilador conhece a lista inteira, ele percebe quando você esqueceu uma opção. A Ana escreveu a função que mostra a etapa do pedido e se esqueceu do ENTREGUE:

#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE };

void mostrar(enum Status s) {
    switch (s) {
    case PENDENTE:
        printf("Aguardando pagamento");
        break;
    case PAGO:
        printf("Pagamento aprovado");
        break;
    case ENVIADO:
        printf("Saiu para entrega");
        break;
    }
}

int main(void) {
    for (enum Status s = PENDENTE; s <= ENTREGUE; s++) {
        printf("%d: ", s);
        mostrar(s);
        printf("\n");
    }
    return 0;
}
$ gcc -std=c23 -Wall -Wextra menu.c -o menu
menu.c: In function ‘mostrar’:
menu.c:6:5: warning: enumeration value ‘ENTREGUE’ not handled in switch [-Wswitch]
    6 |     switch (s) {
      |     ^~~~~~
$ ./menu
0: Aguardando pagamento
1: Pagamento aprovado
2: Saiu para entrega
3:                          « ENTREGUE: nenhum case, nada na tela

enumeration value ‘ENTREGUE’ not handled in switch = "o valor ENTREGUE do enum não foi tratado no switch". O aviso vem do -Wswitch, que o -Wall já liga. Com um int e números mágicos, o compilador não teria como saber que faltava o 3. No editor do guia, o Clang dá o mesmo aviso, com aspas retas: enumeration value 'ENTREGUE' not handled in switch.

for (enum Status s = PENDENTE; s <= ENTREGUE; s++)
Em C, uma variável de enum aceita ++ e <=, como um int. Este laço passa por todas as opções, de 0 a 3. Só funciona porque os valores estão em sequência, sem buracos.
printf("%d: ", s)
Mostra o número por trás da opção. Na seção 5 você vai mostrar o nome.

Missão: rode o programa e veja o aviso do compilador e a linha 3: vazia. Depois acrescente o case ENTREGUE: que falta, mostrando Entregue ao cliente, até o aviso sumir.

#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE };

void mostrar(enum Status s) {
    switch (s) {
    case PENDENTE:
        printf("Aguardando pagamento");
        break;
    case PAGO:
        printf("Pagamento aprovado");
        break;
    case ENVIADO:
        printf("Saiu para entrega");
        break;
    }
}

int main(void) {
    for (enum Status s = PENDENTE; s <= ENTREGUE; s++) {
        printf("%d: ", s);
        mostrar(s);
        printf("\n");
    }
    return 0;
}
#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE };

void mostrar(enum Status s) {
    switch (s) {
    case PENDENTE:
        printf("Aguardando pagamento");
        break;
    case PAGO:
        printf("Pagamento aprovado");
        break;
    case ENVIADO:
        printf("Saiu para entrega");
        break;
    case ENTREGUE:
        printf("Entregue ao cliente");
        break;
    }
}

int main(void) {
    for (enum Status s = PENDENTE; s <= ENTREGUE; s++) {
        printf("%d: ", s);
        mostrar(s);
        printf("\n");
    }
    return 0;
}
E o default?

Se o switch tem default:, o gcc não avisa mais nada: para ele, as opções esquecidas caem no default de propósito. Isso é cômodo, mas apaga a sua rede de proteção. Quando alguém acrescentar uma quinta etapa (CANCELADO), ela cai calada no default. Para ser avisado mesmo com default, compile com -Wswitch-enum:

#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE };

const char *descricao(enum Status s) {
    switch (s) {
    case PENDENTE: return "Aguardando pagamento";
    case PAGO:     return "Pagamento aprovado";
    default:       return "Outro";
    }
}

int main(void) {
    printf("%s\n", descricao(ENTREGUE));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra -Wswitch-enum comdefault.c -o comdefault
comdefault.c: In function ‘descricao’:
comdefault.c:6:5: warning: enumeration value ‘ENVIADO’ not handled in switch [-Wswitch-enum]
    6 |     switch (s) {
      |     ^~~~~~
comdefault.c:6:5: warning: enumeration value ‘ENTREGUE’ not handled in switch [-Wswitch-enum]

5. Mostrar o nome na tela

O printf não sabe que 2 se chama ENVIADO: os nomes do enum existem só no código-fonte, e o programa pronto guarda apenas os números. Para mostrar um texto, você monta um array de textos em que a posição é o valor do enum:

#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE, TOTAL_STATUS };

const char *NOME_STATUS[TOTAL_STATUS] = {
    [PENDENTE] = "pendente",
    [PAGO]     = "pago",
    [ENVIADO]  = "enviado",
    [ENTREGUE] = "entregue",
};

int main(void) {
    enum Status s = PAGO;
    printf("Pedido 1042: %s\n", NOME_STATUS[s]);

    s = ENTREGUE;
    printf("Pedido 1042: %s\n", NOME_STATUS[s]);

    printf("Há %d status possíveis.\n", TOTAL_STATUS);
    return 0;
}
TOTAL_STATUS
Um nome a mais, no fim da lista, que não é uma etapa de verdade. Como a contagem começa em 0, ele vale exatamente quantos nomes vieram antes: 4. Se um dia entrar uma etapa nova antes dele, o total se ajusta sozinho.
const char *NOME_STATUS[TOTAL_STATUS]
Um array de 4 textos (cada elemento aponta para um texto fixo; veja Ponteiros e Strings). O tamanho vem do enum.
[PAGO] = "pago"
Um designador de posição C99: "na posição PAGO (1), ponha o texto "pago"". É como os designadores de struct (.nome = …), só que com colchetes. Assim cada texto fica ao lado do seu nome, e a ordem das linhas não importa.
NOME_STATUS[s]
Usa o valor do enum como índice do array: com s = PAGO, é NOME_STATUS[1].
$ gcc -std=c23 -Wall -Wextra nomes.c -o nomes
$ ./nomes
Pedido 1042: pago
Pedido 1042: entregue
Há 4 status possíveis.
Garanta que nenhum nome ficou sem texto

Se alguém acrescentar CANCELADO ao enum e esquecer o texto, a posição dele no array fica com NULL. Um static_assert(sizeof NOME_STATUS / sizeof NOME_STATUS[0] == TOTAL_STATUS); sem o tamanho escrito nos colchetes impede o programa de compilar nesse caso. A aula static_assert mostra esse truque passo a passo.

Experimente: acrescente a etapa CANCELADO ao enum, antes do TOTAL_STATUS, com o texto "cancelado" no array. Mude o s para CANCELADO e veja o total passar para 5 sem você mexer nele. Depois tire o texto do array e rode de novo: o que aparece no lugar do nome?

#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE, TOTAL_STATUS };

const char *NOME_STATUS[TOTAL_STATUS] = {
    [PENDENTE] = "pendente",
    [PAGO]     = "pago",
    [ENVIADO]  = "enviado",
    [ENTREGUE] = "entregue",
};

int main(void) {
    enum Status s = PAGO;
    printf("Pedido 1042: %s (código %d)\n", NOME_STATUS[s], s);
    printf("Há %d status possíveis.\n", TOTAL_STATUS);
    return 0;
}

6. Por baixo, um int: o C não confere

O enum dá nomes aos números, mas não tranca a porta. Em C, uma variável de enum é um inteiro como outro qualquer, e aceita qualquer número:

#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE };

int main(void) {
    enum Status s = PAGO;
    int n = s;              /* enum vira int sem reclamar */
    enum Status t = 42;     /* e int vira enum, mesmo fora da lista */

    printf("sizeof(enum Status) = %zu\n", sizeof(enum Status));
    printf("n = %d, t = %d\n", n, t);
    s++;
    printf("depois de s++: %d\n", s);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra porbaixo.c -o porbaixo
$ ./porbaixo
sizeof(enum Status) = 4
n = 1, t = 42
depois de s++: 2

Nenhum aviso, nem no gcc nem no Clang do editor: t guardou 42, que não é etapa nenhuma. A variável ocupa 4 bytes, o tamanho de um int (veja sizeof). E o s++ levou PAGO (1) para 2, que por acaso é ENVIADO.

Número que vem de fora: confira antes de usar

Se o valor vem do teclado ou de um arquivo, nada garante que ele está na lista. Usar um número fora da lista como índice do NOME_STATUS lê memória que não é do array: um texto sem sentido, (null) ou uma falha de segmentação (veja Quando dá errado). Leia num int, confira se está entre 0 e TOTAL_STATUS - 1 e só então guarde no enum.

Diferença para o C++

Quem vem do C++ estranha: lá, enum Status t = 42; é erro, e o C++ ainda tem o enum class, que não se mistura com int. Em C, o enum é só uma lista de constantes inteiras com um nome de tipo.


7. Os nomes são de todos: use um prefixo

Os campos de uma struct só existem dentro dela (a.nome e p.nome não se confundem). Os nomes de um enum, não: eles ficam soltos no arquivo, como variáveis globais. Dois enums com o mesmo nome dentro dão erro:

#include <stdio.h>

enum Fruta { LARANJA, MACA, UVA };
enum Cor { VERMELHO, LARANJA, AZUL };

int main(void) {
    enum Cor c = AZUL;
    printf("%d\n", c);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra repetido.c -o repetido
repetido.c:4:22: error: redeclaration of enumerator ‘LARANJA’
    4 | enum Cor { VERMELHO, LARANJA, AZUL };
      |                      ^~~~~~~
repetido.c:3:14: note: previous definition of ‘LARANJA’ with type ‘enum Fruta’
    3 | enum Fruta { LARANJA, MACA, UVA };
      |              ^~~~~~~

redeclaration of enumerator ‘LARANJA’ = "o enumerador LARANJA foi declarado de novo"; a nota diz onde estava o primeiro. O Clang do editor diz redefinition of enumerator 'LARANJA'. A solução usada em quase todo programa C é um prefixo com o nome do enum: FRUTA_LARANJA e COR_LARANJA. Fica mais comprido, mas nunca colide, e quem lê sabe de que lista o nome veio.


8. C23 Escolher o tipo por trás do enum

Você viu que um enum Status ocupa 4 bytes, mesmo com só quatro opções. Num array de mil pedidos, são 4000 bytes para guardar números de 0 a 3. A partir do C23, você escolhe o tipo inteiro que fica por baixo, escrevendo : tipo depois do nome:

#include <stdio.h>

enum Status : unsigned char { PENDENTE, PAGO, ENVIADO, ENTREGUE };

enum Comum { A, B, C };

int main(void) {
    printf("sizeof(enum Comum)  = %zu\n", sizeof(enum Comum));
    printf("sizeof(enum Status) = %zu\n", sizeof(enum Status));
    enum Status pedidos[1000] = {0};
    printf("1000 pedidos ocupam %zu bytes\n", sizeof pedidos);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra tipo23.c -o tipo23
$ ./tipo23
sizeof(enum Comum)  = 4
sizeof(enum Status) = 1
1000 pedidos ocupam 1000 bytes

Com : unsigned char, cada pedido ocupa 1 byte (o editor do guia mostra os mesmos números). Isso importa em arrays grandes e em structs gravadas em arquivos binários, onde você quer saber o tamanho exato de cada campo. Pode ser qualquer tipo inteiro: unsigned char, short, uint8_t, int64_t (veja stdint.h). Em troca, o compilador confere se cada valor cabe no tipo:

$ gcc -std=c23 -Wall -Wextra fora23.c -o fora23
fora23.c:3:78: error: enumerator value outside the range of underlying type
    3 | enum Status : unsigned char { PENDENTE, PAGO, ENVIADO, ENTREGUE, ARQUIVADO = 300 };
      |                                                                              ^~~

enumerator value outside the range of underlying type = "valor do enumerador fora da faixa do tipo de baixo": um unsigned char vai só até 255. No editor, o Clang diz enumerator value is not representable in the underlying type 'unsigned char'.

Em versões antigas, o gcc aceita essa escrita como extensão, e o -pedantic (que avisa tudo o que está fora do padrão) reclama: ISO C does not support specifying ‘enum’ underlying types before C23. Para código que precisa compilar em C17 ou antes, deixe o enum sem tipo e guarde o valor num unsigned char quando o tamanho importar.


9. Um nome mais curto: typedef enum

Escrever enum Status toda hora cansa. Como na struct, o typedef cria um apelido de uma palavra só:

#include <stdio.h>

typedef enum { PENDENTE, PAGO, ENVIADO, ENTREGUE } Status;

int main(void) {
    Status s = ENTREGUE;
    printf("%d\n", s);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra typedefenum.c -o typedefenum
$ ./typedefenum
3

O enum aqui nem tem etiqueta: ele é conhecido só pelo apelido Status. Você vai encontrar as duas formas em programas reais. Sem o typedef, escrever só Status s; é erro (veja "Quando dá errado").


Formas diferentes de dar nome a números: qual usar

FormaExemploQuando usar
enum com etiquetaenum Status { PENDENTE, PAGO };Uma lista de opções que andam juntas (etapas, cores, dias). O switch avisa quando falta uma.
enum sem etiquetaenum { MAX_ALUNOS = 30 };Constantes inteiras soltas, em código que precisa compilar em C antigo (veja Constantes).
constexpr C23constexpr int MAX_ALUNOS = 30;Uma constante só, de qualquer tipo (até double). A melhor escolha para valores isolados no C23.
#define#define MAX_ALUNOS 30Troca de texto, sem tipo. Ainda muito comum, mas com as armadilhas da aula Pré-processador.
enum com potências de 2NEGRITO = 1 << 0, ITALICO = 1 << 1Opções que se combinam (negrito e itálico ao mesmo tempo): aula Máscaras e Flags.

Quando dá errado

"enumeration value ‘ENTREGUE’ not handled in switch"

O que aconteceu: é um aviso, não um erro: o programa compila e roda, mas o switch não tem case para uma das opções do enum (seção 4). Quando o valor chega, nada acontece.

Como resolver: acrescente o case que falta. Evite pôr um default só para calar o aviso: ele vai esconder também as opções que forem criadas no futuro.

"redeclaration of enumerator ‘LARANJA’"

O que aconteceu: o mesmo nome aparece em dois enums do mesmo arquivo (ou num enum e numa variável global). Os nomes do enum não ficam "dentro" dele (seção 7).

Como resolver: use um prefixo com o nome do enum: FRUTA_LARANJA, COR_LARANJA.

"unknown type name ‘Status’; use ‘enum’ keyword"
$ gcc -std=c23 -Wall -Wextra semtag.c -o semtag
semtag.c: In function ‘main’:
semtag.c:6:5: error: unknown type name ‘Status’; use ‘enum’ keyword to refer to the type
    6 |     Status s = PAGO;
      |     ^~~~~~
      |     enum 

O que aconteceu: o enum foi declarado como enum Status { … };, então o nome do tipo são as duas palavras. O gcc até sugere o conserto na última linha: escrever enum na frente. O Clang do editor diz must use 'enum' tag to refer to type 'Status'.

Como resolver: escreva enum Status s = PAGO;, ou crie o apelido com typedef (seção 9).

"incompatible types when initializing type ‘enum Status’ using type ‘char *’"
$ gcc -std=c23 -Wall -Wextra texto.c -o texto
texto.c: In function ‘main’:
texto.c:6:21: error: incompatible types when initializing type ‘enum Status’ using type ‘char *’
    6 |     enum Status s = "PAGO";
      |                     ^~~~~~

O que aconteceu: "PAGO", com aspas, é um texto (uma string), e não o nome do enum. "Tipos incompatíveis: inicializando um enum Status com um char *". O Clang do editor diz incompatible pointer to integer conversion initializing 'enum Status' with an expression of type 'char[5]'.

Como resolver: tire as aspas: enum Status s = PAGO;. Se o texto veio do usuário, compare com strcmp para descobrir qual opção é (if (strcmp(texto, "pago") == 0) s = PAGO;).

(null) ou falha de segmentação ao mostrar o nome
#include <stdio.h>

enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE, TOTAL_STATUS };

const char *NOME_STATUS[TOTAL_STATUS] = {
    "pendente", "pago", "enviado", "entregue",
};

int main(void) {
    int codigo;
    printf("Código do status (0 a 3): ");
    if (scanf("%d", &codigo) != 1) {
        return 1;
    }
    enum Status s = codigo;
    printf("Status: %s\n", NOME_STATUS[s]);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra foradolimite.c -o foradolimite
$ ./foradolimite
Código do status (0 a 3): 2
Status: enviado
$ ./foradolimite
Código do status (0 a 3): 7
Status: (null)
$ ./foradolimite
Código do status (0 a 3): 900000
Falha de segmentação     (imagem do núcleo gravada) ./foradolimite

O que aconteceu: o C aceitou 7 e 900000 no enum (seção 6), e o programa usou esses números como índice de um array de 4 posições. Com 7, ele leu um endereço vazio por acaso, e o printf do Linux escreve (null) para um texto nulo. Com 900000, foi parar numa memória que nem é do programa, e o sistema o derrubou. Ler fora do array é comportamento indefinido: o resultado muda de um computador para outro. No editor do guia, o 7 também dá (null) e o 900000 dá Falha de segmentação (código 139).

Como resolver: confira o número antes de usá-lo:

    if (codigo < 0 || codigo >= TOTAL_STATUS) {
        printf("Código inválido: %d\n", codigo);
        return 1;
    }
    enum Status s = codigo;
$ ./validar
Código do status (0 a 3): 900000
Código inválido: 900000
$ ./validar
Código do status (0 a 3): 1
Status: pago
"format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘enum Status *’"
$ gcc -std=c23 -Wall -Wextra lerenum.c -o lerenum
lerenum.c: In function ‘main’:
lerenum.c:7:17: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘enum Status *’ [-Wformat=]
    7 |     if (scanf("%d", &s) != 1) {
      |                ~^   ~~
      |                 |   |
      |                 |   enum Status *
      |                 int *
      |                %d

O que aconteceu: o scanf("%d", …) espera o endereço de um int, e recebeu o de um enum Status. O padrão do C deixa cada compilador escolher o tipo inteiro por trás de um enum (o gcc escolhe unsigned int quando não há negativos), e ele pode não ser exatamente um int. O Clang do editor não dá esse aviso, mas o problema é o mesmo.

Como resolver: leia num int, confira a faixa e depois guarde no enum, como no validar.c acima.

"case value ‘7’ not in enumerated type ‘enum Status’"
$ gcc -std=c23 -Wall -Wextra casefora.c -o casefora
casefora.c: In function ‘main’:
casefora.c:12:5: warning: case value ‘7’ not in enumerated type ‘enum Status’ [-Wswitch]
   12 |     case 7:        printf("cancelado\n"); break;
      |     ^~~~

O que aconteceu: um case com um número que não é nenhuma opção do enum: "o valor 7 do case não está no tipo enum Status". Em geral, é uma opção nova que alguém quis tratar sem acrescentá-la ao enum.

Como resolver: acrescente a opção ao enum (CANCELADO) e use o nome no case.


Referência rápida

EscritaSignificado
enum Status { PENDENTE, PAGO, ENVIADO };Declara o tipo; os nomes valem 0, 1, 2 (não esqueça o ;).
enum Mes { JAN = 1, FEV, MAR };Escolhe o primeiro valor; os seguintes continuam: 1, 2, 3.
enum Status s = PAGO;Cria uma variável do enum.
switch (s) { case PAGO: … }Um case por opção; o -Wall avisa se faltar uma.
…, TOTAL_STATUS };Último nome extra: vale a quantidade de opções.
const char *NOME[TOTAL] = { [PAGO] = "pago" };Texto de cada opção, por posição (C99).
printf("%d", s)Mostra o número por trás da opção.
enum Status : unsigned char { … };C23: escolhe o tipo de baixo (aqui, 1 byte).
typedef enum { … } Status;Apelido: Status s; sem a palavra enum.
COR_AZUL, FRUTA_UVAPrefixo para os nomes de enums diferentes não colidirem.

Exercícios

1. Declarar o enum

Complete para criar um enum Naipe com as opções COPAS, ESPADAS, OUROS e PAUS, nessa ordem:

enum Naipe { };

2. Quanto vale?

Com enum Nivel { FACIL = 1, MEDIO, DIFICIL };, quanto vale DIFICIL?

3. A variável

Complete para criar a variável n do tipo do exercício 2, valendo MEDIO:

4. Com enum Cor { VERDE, AMARELO, VERMELHO, TOTAL_CORES };, quanto vale TOTAL_CORES?

5. O que o gcc faz com enum Status s = 42;, se o enum só tem os valores 0 a 3?

6. Um switch sobre um enum Status não tem case ENTREGUE nem default. O que o -Wall faz?

Missão final: o semáforo

Missão: complete a função proximo com um switch: depois do VERDE vem o AMARELO, depois do AMARELO vem o VERMELHO, e depois do VERMELHO volta o VERDE. Hoje ela devolve o mesmo sinal, e a tela fica toda verde. A saída certa é:

verde → amarelo → vermelho → verde → amarelo → vermelho → verde

Dica: como TOTAL_SINAIS também é um nome do enum, o -Wswitch vai pedir um case para ele. Um default: resolve.

#include <stdio.h>

enum Sinal { VERDE, AMARELO, VERMELHO, TOTAL_SINAIS };

const char *NOME_SINAL[TOTAL_SINAIS] = {
    [VERDE]    = "verde",
    [AMARELO]  = "amarelo",
    [VERMELHO] = "vermelho",
};

enum Sinal proximo(enum Sinal s) {
    /* devolva o sinal que vem depois de s */
    return s;
}

int main(void) {
    enum Sinal s = VERDE;
    for (int i = 0; i < 7; i++) {
        printf("%s", NOME_SINAL[s]);
        if (i < 6) {
            printf(" → ");
        }
        s = proximo(s);
    }
    printf("\n");
    return 0;
}
#include <stdio.h>

enum Sinal { VERDE, AMARELO, VERMELHO, TOTAL_SINAIS };

const char *NOME_SINAL[TOTAL_SINAIS] = {
    [VERDE]    = "verde",
    [AMARELO]  = "amarelo",
    [VERMELHO] = "vermelho",
};

enum Sinal proximo(enum Sinal s) {
    switch (s) {
    case VERDE:
        return AMARELO;
    case AMARELO:
        return VERMELHO;
    case VERMELHO:
        return VERDE;
    default:
        return VERMELHO;
    }
}

int main(void) {
    enum Sinal s = VERDE;
    for (int i = 0; i < 7; i++) {
        printf("%s", NOME_SINAL[s]);
        if (i < 6) {
            printf(" → ");
        }
        s = proximo(s);
    }
    printf("\n");
    return 0;
}

Por que o default devolve VERMELHO? Se um valor estranho chegar (um 42, por exemplo), o mais seguro para um semáforo é parar o trânsito.


Resumo

  • Um enum dá nomes a uma lista de opções: enum Status { PENDENTE, PAGO, ENVIADO, ENTREGUE };. Os nomes trocam os números mágicos do código.
  • Os valores começam em 0 e sobem de 1 em 1; com = você escolhe outros (JAN = 1), e os seguintes continuam a contagem.
  • Com switch, o -Wall avisa quando falta uma opção (not handled in switch). Um default cala esse aviso.
  • Para mostrar o nome, use um array de textos indexado pelo enum, e um último nome TOTAL_… para saber quantas opções existem.
  • Por baixo, é um inteiro: o C aceita qualquer número num enum. Confira a faixa antes de usar um valor que veio de fora.
  • Os nomes do enum são globais: use prefixos (COR_AZUL). No C23, enum Status : unsigned char escolhe o tipo de baixo; typedef enum { … } Status; encurta o nome.