C Encerrar o Programa: exit e atexit
Todo programa termina, e ao terminar ele deixa um recado para quem o chamou: um número que diz "deu certo" ou "deu errado". É esse número que você vem escrevendo no return 0; do main. Nesta aula você vai ver quem lê esse recado, como encerrar o programa de qualquer lugar com exit, o que acontece com os arquivos e com a saída no momento do fim, e como deixar uma função de "fechar a loja" que roda sozinha quando o programa termina, com atexit.
- O que é o código de saída e como vê-lo no terminal com
echo $?. - Por que
return 1;nomaineexit(1);são a mesma coisa, e quando só oexitresolve. EXIT_SUCCESS,EXIT_FAILUREe os valores de 0 a 255.- O que o
exitfaz antes de sair, e o que se perde com_Exiteabort. - Como registrar funções de limpeza com
atexit, e em que ordem elas rodam. - O atributo
[[noreturn]]C23 para funções que nunca voltam.
Você vai precisar de Funções, Argumentos da Linha de Comando e Arquivos. Todos os programas foram compilados com o gcc 15.2 na máquina fictícia da Ana (usuária ana, computador notebook), na pasta ~/c, com gcc -std=c23 -Wall -Wextra arquivo.c -o programa.
A ideia: o recado no fim do expediente
Quando a funcionária da lojinha vai embora, ela deixa um bilhete para a Ana: "tudo certo" ou "faltou troco no caixa". A Ana não precisa saber de tudo o que aconteceu no dia; basta o bilhete para decidir o que fazer amanhã. E, antes de sair, a funcionária segue um ritual: apaga as luzes, fecha o caixa, tranca a porta.
Um programa faz o mesmo. O bilhete é o código de saída (um número: 0 = tudo certo, outro número = algo deu errado). O ritual são as tarefas de fim: esvaziar o que ainda não foi escrito na tela, fechar os arquivos e rodar as funções que você pediu com atexit.
1. O código de saída e o echo $?
Este programa confere se um arquivo existe e devolve um código diferente para cada situação:
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Uso: %s ARQUIVO\n", argv[0]);
return 2;
}
FILE *f = fopen(argv[1], "r");
if (f == NULL) {
printf("%s: não encontrei\n", argv[1]);
return 1;
}
fclose(f);
printf("%s: existe\n", argv[1]);
return 0;
}
- return 2;
- Chamado do jeito errado (faltou o nome do arquivo). Muitos comandos do Linux usam 2 para "uso errado".
- return 1;
- O arquivo não existe: deu errado.
- return 0;
- Tudo certo. 0 é sucesso, ao contrário do que a intuição diz: há um jeito só de dar certo e muitos de dar errado, e cada um pode ganhar seu número.
O terminal guarda o código do último programa numa variável especial do shell, a $?. O comando echo $? mostra o valor:
$ gcc -std=c23 -Wall -Wextra existe.c -o existe $ ./existe lista.txt lista.txt: existe $ echo $? 0 $ ./existe compras.txt compras.txt: não encontrei $ echo $? 1 $ ./existe Uso: ./existe ARQUIVO $ echo $? 2
Quem lê o código não é a pessoa, é outro programa: o shell, um script, o make. No Bash, && quer dizer "se o anterior deu certo (0), rode este" e ||, "se deu errado, rode este":
$ ./existe lista.txt && echo "Pode abrir." lista.txt: existe Pode abrir. $ ./existe compras.txt && echo "Pode abrir." compras.txt: não encontrei « deu 1: o echo não rodou $ ./existe compras.txt || echo "Crie o arquivo primeiro." compras.txt: não encontrei Crie o arquivo primeiro.
É assim que o make sabe que o gcc falhou e para tudo (aula Makefile), e é por isso que um programa que deu errado não deve terminar com 0, mesmo que já tenha mostrado a mensagem de erro.
[código de saída: 1]. Não há echo $?, porque não há um shell de verdade.
2. exit: encerrar de dentro de uma função
O return sai da função em que está. No main, sair da função é sair do programa. Mas, numa função qualquer, o return só volta para quem chamou, e o programa continua. Quando um erro é tão grave que não faz sentido continuar, use exit (do stdlib.h), que encerra o programa inteiro, de qualquer lugar:
#include <stdio.h>
#include <stdlib.h>
int ler_volume(const char *arquivo) {
FILE *f = fopen(arquivo, "r");
if (f == NULL) {
perror(arquivo);
exit(EXIT_FAILURE);
}
int volume = 0;
if (fscanf(f, "volume=%d", &volume) != 1) {
fprintf(stderr, "%s: formato errado\n", arquivo);
fclose(f);
exit(EXIT_FAILURE);
}
fclose(f);
return volume;
}
int main(void) {
int volume = ler_volume("config.txt");
printf("Volume: %d\n", volume);
return EXIT_SUCCESS;
}
- #include <stdlib.h>
- Onde moram
exit,EXIT_SUCCESSeEXIT_FAILURE. - perror(arquivo);
- Mostra o motivo da falha do
fopen(aula errno e perror). - exit(EXIT_FAILURE);
- Encerra o programa aqui mesmo, com o código de "falhou". O
mainnunca recebe um volume inventado, e oprintf("Volume…")não roda. - fclose(f); exit(EXIT_FAILURE);
- O
exitfecharia o arquivo sozinho (seção 4), mas fechar o que você abriu é um bom hábito. - return EXIT_SUCCESS;
- O mesmo que
return 0;, com um nome que diz o que significa.
$ gcc -std=c23 -Wall -Wextra config.c -o config $ ./config config.txt: No such file or directory $ echo $? 1 $ echo 'volume=7' > config.txt $ ./config Volume: 7 $ echo $? 0 $ echo 'vol 7' > config.txt $ ./config config.txt: formato errado $ echo $? 1
EXIT_SUCCESS e EXIT_FAILURE são constantes do stdlib.h. No Linux, no Windows, no macOS e no editor do guia, valem 0 e 1:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
printf("EXIT_SUCCESS = %d\n", EXIT_SUCCESS);
printf("EXIT_FAILURE = %d\n", EXIT_FAILURE);
return 0;
}
$ gcc -std=c23 -Wall -Wextra valores.c -o valores $ ./valores EXIT_SUCCESS = 0 EXIT_FAILURE = 1
E no main? Lá, return N; e exit(N); fazem a mesma coisa: o C define que voltar do main é chamar exit com o valor devolvido. E se o main chega ao } sem return, é como return 0; C99 (aula Retorno). Use return no main e exit fora dele.
Missão: esta caixa não tem o config.txt, então o fopen falha. Mas a função faz return 0; e o programa segue mostrando Volume: 0, terminando com código 0 como se estivesse tudo bem. Troque esse return 0; por exit(EXIT_FAILURE); e rode: a última linha deve ser [código de saída: 1], sem o "Volume".
#include <stdio.h>
#include <stdlib.h>
int ler_volume(const char *arquivo) {
FILE *f = fopen(arquivo, "r");
if (f == NULL) {
perror(arquivo);
return 0;
}
int volume = 0;
if (fscanf(f, "volume=%d", &volume) != 1) {
volume = 0;
}
fclose(f);
return volume;
}
int main(void) {
int volume = ler_volume("config.txt");
printf("Volume: %d\n", volume);
return EXIT_SUCCESS;
}
#include <stdio.h>
#include <stdlib.h>
int ler_volume(const char *arquivo) {
FILE *f = fopen(arquivo, "r");
if (f == NULL) {
perror(arquivo);
exit(EXIT_FAILURE);
}
int volume = 0;
if (fscanf(f, "volume=%d", &volume) != 1) {
volume = 0;
}
fclose(f);
return volume;
}
int main(void) {
int volume = ler_volume("config.txt");
printf("Volume: %d\n", volume);
return EXIT_SUCCESS;
}
3. Os valores: de 0 a 255
O código de saída é um int no C, mas o Linux só guarda um byte dele: de 0 a 255. Valores maiores "dão a volta" (o resto da divisão por 256):
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
return 2;
}
int n = atoi(argv[1]);
exit(n);
}
$ gcc -std=c23 -Wall -Wextra codigos.c -o codigos $ ./codigos 255; echo $? 255 $ ./codigos 256; echo $? 0 « 256 deu a volta e virou "sucesso"! $ ./codigos 300; echo $? 44 « 300 - 256 $ ./codigos -1; echo $? 255
O ; no terminal só separa dois comandos na mesma linha. Repare no perigo do 256: um programa que devolvesse "a quantidade de erros" diria "sucesso" com 256 erros. Por isso, use números pequenos. As convenções do Linux:
| Código | Quem usa | Significado |
|---|---|---|
| 0 | Todos | Deu certo |
| 1 | A maioria dos programas | Erro geral |
| 2 | Muitos comandos (ls, grep, o Bash) | Uso errado ou problema sério |
| 126, 127 | O shell | O programa não pôde rodar / não foi encontrado |
| 128 + N | O shell | O programa morreu pelo sinal N: 134 = 128 + 6 (abort), 139 = 128 + 11 (falha de segmentação) |
$ ls /nao-existe; echo $? ls: não foi possível acessar '/nao-existe': Arquivo ou diretório inexistente 2 $ nao-existe; echo $? bash: nao-existe: comando não encontrado 127
Experimente: o campo Argumentos começa com 300. Rode e veja o [código de saída: 44]. Depois tente 256, -1, 255 e 1000. O editor faz a mesma conta que o Linux.
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
return 2;
}
int n = atoi(argv[1]);
exit(n);
}
4. O que o exit faz antes de sair
O exit não "desliga a tomada". Ele segue um ritual, nesta ordem:
- Roda as funções registradas com
atexit(seção 5), da última para a primeira. - Esvazia os buffers: tudo o que o
printfe ofprintfguardaram e ainda não escreveram vai para a tela ou para o arquivo. - Fecha todos os arquivos abertos.
- Entrega o código de saída ao sistema.
O passo 2 importa porque o printf não escreve na hora: ele junta o texto num buffer (uma área de espera) e escreve de uma vez, no \n ou quando o buffer enche (aula stdin, stdout e stderr). Para ver o ritual funcionando, compare exit com _Exit C99, que sai sem ritual nenhum:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
FILE *f = fopen("vendas.txt", "w");
if (f == NULL) {
perror("vendas.txt");
return 1;
}
fprintf(f, "3 pães\n");
printf("Venda anotada");
exit(0);
}
O programa não fecha o arquivo e o printf não tem \n: os dois textos ainda estão nos buffers quando o exit é chamado. O fechar2.c é igual, com _Exit(0); na última linha.
$ gcc -std=c23 -Wall -Wextra fechar.c -o fechar
$ ./fechar; echo; cat vendas.txt
Venda anotada
3 pães
$ rm vendas.txt
$ gcc -std=c23 -Wall -Wextra fechar2.c -o fechar2
$ ./fechar2; echo; wc -c vendas.txt
« nada na tela
0 vendas.txt « o arquivo foi criado, mas ficou com 0 bytes
- ./fechar; echo; cat vendas.txt
- Roda o programa, pula uma linha (o
echosozinho só escreve um Enter, porque oprintfnão tinha\n) e mostra o arquivo. - exit(0)
- Esvaziou os dois buffers: o "Venda anotada" apareceu e o "3 pães" foi gravado.
- _Exit(0)
- Saiu na hora. O que estava nos buffers se perdeu: nem a tela nem o arquivo receberam nada.
wc -cconta os bytes do arquivo.
O abort() também sai sem ritual, e ainda avisa o sistema de que o programa morreu de propósito, por um erro grave (é o que o assert usa quando falha; aula Testes). O código é 134:
#include <stdio.h>
#include <stdlib.h>
void tchau(void) {
printf("Até logo!\n");
}
int main(void) {
atexit(tchau);
printf("Algo muito errado aconteceu.\n");
abort();
}
$ gcc -std=c23 -Wall -Wextra aborta.c -o aborta $ ./aborta Algo muito errado aconteceu. Abortado (imagem do núcleo gravada) ./aborta $ echo $? 134
O "Até logo!" registrado com atexit não apareceu: o abort não roda o ritual. O mesmo vale quando o programa morre sozinho (falha de segmentação, divisão por zero): nenhuma função de atexit roda.
| Como termina | Roda o atexit? | Esvazia buffers e fecha arquivos? | Código |
|---|---|---|---|
return N; no main | Sim | Sim | N |
exit(N); | Sim | Sim | N |
_Exit(N); C99 | Não | Não | N |
abort(); | Não | Não | 134 |
| Falha de segmentação | Não | Não | 139 |
5. atexit: o ritual de fim do expediente
atexit = "at exit", "na saída". Você passa o nome de uma função, e o C guarda esse nome para chamá-la quando o programa terminar normalmente (pelo return do main ou por um exit, de onde for):
#include <stdio.h>
#include <stdlib.h>
void apagar_luzes(void) {
printf("Luzes apagadas.\n");
}
void fechar_caixa(void) {
printf("Caixa fechado.\n");
}
void trancar_porta(void) {
printf("Porta trancada.\n");
}
int main(void) {
atexit(trancar_porta);
atexit(fechar_caixa);
atexit(apagar_luzes);
printf("Lojinha aberta.\n");
printf("Fim do expediente.\n");
return 0;
}
- void apagar_luzes(void)
- Uma função de
atexittem sempre essa forma: não recebe nada e não devolve nada. - atexit(trancar_porta);
- Registra a função, sem parênteses depois do nome: você não está chamando
trancar_portaagora, está entregando a função para ser chamada depois (um ponteiro para função, aula Ponteiros para Função). - return 0;
- Ao terminar, o C chama as funções registradas na ordem inversa: a última registrada roda primeiro.
$ gcc -std=c23 -Wall -Wextra ordem.c -o ordem $ ./ordem Lojinha aberta. Fim do expediente. Luzes apagadas. Caixa fechado. Porta trancada.
Por que de trás para a frente? Pense numa pilha de pratos: o último que você pôs é o primeiro que tira. Na vida real, quem abre a loja destranca a porta, depois abre o caixa, depois acende as luzes; para fechar, desfaz na ordem contrária. O atexit segue essa lógica: o que foi preparado por último é desfeito primeiro.
atexit é útil para limpezas que precisam acontecer por qualquer caminho de saída: salvar um arquivo de estado, apagar um arquivo temporário, mostrar um resumo. Se o programa tem dez exit espalhados, você não precisa lembrar da limpeza em cada um. O C garante pelo menos 32 registros. Como a função não recebe parâmetros, o que ela usa costuma ficar numa variável global (aula Escopo).
Missão: a Ana registrou as funções na ordem em que pensou nelas, e o fim saiu ao contrário: a porta foi trancada com as luzes ainda acesas. Mude a ordem dos três atexit para que o programa termine com Luzes apagadas., Caixa fechado. e Porta trancada., nessa ordem.
#include <stdio.h>
#include <stdlib.h>
void apagar_luzes(void) {
printf("Luzes apagadas.\n");
}
void fechar_caixa(void) {
printf("Caixa fechado.\n");
}
void trancar_porta(void) {
printf("Porta trancada.\n");
}
int main(void) {
atexit(apagar_luzes);
atexit(fechar_caixa);
atexit(trancar_porta);
printf("Lojinha aberta.\n");
printf("Fim do expediente.\n");
return 0;
}
#include <stdio.h>
#include <stdlib.h>
void apagar_luzes(void) {
printf("Luzes apagadas.\n");
}
void fechar_caixa(void) {
printf("Caixa fechado.\n");
}
void trancar_porta(void) {
printf("Porta trancada.\n");
}
int main(void) {
atexit(trancar_porta);
atexit(fechar_caixa);
atexit(apagar_luzes);
printf("Lojinha aberta.\n");
printf("Fim do expediente.\n");
return 0;
}
6. [[noreturn]]: a função que nunca volta
É comum criar uma função "erro fatal" que mostra a mensagem e chama exit. O compilador, porém, não sabe que ela nunca volta, e reclama de quem a usa:
#include <stdio.h>
#include <stdlib.h>
void erro_fatal(const char *msg) {
fprintf(stderr, "Erro: %s\n", msg);
exit(EXIT_FAILURE);
}
int dividir(int a, int b) {
if (b != 0) {
return a / b;
}
erro_fatal("divisão por zero");
}
int main(void) {
printf("10 / 2 = %d\n", dividir(10, 2));
printf("10 / 0 = %d\n", dividir(10, 0));
return 0;
}
$ gcc -std=c23 -Wall -Wextra fatal.c -o fatal
fatal.c: In function ‘dividir’:
fatal.c:14:1: warning: control reaches end of non-void function [-Wreturn-type]
14 | }
| ^
"O controle chega ao fim de uma função que não é void": o gcc acha que, depois do erro_fatal, a dividir termina sem devolver nada. Para contar a verdade ao compilador, marque a função com o atributo [[noreturn]] C23 ("não volta"; aula Atributos):
[[noreturn]] void erro_fatal(const char *msg) {
fprintf(stderr, "Erro: %s\n", msg);
exit(EXIT_FAILURE);
}
$ gcc -std=c23 -Wall -Wextra fatal2.c -o fatal2 $ ./fatal2 10 / 2 = 5 Erro: divisão por zero $ echo $? 1
Sem aviso. O próprio exit é declarado assim no stdlib.h: se a dividir chamasse exit(EXIT_FAILURE); direto, no lugar do erro_fatal, o gcc também não reclamaria. Antes do C23, escrevia-se _Noreturn void erro_fatal(…) C11, ou noreturn com #include <stdnoreturn.h>; essas formas ainda funcionam.
Formas diferentes, e qual usar
| Forma | Quando usar |
|---|---|
return 0; / return 1; no main | O jeito normal de terminar |
return EXIT_SUCCESS; / EXIT_FAILURE | O mesmo, com nomes que se explicam; comum em código que quer ser portátil |
exit(EXIT_FAILURE); numa função | Erro grave, longe do main, em que não faz sentido continuar |
Devolver um código de erro e deixar o main decidir | Funções que outros programas vão reaproveitar (uma biblioteca não deve encerrar o programa de quem a usa) |
atexit(funcao); | Limpeza que tem de acontecer por qualquer caminho de saída |
_Exit(N); | Raro: sair sem limpar nada (por exemplo, num processo filho depois de um fork) |
abort(); | Um erro "impossível", do programador: melhor morrer na hora e deixar o rastro (o assert faz isso) |
Quando dá errado
error: implicit declaration of function ‘exit’
$ gcc -std=c23 -Wall -Wextra semstdlib.c -o semstdlib
semstdlib.c: In function ‘main’:
semstdlib.c:5:5: error: implicit declaration of function ‘exit’ [-Wimplicit-function-declaration]
5 | exit(1);
| ^~~~
semstdlib.c:2:1: note: include ‘<stdlib.h>’ or provide a declaration of ‘exit’
1 | #include <stdio.h>
+++ |+#include <stdlib.h>
2 |
semstdlib.c:5:5: warning: incompatible implicit declaration of built-in function ‘exit’ [-Wbuiltin-declaration-mismatch]
5 | exit(1);
| ^~~~
semstdlib.c:5:5: note: include ‘<stdlib.h>’ or provide a declaration of ‘exit’
O que significa: o programa usa exit sem o #include <stdlib.h>. O próprio gcc mostra onde pôr a linha (o +++ |+). No editor, o Clang diz call to undeclared library function 'exit' e include the header <stdlib.h>.
Como consertar: #include <stdlib.h> no topo. O mesmo vale para atexit, _Exit, abort e as constantes EXIT_….
error: passing argument 1 of ‘atexit’ from incompatible pointer type
$ gcc -std=c23 -Wall -Wextra assinatura.c -o assinatura
assinatura.c: In function ‘main’:
assinatura.c:9:12: error: passing argument 1 of ‘atexit’ from incompatible pointer type [-Wincompatible-pointer-types]
9 | atexit(tchau);
| ^~~~~
| |
| void (*)(int)
In file included from assinatura.c:2:
/usr/include/stdlib.h:756:27: note: expected ‘void (*)(void)’ but argument is of type ‘void (*)(int)’
756 | extern int atexit (void (*__func) (void)) __THROW __nonnull ((1));
| ~~~~~~~^~~~~~~~~~~~~~
assinatura.c:4:6: note: ‘tchau’ declared here
4 | void tchau(int codigo) {
| ^~~~~
O que significa: a função tchau recebe um int, e o atexit só aceita funções void f(void). O void (*)(void) da nota é "ponteiro para função que não recebe nada e não devolve nada".
Como consertar: escreva a função como void tchau(void). Se ela precisa de um valor, guarde-o numa variável global antes do exit.
error: invalid use of void expression no atexit
$ gcc -std=c23 -Wall -Wextra parenteses.c -o parenteses
parenteses.c: In function ‘main’:
parenteses.c:9:12: error: invalid use of void expression
9 | atexit(tchau());
| ^~~~~~~
O que significa: "uso inválido de uma expressão void". com os parênteses, você chamou a tchau agora e tentou passar o resultado dela (que é void, nada) para o atexit.
Como consertar: atexit(tchau);, só o nome.
printf sumiu
O que significa: o programa terminou com _Exit ou abort (ou morreu por um erro) enquanto o texto ainda estava no buffer. Foi o que aconteceu com o fechar2: nem a tela nem o vendas.txt receberam nada.
Como consertar: termine com return ou exit. Se precisa mesmo sair sem ritual, chame fflush(stdout); e fclose antes.
warning: ‘noreturn’ function does return
$ gcc -std=c23 -Wall -Wextra noreturnerr.c -o noreturnerr
noreturnerr.c: In function ‘erro_fatal’:
noreturnerr.c:6:1: warning: ‘noreturn’ function does return
6 | }
| ^
$ ./noreturnerr
Erro: teste
Instrução ilegal (imagem do núcleo gravada) ./noreturnerr
$ echo $?
132
O que significa: a função foi marcada [[noreturn]], mas esqueceram o exit dentro dela. Voltar de uma função [[noreturn]] é comportamento indefinido: o gcc confiou na promessa, não preparou a volta e pôs uma "instrução ilegal" no lugar. No editor do guia, o mesmo programa morre com Falha de segmentação (139), e o Clang avisa com function declared 'noreturn' should not return.
Como consertar: toda função [[noreturn]] tem de terminar com exit, abort ou um laço infinito.
make (ou o script) continuou como se estivesse tudo bem
O que significa: depois da mensagem de erro, o programa terminou com 0 (esqueceram o return 1;, ou o main chegou ao fim). Quem chama só olha o código.
Como consertar: todo caminho de erro termina com um código diferente de 0. Teste com ./programa; echo $?.
echo $? mostrou 0
$ ./existe compras.txt compras.txt: não encontrei $ echo $? 1 $ echo $? 0
O que significa: o $? guarda o código do último comando, e o último foi o primeiro echo, que deu certo.
Como consertar: olhe o $? logo depois do programa, ou guarde-o: ./existe x; codigo=$?.
Referência rápida
| Escrita | Cabeçalho | O que faz |
|---|---|---|
return N; no main | — | Termina o programa com o código N (o mesmo que exit(N)) |
exit(N); | stdlib.h | Termina de qualquer lugar: roda o atexit, esvazia buffers, fecha arquivos |
EXIT_SUCCESS, EXIT_FAILURE | stdlib.h | 0 e 1 (os códigos portáteis de sucesso e falha) |
atexit(f); | stdlib.h | Registra void f(void) para o fim; ordem inversa; devolve 0 se deu certo |
_Exit(N); C99 | stdlib.h | Termina na hora, sem ritual |
abort(); | stdlib.h | Termina por erro grave, sem ritual; código 134 |
quick_exit(N);, at_quick_exit(f); C11 | stdlib.h | Saída rápida que roda só as funções de at_quick_exit (raro) |
[[noreturn]] void f(…) C23 | — | Promete que a função nunca volta |
echo $? | (shell) | Mostra o código de saída do último comando |
prog && outro / prog || outro | (shell) | Roda outro se prog deu certo / deu errado |
Exercícios
1. Sucesso
Qual código de saída quer dizer "deu certo"?
2. Sair de dentro da função
Complete para encerrar o programa com o código de falha, de dentro de uma função qualquer:
3. Dando a volta
Qual valor o echo $? mostra depois de um programa que fez exit(260);?
4. O programa fez atexit(a); atexit(b); atexit(c); e terminou com return 0;. Em que ordem as funções rodam?
5. Qual destas saídas não roda as funções do atexit?
6. Qual é a forma certa de uma função para o atexit?
Missão final: o caixa da lojinha
Missão: o caixa vende pães de um estoque de 10. Quando um pedido é maior que o estoque, a função vender deve mostrar o erro e encerrar o programa com o código 3 (hoje ela continua e o estoque fica negativo). E o relatorio tem de rodar no fim, por qualquer caminho: registre-o com atexit no começo do main. A saída deve ser:
Vendi 4. Restam 6. Vendi 5. Restam 1. Estoque insuficiente para 3 pães. Sobraram no estoque: 1
#include <stdio.h>
#include <stdlib.h>
int estoque = 10;
void relatorio(void) {
printf("Sobraram no estoque: %d\n", estoque);
}
void vender(int qtd) {
if (qtd > estoque) {
fprintf(stderr, "Estoque insuficiente para %d pães.\n", qtd);
// encerre o programa com o código 3
}
estoque -= qtd;
printf("Vendi %d. Restam %d.\n", qtd, estoque);
}
int main(void) {
// registre o relatorio para rodar no fim
vender(4);
vender(5);
vender(3);
vender(1);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
int estoque = 10;
void relatorio(void) {
printf("Sobraram no estoque: %d\n", estoque);
}
void vender(int qtd) {
if (qtd > estoque) {
fprintf(stderr, "Estoque insuficiente para %d pães.\n", qtd);
exit(3);
}
estoque -= qtd;
printf("Vendi %d. Restam %d.\n", qtd, estoque);
}
int main(void) {
atexit(relatorio);
vender(4);
vender(5);
vender(3);
vender(1);
return 0;
}
Repare que o vender(1) nunca roda: o exit(3) encerrou o programa no meio do vender(3), e mesmo assim o relatório apareceu. No laboratório:
$ gcc -std=c23 -Wall -Wextra caixa.c -o caixa $ ./caixa Vendi 4. Restam 6. Vendi 5. Restam 1. Estoque insuficiente para 3 pães. Sobraram no estoque: 1 $ echo $? 3
Resumo
- Todo programa termina com um código de saída: 0 = deu certo, outro valor = deu errado. No terminal,
echo $?mostra o último;&&e||decidem por ele. - No
main,return N;é o mesmo queexit(N);. Fora dele, só oexit(dostdlib.h) encerra o programa. EXIT_SUCCESSeEXIT_FAILUREvalem 0 e 1. O Linux guarda só de 0 a 255:exit(256)vira 0.- O
exitroda oatexit, esvazia os buffers e fecha os arquivos._Exit,aborte as mortes por erro não fazem nada disso. atexit(f)registravoid f(void)para o fim; as funções rodam na ordem inversa do registro.[[noreturn]]C23 diz ao compilador que a função nunca volta; ela precisa mesmo nunca voltar.