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.
- 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
switche o aviso-Wswitchajudam 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 atalhotypedef 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 quadro | Em C | Exemplo |
|---|---|---|
| O pacote com as quatro etiquetas | o enum (um tipo novo) | enum Status { … }; |
| Cada etiqueta | uma constante do enum (o nome técnico é enumerador) | PAGO |
| O espaço ao lado do pedido, onde vai uma etiqueta | uma variável do tipo enum Status | enum 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 tipoenum Status(as duas palavras, como emstruct Aluno), e guarda nela a opçãoENVIADO. - 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
%dmostra 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.
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
| Escrita | O 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 = 500 | Cada um com o seu valor. Não precisam estar em sequência: são os tamanhos dos copos de suco, em ml. |
GIGANTE = GRANDE + 300 | O valor pode ser uma conta com os nomes que vieram antes, desde que o compilador saiba o resultado na hora de compilar. |
TALVEZ = -1 | Números negativos também valem. |
NEGATIVO = NAO | Dois 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 umint. 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;
}
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.
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.
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.
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
| Forma | Exemplo | Quando usar |
|---|---|---|
enum com etiqueta | enum Status { PENDENTE, PAGO }; | Uma lista de opções que andam juntas (etapas, cores, dias). O switch avisa quando falta uma. |
enum sem etiqueta | enum { MAX_ALUNOS = 30 }; | Constantes inteiras soltas, em código que precisa compilar em C antigo (veja Constantes). |
constexpr C23 | constexpr int MAX_ALUNOS = 30; | Uma constante só, de qualquer tipo (até double). A melhor escolha para valores isolados no C23. |
#define | #define MAX_ALUNOS 30 | Troca de texto, sem tipo. Ainda muito comum, mas com as armadilhas da aula Pré-processador. |
enum com potências de 2 | NEGRITO = 1 << 0, ITALICO = 1 << 1 | Opções que se combinam (negrito e itálico ao mesmo tempo): aula Máscaras e Flags. |
Quando dá errado
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.
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.
$ 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).
$ 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
$ 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.
$ 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
| Escrita | Significado |
|---|---|
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_UVA | Prefixo 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:
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-Wallavisa quando falta uma opção (not handled in switch). Umdefaultcala 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 charescolhe o tipo de baixo;typedef enum { … } Status;encurta o nome.