C stdlib.h e stdio.h

Esta é a página de consulta rápida das duas bibliotecas que quase todo programa em C inclui. O <stdlib.h> ("standard library", biblioteca padrão) tem memória dinâmica, conversão de texto em número, números aleatórios, ordenação e o encerramento do programa. O <stdio.h> ("standard input/output", entrada e saída padrão) tem a tela, o teclado e os arquivos. Cada linha das tabelas leva à aula que explica a função com calma. Os formatos do printf e do scanf estão em printf e scanf.

Todas as saídas foram produzidas de verdade: no gcc 15.2 da máquina da Ana (Linux de 64 bits, pasta ~/c/ref, comando gcc -std=c23 -Wall -Wextra) e no editor do guia (Clang em WebAssembly). Quando os dois diferem, a tabela diz.

Atalhos: stdlib.h: Memória · Texto → número · Ordenar, buscar, contas · Aleatórios · Encerrar e ambiente · stdio.h: Tela e teclado · fopen e os modos · Arquivos · Constantes · Editor × gcc · Erros comuns · Experimente


stdlib.h: memória dinâmica

A memória dinâmica é a que o programa pede durante a execução, do tamanho que precisar, no heap (aula Pilha e Heap). Toda memória pedida tem de ser devolvida com free.

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int *v = malloc(3 * sizeof *v);
    if (v == NULL) {
        return 1;
    }
    v[0] = 10; v[1] = 20; v[2] = 30;
    int *maior = realloc(v, 5 * sizeof *v);
    if (maior == NULL) {
        free(v);
        return 1;
    }
    v = maior;
    v[3] = 40; v[4] = 50;
    printf("realloc: %d %d %d %d %d\n", v[0], v[1], v[2], v[3], v[4]);
    free(v);
    double *z = calloc(4, sizeof *z);
    if (z == NULL) {
        return 1;
    }
    printf("calloc: %.1f %.1f %.1f %.1f\n", z[0], z[1], z[2], z[3]);
    free(z);
    char *gigante = malloc(300u * 1024 * 1024);
    printf("malloc de 300 MB: %s\n", gigante ? "deu certo" : "NULL");
    free(gigante);
    void *alinhado = aligned_alloc(64, 128);
    printf("aligned_alloc(64, 128): múltiplo de 64? %s\n", (unsigned long)alinhado % 64 == 0 ? "sim" : "não");
    free(alinhado);
    free(NULL);
    printf("free(NULL): não faz nada\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra mem.c -o mem
$ ./mem
realloc: 10 20 30 40 50
calloc: 0.0 0.0 0.0 0.0
malloc de 300 MB: deu certo                       « no editor: NULL (o limite é 256 MB)
aligned_alloc(64, 128): múltiplo de 64? sim
free(NULL): não faz nada
FunçãoO que fazDevolveExemploAula
malloc(n)
"memory allocation"
Reserva n bytes. O conteúdo é lixo.o endereço, ou NULL se não deuint *v = malloc(3 * sizeof *v);malloc e free
calloc(qtd, tam)
"cleared allocation"
Reserva qtd × tam bytes, todos zerados.o endereço, ou NULLcalloc(4, sizeof(double)) → 0.0 0.0 0.0 0.0calloc e realloc
realloc(p, n)Muda o tamanho do bloco p para n bytes, mantendo o conteúdo (pode mudar o bloco de lugar).o novo endereço, ou NULL (aí o bloco antigo continua valendo)de 3 para 5 int: 10 20 30 40 50calloc e realloc
free(p)Devolve o bloco. Depois disso, p não pode mais ser usado. free(NULL) não faz nada.nadafree(v); v = NULL;Vazamentos
aligned_alloc(al, n) C11Como malloc, mas o endereço é múltiplo de al.o endereço, ou NULLaligned_alloc(64, 128)Pilha e Heap

Sempre teste o NULL. E nunca escreva v = realloc(v, n): se o realloc falhar, você perde o único endereço do bloco antigo (vazamento). Use uma variável nova, como maior no programa acima.

stdlib.h: texto → número

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(void) {
    printf("atoi(\"42abc\") = %d\n", atoi("42abc"));
    printf("atoi(\"abc\")   = %d\n", atoi("abc"));
    printf("atof(\"3.14\")  = %g\n", atof("3.14"));
    char *fim;
    long n = strtol("  -17 kg", &fim, 10);
    printf("strtol: %ld, sobrou [%s]\n", n, fim);
    printf("strtol base 16: %ld\n", strtol("ff", NULL, 16));
    printf("strtol base 2:  %ld\n", strtol("1010", NULL, 2));
    printf("strtol base 0:  %ld %ld %ld\n", strtol("0x1F", NULL, 0), strtol("017", NULL, 0), strtol("17", NULL, 0));
    n = strtol("abc", &fim, 10);
    printf("strtol(\"abc\"): %ld, nada lido: %s\n", n, *fim == 'a' ? "sim" : "não");
    errno = 0;
    long long g = strtoll("99999999999999999999", NULL, 10);
    printf("strtoll grande demais: %lld, ERANGE: %s\n", g, errno == ERANGE ? "sim" : "não");
    unsigned long u = strtoul("4000000000", NULL, 10);
    printf("strtoul: %lu\n", u);
    double d = strtod("12.90 reais", &fim);
    printf("strtod: %.2f, sobrou [%s]\n", d, fim);
    float f = strtof("1e3", NULL);
    printf("strtof: %.1f\n", f);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra conv.c -o conv
$ ./conv
atoi("42abc") = 42
atoi("abc")   = 0
atof("3.14")  = 3.14
strtol: -17, sobrou [ kg]
strtol base 16: 255
strtol base 2:  10
strtol base 0:  31 15 17
strtol("abc"): 0, nada lido: sim
strtoll grande demais: 9223372036854775807, ERANGE: sim
strtoul: 4000000000
strtod: 12.90, sobrou [ reais]
strtof: 1000.0

A saída do editor é idêntica.

FunçãoConverte paraSe o texto não é um númeroExemplo → resultadoAula
atoi(s), atol(s), atoll(s)
"ASCII to integer"
int, long, long longdevolve 0, calado (não dá para saber se era "0")atoi("42abc") → 42; atoi("abc") → 0Texto ↔ Número
atof(s)doubledevolve 0.0, caladoatof("3.14") → 3.14Texto ↔ Número
strtol(s, &fim, base)
"string to long"
longdevolve 0 e fim aponta para o começo de s; se o número não cabe, devolve LONG_MAX/LONG_MIN e põe ERANGE no errnostrtol(" -17 kg", &fim, 10) → -17, fim = " kg"Texto ↔ Número
strtoll, strtoul, strtoulllong long, unsigned long, unsigned long longcomo strtolstrtoll("99999999999999999999", …) → 9223372036854775807 + ERANGEValidar a Entrada
strtod(s, &fim), strtof, strtolddouble, float, long doublecomo strtolstrtod("12.90 reais", &fim) → 12.90, fim = " reais"Texto ↔ Número
A base do strtolEntendeExemplo → resultado
10decimal"17" → 17
16hexadecimal, com ou sem 0x"ff" → 255
2binário"1010" → 10
0decide pelo começo: 0x = hexadecimal, 0 = octal, o resto = decimal"0x1F" → 31; "017" → 15; "17" → 17

Prefira as strto… às ato…: só elas dizem onde a leitura parou (fim) e se o número estourou (errno). Para um int, leia com strtol e confira se o valor está entre INT_MIN e INT_MAX (veja Tipos e Limites). O caminho inverso, número → texto, é o snprintf.

stdlib.h: ordenar, buscar e contas com inteiros

#include <stdio.h>
#include <stdlib.h>

int comparar(const void *a, const void *b) {
    int x = *(const int *)a, y = *(const int *)b;
    return (x > y) - (x < y);
}

int main(void) {
    printf("abs(-5) = %d, labs(-5L) = %ld, llabs(-5LL) = %lld\n", abs(-5), labs(-5L), llabs(-5LL));
    div_t r = div(17, 5);
    printf("div(17, 5): quot = %d, rem = %d\n", r.quot, r.rem);
    int notas[] = {7, 3, 9, 1, 5};
    qsort(notas, 5, sizeof notas[0], comparar);
    printf("qsort: %d %d %d %d %d\n", notas[0], notas[1], notas[2], notas[3], notas[4]);
    int chave = 7;
    int *achou = bsearch(&chave, notas, 5, sizeof notas[0], comparar);
    printf("bsearch(7): posição %td\n", achou - notas);
    chave = 4;
    printf("bsearch(4): %s\n", bsearch(&chave, notas, 5, sizeof notas[0], comparar) ? "achou" : "NULL");
    printf("getenv(\"HOME\") = %s\n", getenv("HOME"));
    printf("getenv(\"NAO_EXISTE\") = %s\n", getenv("NAO_EXISTE") ? "achou" : "NULL");
    printf("EXIT_SUCCESS = %d, EXIT_FAILURE = %d\n", EXIT_SUCCESS, EXIT_FAILURE);
    printf("RAND_MAX = %d\n", RAND_MAX);
    printf("MB_CUR_MAX = %zu\n", MB_CUR_MAX);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra misc.c -o misc
$ ./misc
abs(-5) = 5, labs(-5L) = 5, llabs(-5LL) = 5
div(17, 5): quot = 3, rem = 2
qsort: 1 3 5 7 9
bsearch(7): posição 3
bsearch(4): NULL
getenv("HOME") = /home/ana
getenv("NAO_EXISTE") = NULL
EXIT_SUCCESS = 0, EXIT_FAILURE = 1
RAND_MAX = 2147483647
MB_CUR_MAX = 1

A saída do editor é idêntica (lá, o HOME também é /home/ana).

FunçãoO que fazExemplo → resultadoAula
qsort(v, qtd, tam, cmp)
"quick sort"
Ordena um array qualquer. cmp é uma função sua que recebe dois const void * e devolve negativo, 0 ou positivo.{7, 3, 9, 1, 5} → 1 3 5 7 9Ordenação (qsort)
bsearch(&chave, v, qtd, tam, cmp)
"binary search"
Procura num array já ordenado, com a mesma cmp.7 → ponteiro para a posição 3; 4 → NULLBusca (bsearch)
abs(x), labs, llabs
"absolute"
Valor absoluto (sem sinal) de um int, long, long long. Para double, é o fabs do math.h.abs(-5) → 5Matemática
div(a, b), ldiv, lldivQuociente e resto de uma vez, numa struct (.quot, .rem).div(17, 5) → quot = 3, rem = 2Aritméticos

A comparação (x > y) - (x < y) devolve −1, 0 ou 1 sem risco. O atalho return x - y; funciona com notas, mas estoura com números grandes de sinais opostos (aula Inteiros).

stdlib.h: números aleatórios

Função ou constanteO que fazExemploAula
rand()Devolve um inteiro "aleatório" de 0 a RAND_MAX. A sequência é sempre a mesma se a semente for a mesma.rand() % 6 + 1 → um dado, de 1 a 6Números Aleatórios
srand(semente)
"seed rand"
Escolhe a semente. Chame uma vez, no começo do programa.srand((unsigned)time(NULL)); (precisa de <time.h>)Números Aleatórios
RAND_MAXO maior valor do rand().2147483647 nos doisNúmeros Aleatórios

Os números mudam entre o editor e o gcc: com srand(42), os três primeiros rand() dão 311430560 131117839 1110653038 no editor e 1483128881 708592740 71876166 no gcc. O rand() não serve para senhas nem sorteios sérios.

stdlib.h: encerrar o programa e falar com o sistema

#include <stdio.h>
#include <stdlib.h>

void tchau(void) {
    printf("tchau (registrada primeiro)\n");
}

void salvar(void) {
    printf("salvando (registrada depois)\n");
}

int main(void) {
    atexit(tchau);
    atexit(salvar);
    printf("trabalhando...\n");
    exit(3);
}
$ gcc -std=c23 -Wall -Wextra saida.c -o saida
$ ./saida; echo "código: $?"
trabalhando...
salvando (registrada depois)
tchau (registrada primeiro)
código: 3

No editor, igual, com [código de saída: 3] no fim. As funções do atexit rodam na ordem contrária à do registro.

Função ou constanteO que fazExemploAula
exit(código)Encerra o programa de qualquer ponto: roda as funções do atexit, esvazia os buffers e fecha os arquivos. O código vai para o $? do terminal (de 0 a 255).exit(3) → código: 3exit e atexit
EXIT_SUCCESS, EXIT_FAILUREOs códigos de "deu certo" e "deu errado".0 e 1 nos doisexit e atexit
atexit(f)Registra uma função void f(void) para rodar no fim (no exit ou no return do main).ordem contrária: salvar, depois tchauexit e atexit
abort()Mata o programa na hora, sem atexit e sem esvaziar buffers.Abortado (imagem do núcleo gravada), código 134Sinais
quick_exit(c), at_quick_exit(f) C11; _Exit(c) C99Saídas rápidas: quick_exit só roda as funções do at_quick_exit; _Exit não roda nada.rarasexit e atexit
getenv(nome)
"get environment"
Lê uma variável de ambiente.getenv("HOME") → /home/ana; uma que não existe → NULLgetenv
system(comando)Roda um comando do terminal e espera ele acabar.system("ls"). No editor, não há terminal: sai sh: 1: ls: not found e devolve 32512Processos

stdio.h: tela e teclado

A tela é o stdout, o teclado é o stdin e as mensagens de erro vão para o stderr (aula stdin, stdout e stderr). As funções com f recebem um FILE * e servem para qualquer um dos três e para arquivos.

FunçãoO que fazDevolveAula
printf(fmt, …), fprintf(arq, fmt, …)Escreve com formato (tabela dos formatos).bytes escritos, ou negativoprintf
snprintf(buf, tam, fmt, …) C99Escreve com formato numa string, sem passar de tam.bytes que precisariaTexto ↔ Número
puts(s)Escreve s e pula linha.não negativo, ou EOFprintf
fputs(s, arq)Escreve s sem pular linha.não negativo, ou EOFEscrever Arquivos
putchar(c), fputc(c, arq), putcEscreve um caractere.o caractere, ou EOFchar
scanf(fmt, …), fscanf(arq, fmt, …), sscanf(s, fmt, …)Lê com formato (do teclado, de um arquivo, de uma string).campos lidos, ou EOFscanf
fgets(buf, tam, arq)Lê uma linha inteira (com o '\n'), no máximo tam - 1 bytes. Para o teclado: fgets(buf, sizeof buf, stdin).buf, ou NULL no fimfgets
getchar(), fgetc(arq), getcLê um caractere. Guarde num int, não num char, para caber o EOF.o caractere, ou EOFchar
ungetc(c, arq)Devolve um caractere à entrada: a próxima leitura o lê de novo.c, ou EOFValidar a Entrada
perror(texto)Escreve no stderr: texto: mensagem do errno.nadaerrno e perror
fflush(arq)Manda já o que está no buffer de saída. fflush(stdin) não é do C: não use.0, ou EOFstdin, stdout e stderr
setvbuf(arq, buf, modo, tam)Escolhe o buffer: _IONBF (nenhum), _IOLBF (por linha), _IOFBF (cheio).0 se deu certostdin, stdout e stderr
gets(buf)Removida no C11. Não sabia o tamanho do buf. No C23, nem compila (veja abaixo).—Segurança

stdio.h: fopen e os modos

FILE *f = fopen("compras.txt", "r"); abre um arquivo e devolve um FILE * (um "ponteiro de arquivo", que você passa para as outras funções), ou NULL se não deu. Sempre teste o NULL e feche com fclose(f). Aula: Arquivos.

ModoParaSe o arquivo não existeSe já existeComeça em
"r"ler ("read")NULL (No such file or directory)abreno começo
"w"escrever ("write")criaapaga tudo e começa vaziono começo
"a"acrescentar ("append")criamantémno fim (toda escrita vai para o fim)
"r+"ler e escreverNULLmantémno começo
"w+"escrever e lercriaapaga tudono começo
"a+"ler e acrescentarcriamantémlê de qualquer lugar; escreve no fim
"wx", "w+x" C11criar um arquivo novocriaNULL (File exists): não apaga nadano começo
b no modo ("rb", "wb", "ab"…)arquivo binárioNo Linux não muda nada; no Windows, desliga a troca de \n por \r\n. Use em fread/fwrite. Aula: Arquivos Binários.

stdio.h: ler, escrever e andar no arquivo

#include <stdio.h>

int main(void) {
    FILE *f = fopen("compras.txt", "w");
    if (f == NULL) {
        perror("compras.txt");
        return 1;
    }
    fprintf(f, "arroz\nfeijão\n");
    fputs("café\n", f);
    fclose(f);

    f = fopen("compras.txt", "a");
    fputc('p', f);
    fputs("ão\n", f);
    fclose(f);

    f = fopen("compras.txt", "r");
    char linha[50];
    while (fgets(linha, sizeof linha, f) != NULL) {
        printf("lido: %s", linha);
    }
    printf("feof = %d, ferror = %d\n", feof(f) != 0, ferror(f) != 0);
    fseek(f, 0, SEEK_END);
    printf("tamanho: %ld bytes\n", ftell(f));
    rewind(f);
    int c = fgetc(f);
    ungetc(c, f);
    printf("primeira letra: %c, de novo: %c\n", c, fgetc(f));
    fclose(f);

    if (fopen("compras.txt", "wx") == NULL) {
        perror("modo \"wx\"");
    }
    if (rename("compras.txt", "lista.txt") == 0) {
        printf("rename: ok\n");
    }
    if (fopen("compras.txt", "r") == NULL) {
        perror("compras.txt");
    }
    printf("remove: %d\n", remove("lista.txt"));
    printf("remove de novo: %d\n", remove("lista.txt"));
    return 0;
}
$ gcc -std=c23 -Wall -Wextra arq.c -o arq
$ ./arq
lido: arroz
lido: feijão
lido: café
lido: pão
feof = 1, ferror = 0
tamanho: 25 bytes
primeira letra: a, de novo: a
modo "wx": File exists
rename: ok
compras.txt: No such file or directory
remove: 0
remove de novo: -1

A saída do editor é idêntica (os arquivos ficam numa pasta em memória, que começa limpa a cada Rodar). O tamanho, 25 bytes, é a soma de 18 letras, 4 \n e mais 1 byte para cada uma das 3 letras acentuadas (que ocupam 2 bytes).

FunçãoO que fazDevolveAula
fopen(nome, modo)Abre (tabela dos modos).FILE *, ou NULLArquivos
fclose(f)Fecha: grava o que estava no buffer e libera o FILE.0, ou EOFArquivos
fgets, fgetc, fscanfLeem uma linha, um caractere, com formato.veja acimaLer Arquivos
fprintf, fputs, fputcEscrevem com formato, uma string, um caractere.veja acimaEscrever Arquivos
fread(buf, tam, qtd, f)Lê qtd itens de tam bytes, do jeito que estão na memória (binário).quantos itens leu (size_t)Arquivos Binários
fwrite(buf, tam, qtd, f)Escreve qtd itens de tam bytes.quantos itens escreveuArquivos Binários
fseek(f, desloc, origem)Vai para outra posição: desloc bytes a partir de SEEK_SET (começo), SEEK_CUR (onde está) ou SEEK_END (fim).0 se deu certofseek e ftell
ftell(f)A posição atual, em bytes desde o começo. Depois de fseek(f, 0, SEEK_END), é o tamanho do arquivo.long, ou -1fseek e ftell
rewind(f)Volta ao começo (e limpa o feof).nadafseek e ftell
feof(f), ferror(f)Dizem se uma leitura já bateu no fim, ou se deu erro. Use depois que a leitura falhou, para saber o motivo.não zero = simLer Arquivos
clearerr(f)Limpa os indicadores de fim e de erro.nadaLer Arquivos
remove(nome)Apaga o arquivo.0, ou não zero (aqui, -1)Arquivos
rename(velho, novo)Muda o nome (ou a pasta) do arquivo.0, ou não zeroArquivos
tmpfile()Cria um arquivo temporário, apagado ao fechar. Não existe no editor (erro do ligador undefined symbol: tmpfile).FILE *, ou NULLArquivos

Constantes do stdio.h

#include <stdio.h>

int main(void) {
    printf("EOF = %d\n", EOF);
    printf("BUFSIZ = %d\n", BUFSIZ);
    printf("FILENAME_MAX = %d\n", FILENAME_MAX);
    printf("FOPEN_MAX = %d\n", FOPEN_MAX);
    printf("SEEK_SET = %d, SEEK_CUR = %d, SEEK_END = %d\n", SEEK_SET, SEEK_CUR, SEEK_END);
    printf("_IOFBF = %d, _IOLBF = %d, _IONBF = %d\n", _IOFBF, _IOLBF, _IONBF);
    return 0;
}
ConstanteO que égcc 15 (glibc)Editor (wasi-libc)
EOF"end of file": o valor que as leituras devolvem no fim da entrada-1-1
NULLo ponteiro nulo (também em stdlib.h, string.h, stddef.h)veja Ponteiro Nulo
BUFSIZum bom tamanho de buffer para setvbuf81921024
FILENAME_MAXtamanho de array que cabe o maior nome de arquivo40964096
FOPEN_MAXquantos arquivos abertos ao mesmo tempo, no mínimo, são garantidos161000
SEEK_SET, SEEK_CUR, SEEK_ENDas origens do fseek0, 1, 20, 1, 2
_IOFBF, _IOLBF, _IONBFos modos do setvbuf0, 1, 20, 1, 2
TMP_MAX, L_tmpnamlimites do tmpnam (nomes temporários)238328, 20não existem (use of undeclared identifier 'TMP_MAX')

Escreva o nome da constante, nunca o número: while ((c = getchar()) != EOF), e não != -1.

Editor × gcc

AssuntoEditor (Clang + wasi-libc)gcc 15 (glibc)
strtol("3000000000", NULL, 10)2147483647 e ERANGE (o long tem 4 bytes)3000000000, sem erro (o long tem 8)
malloc de 300 MBNULL (limite de 256 MB)funciona
srand(42) + rand()311430560 131117839 11106530381483128881 708592740 71876166
system("ls")sh: 1: ls: not found, devolve 32512roda o ls
tmpfile, tmpnam, TMP_MAXnão existemexistem
BUFSIZ, FOPEN_MAX1024, 10008192, 16
free duplopassa caladofree(): double free detected in tcache 2 + Abortado (134)
Arquivosnuma pasta em memória (/home/ana), limpa a cada Rodarno disco, na pasta atual

Iguais nos dois: as conversões ato…/strto… (fora o tamanho do long), qsort, bsearch, abs, div, atexit/exit, getenv("HOME"), EOF, RAND_MAX, os modos do fopen e as mensagens do perror.


Erros comuns

Esqueceu o #include <stdlib.h> (e usou gets)
#include <stdio.h>

int main(void) {
    int *v = malloc(10 * sizeof *v);
    char nome[20];
    gets(nome);
    free(v);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra semstdlib.c -o semstdlib
semstdlib.c: In function ‘main’:
semstdlib.c:4:14: error: implicit declaration of function ‘malloc’ [-Wimplicit-function-declaration]
    4 |     int *v = malloc(10 * sizeof *v);
      |              ^~~~~~
semstdlib.c:2:1: note: include ‘<stdlib.h>’ or provide a declaration of ‘malloc’
    1 | #include <stdio.h>
  +++ |+#include <stdlib.h>
    2 |
semstdlib.c:4:14: warning: incompatible implicit declaration of built-in function ‘malloc’ [-Wbuiltin-declaration-mismatch]
    4 |     int *v = malloc(10 * sizeof *v);
      |              ^~~~~~
semstdlib.c:4:14: note: include ‘<stdlib.h>’ or provide a declaration of ‘malloc’
semstdlib.c:6:5: error: implicit declaration of function ‘gets’; did you mean ‘fgets’? [-Wimplicit-function-declaration]
    6 |     gets(nome);
      |     ^~~~
      |     fgets
semstdlib.c:7:5: error: implicit declaration of function ‘free’ [-Wimplicit-function-declaration]
    7 |     free(v);
      |     ^~~~
semstdlib.c:7:5: note: include ‘<stdlib.h>’ or provide a declaration of ‘free’
semstdlib.c:7:5: warning: incompatible implicit declaration of built-in function ‘free’ [-Wbuiltin-declaration-mismatch]
semstdlib.c:7:5: note: include ‘<stdlib.h>’ or provide a declaration of ‘free’

No editor (Clang):

semstdlib.c:4:14: error: call to undeclared library function 'malloc' with type 'void *(__size_t)' (aka 'void *(unsigned long)'); ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
semstdlib.c:4:14: note: include the header <stdlib.h> or explicitly provide a declaration for 'malloc'
semstdlib.c:6:5: error: use of undeclared identifier 'gets'
semstdlib.c:7:5: error: call to undeclared library function 'free' with type 'void (void *)'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]

O que significa: o malloc e o free moram no <stdlib.h>; sem ele, o compilador não os conhece. Os dois compiladores até dizem qual #include falta (o gcc mostra a linha com +++ |+). Já o gets foi removido do C: não existe em nenhum cabeçalho.

Como consertar: acrescente #include <stdlib.h> e troque gets(nome) por fgets(nome, sizeof nome, stdin). Aulas: Protótipos e fgets.

O laço while (!feof(f)) leu a última nota duas vezes
#include <stdio.h>

int main(void) {
    FILE *f = fopen("notas.txt", "w");
    if (f == NULL) {
        return 1;
    }
    fprintf(f, "7\n9\n");
    fclose(f);
    f = fopen("notas.txt", "r");
    if (f == NULL) {
        return 1;
    }
    int nota = 0, soma = 0;
    while (!feof(f)) {
        fscanf(f, "%d", &nota);
        printf("leu %d\n", nota);
        soma += nota;
    }
    printf("soma = %d\n", soma);
    fclose(f);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra feof.c -o feof
$ ./feof
leu 7
leu 9
leu 9
soma = 25

O que significa: o feof não adivinha o futuro. Depois de ler o 9, ainda sobrou o \n, então o fim não foi alcançado. A volta seguinte chama fscanf, que falha (devolve EOF) e não muda nota: o 9 é somado de novo. Sem aviso nenhum, nos dois compiladores.

Como consertar: o teste é o retorno da leitura: while (fscanf(f, "%d", &nota) == 1), ou while (fgets(linha, sizeof linha, f) != NULL). É a missão abaixo. Aula: Ler Arquivos.

Não testou o NULL do fopen ou do malloc

Se o arquivo não existe, fopen devolve NULL, e o primeiro fgets(linha, 50, f) com esse f derruba o programa com Falha de segmentação (139), no gcc e no editor. Como consertar: if (f == NULL) { perror("compras.txt"); return 1; }, que mostra compras.txt: No such file or directory. Aula: errno e perror.

char c = getchar() num laço até EOF

O getchar devolve int porque o EOF (−1) precisa ser diferente de todos os 256 caracteres. Guardado num char, o EOF se confunde com o byte 255 (ou nunca é visto, se o char não tem sinal). Como consertar: int c;. Aula: char.


Experimente

Missão: o arquivo notas.txt (na outra aba) tem as notas 7 e 9, mas o programa soma 25, porque usa while (!feof(f)). Troque a condição do laço pelo retorno do fscanf (== 1), apague o fscanf de dentro do laço e faça o programa mostrar soma = 16.

#include <stdio.h>

int main(void) {
    FILE *f = fopen("notas.txt", "r");
    if (f == NULL) {
        perror("notas.txt");
        return 1;
    }
    int nota = 0, soma = 0;
    while (!feof(f)) {
        fscanf(f, "%d", &nota);
        printf("leu %d\n", nota);
        soma += nota;
    }
    printf("soma = %d\n", soma);
    fclose(f);
    return 0;
}
7
9
#include <stdio.h>

int main(void) {
    FILE *f = fopen("notas.txt", "r");
    if (f == NULL) {
        perror("notas.txt");
        return 1;
    }
    int nota = 0, soma = 0;
    while (fscanf(f, "%d", &nota) == 1) {
        printf("leu %d\n", nota);
        soma += nota;
    }
    printf("soma = %d\n", soma);
    fclose(f);
    return 0;
}

Resumo

  • stdlib.h: malloc/calloc/realloc/free (teste o NULL; todo malloc tem o seu free); strtol/strtod (melhores que atoi/atof); qsort/bsearch; abs/div; rand/srand; exit/atexit/abort; getenv/system.
  • stdio.h: printf/puts/putchar e scanf/fgets/getchar para tela e teclado; fopen ("r", "w" apaga, "a" acrescenta, "x" não apaga) e fclose; fread/fwrite para binário; fseek/ftell/rewind; remove/rename; perror.
  • Leia em laço pelo retorno da leitura, nunca com while (!feof(f)).
  • No editor, o long tem 4 bytes (o strtol estoura antes) e o malloc vai até 256 MB.