C Pré-processador

Desde o primeiro programa você escreve #include <stdio.h> no topo do arquivo. Essa linha começa com # e não termina com ;. Ela é diferente de todas as outras porque não é para o compilador: é uma ordem para um programa que trabalha antes dele, o pré-processador. Nesta aula você vai ver o que ele faz com o seu arquivo, espiar o resultado com gcc -E e conhecer as primeiras ordens que ele entende: #include, #define, #error e #warning.

Você vai aprender
  • O que é o pré-processador e em que momento ele roda.
  • O que #include faz de verdade, e a diferença entre <stdio.h> e "loja.h".
  • Como criar constantes com #define, e por que ali não vai = nem ;.
  • Como ver o arquivo que o compilador recebe, com gcc -E.
  • Os nomes prontos __FILE__, __LINE__, __func__, __DATE__, __TIME__ e __STDC_VERSION__.
  • Como parar a compilação com #error e deixar um lembrete com #warning C23.

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/prepro, com o comando padrão do guia: gcc -std=c23 -Wall -Wextra arquivo.c -o programa (cada opção é explicada em Compilar com gcc). Nas caixas "Experimente", o editor do guia compila de verdade, com o Clang.


A ideia: o assistente que prepara a receita

Imagine uma cozinheira (o compilador) que só sabe seguir uma receita escrita por inteiro, do começo ao fim. Antes de o papel chegar até ela, um assistente passa pela receita e faz trocas mecânicas:

  • onde está escrito "cole aqui a receita do molho", ele copia o texto inteiro da receita do molho naquele lugar;
  • onde está escrito "XÍCARA", ele troca por "240 ml", porque alguém anotou lá em cima que XÍCARA quer dizer 240 ml;
  • ele risca as anotações a lápis na margem, que a cozinheira não precisa ler.

O assistente não sabe cozinhar. Ele não entende a receita: só copia, troca e apaga texto. Esse assistente é o pré-processador. As ordens para ele são as linhas que começam com #, chamadas diretivas (do inglês directive, "instrução"). As anotações a lápis são os seus comentários.

EtapaQuem fazO que recebeO que entrega
1. Pré-processamentoo pré-processadoro seu .c, com as linhas # e os comentáriosum texto C puro: sem diretivas, sem comentários, com os #include já colados
2. Compilaçãoo compiladoresse textocódigo de máquina (um arquivo .o)
3. Ligaçãoo ligador (linker)os .o e as bibliotecaso programa pronto

Quando você digita gcc ola.c -o ola, as três etapas rodam em sequência, sem você ver. A aula Como o C Vira Programa mostra o caminho todo. Aqui vamos olhar só a primeira etapa, e o gcc deixa: com a opção -E, ele faz o pré-processamento e para.


1. Ver o que o pré-processador entrega: gcc -E

Comece com um arquivo pequeno, sem #include, para dar para ler o resultado inteiro. A Ana anotou o preço do caderno e o nome da loja com #define:

// Preço do caderno na lojinha da Ana
#define PRECO 12
#define LOJA "Lojinha da Ana"

int main(void) {
    int total = PRECO * 3;   /* três cadernos */
    const char *nome = LOJA;
    return total;
}
$ gcc -E mini.c
# 0 "mini.c"
# 0 "<built-in>"
# 0 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 0 "<command-line>" 2
# 1 "mini.c"




int main(void) {
    int total = 12 * 3;                     « PRECO virou 12
    const char *nome = "Lojinha da Ana";    « LOJA virou o texto
    return total;
}

Compare com o arquivo original. Três coisas aconteceram:

  1. As duas linhas #define sumiram: o pré-processador cumpriu a ordem e jogou a linha fora. No lugar ficaram linhas vazias.
  2. Os comentários sumiram, tanto o // quanto o /* */. O compilador nunca vê os seus comentários.
  3. Onde estava escrito PRECO agora está 12, e onde estava LOJA está "Lojinha da Ana". Foi uma troca de texto: nada foi calculado (a conta 12 * 3 continua lá, para o compilador fazer).
-E
"Pare depois do pré-processamento" (E de expand, expandir). O resultado sai na tela (ou num arquivo, com -o mini.i). Nada é compilado e nenhum programa é criado.
# 1 "mini.c"
Marcas de linha. O pré-processador as deixa para o compilador saber de que arquivo e de que linha veio cada pedaço. É graças a elas que um erro consegue dizer mini.c:6 mesmo depois de tudo ter sido trocado.
stdc-predef.h
Um arquivo pequeno do sistema que o gcc inclui sozinho em todo programa. Você pode ignorá-lo.

Para ver só o código, sem as marcas e sem as linhas vazias, acrescente -P:

$ gcc -E -P mini.c
int main(void) {
    int total = 12 * 3;
    const char *nome = "Lojinha da Ana";
    return total;
}
Para que serve isso no dia a dia? Quando uma macro se comporta de um jeito estranho (você vai ver vários casos na aula Macros com Parâmetros), o gcc -E mostra exatamente o texto que o compilador recebeu. É o "raio X" do pré-processador.

2. #include: colar outro arquivo aqui

Agora o programa de sempre:

#include <stdio.h>

int main(void) {
    printf("Olá!\n");
    return 0;
}

São 6 linhas. Veja quantas linhas o compilador recebe depois do pré-processamento (o | wc -l conta as linhas da saída, como você viu no Guia Bash):

$ gcc -E ola.c | wc -l
844

Oitocentas e quarenta e quatro linhas! A diretiva #include <stdio.h> mandou o pré-processador procurar o arquivo stdio.h e colar o conteúdo inteiro naquele lugar. E o stdio.h, por sua vez, tem os seus próprios #include, que também foram colados. O fim da saída mostra o seu código lá embaixo, depois de tudo o que veio do stdio.h:

$ gcc -E -P ola.c | tail -8
extern void funlockfile (FILE *__stream) __attribute__ ((__nothrow__ , __leaf__)) __attribute__ ((__nonnull__ (1)));
extern int __uflow (FILE *);
extern int __overflow (FILE *, int);

int main(void) {
    printf("Olá!\n");
    return 0;
}

As linhas com extern são declarações de funções da biblioteca (você aprende a ler isso em Protótipos e static e extern). Entre as 844 linhas está a declaração do printf: é por isso que, sem o #include, o compilador reclama que não conhece o printf.

O .h não é a biblioteca. O stdio.h só traz as declarações ("existe uma função printf que recebe isto e devolve aquilo"). O código de verdade do printf já vem pronto, compilado, na biblioteca C do sistema, e entra na etapa de ligação. A extensão .h vem de header, "cabeçalho": o arquivo que vai no alto.

Com < > ou com " "?

Você também pode escrever os seus próprios arquivos .h. A Ana guardou os dados da loja num arquivo separado:

#define LOJA "Lojinha da Ana"
#define PRECO_CADERNO 12
#include <stdio.h>
#include "loja.h"

int main(void) {
    printf("%s: caderno a R$ %d\n", LOJA, PRECO_CADERNO);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra usa_loja.c -o usa_loja
$ ./usa_loja
Lojinha da Ana: caderno a R$ 12

Repare: o stdio.h está entre < > e o loja.h está entre aspas. A diferença é onde o pré-processador procura:

FormaOnde procuraUse para
#include <stdio.h>Só nas pastas do sistema (/usr/include e companhia)Arquivos da biblioteca padrão e de bibliotecas instaladas
#include "loja.h"Primeiro na pasta do arquivo .c; se não achar, nas pastas do sistemaOs arquivos .h do seu próprio projeto

O gcc mostra a lista de pastas com a opção -v (de verbose, "falador"). O comando abaixo pré-processa um arquivo vazio só para ver essa lista:

$ echo | gcc -E -v - 2>&1 | sed -n "/search starts/,/End of/p"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-linux-gnu/15/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

Em português: "a busca do #include "..." começa aqui" (a pasta do próprio arquivo, que não aparece na lista) e "a busca do #include <...> começa aqui", seguida das pastas do sistema. O stdio.h mora em /usr/include/stdio.h.

O que acontece se a Ana escrever #include <loja.h>, com sinais de menor e maior? O pré-processador procura só nas pastas do sistema, e o loja.h não está lá:

$ gcc -std=c23 -Wall -Wextra angulo.c -o angulo
angulo.c:2:10: fatal error: loja.h: Arquivo ou diretório inexistente
    2 | #include <loja.h>
      |          ^~~~~~~~
compilation terminated.
fatal error
"Erro fatal": um erro tão grave que o gcc nem continua. Sem o arquivo, não há o que compilar.
Arquivo ou diretório inexistente
Esta parte vem do sistema, e o sistema da Ana está em português. Em fóruns você vai ver a versão em inglês: No such file or directory.
compilation terminated.
"Compilação encerrada."

Dá para acrescentar uma pasta à lista do sistema com -I (i maiúsculo, de include). -I. quer dizer "procure também na pasta atual" (o ponto é a pasta atual):

$ gcc -std=c23 -Wall -Wextra -I. angulo.c -o angulo && ./angulo
Lojinha da Ana: caderno a R$ 12

Funciona, mas a regra continua simples: arquivos seus, entre aspas; arquivos do sistema, entre < >. O -I é útil em projetos grandes, com os .h numa pasta include/ (você vai ver isso em Arquivos .h e .c e Bibliotecas).

No editor do guia, o Clang dá uma dica ainda mais direta para o mesmo erro:

angulo.c:2:10: error: 'loja.h' file not found with <angled> include; use "quotes" instead
    2 | #include <loja.h>
      |          ^~~~~~~~
      |          "loja.h"

"loja.h não encontrado com include de sinais angulares; use aspas."

Experimente: esta caixa tem duas abas, usa_loja.c e loja.h. Rode. Depois mude o preço no loja.h e rode de novo. Por último, troque "loja.h" por <loja.h> no usa_loja.c e leia o erro.

#include <stdio.h>
#include "loja.h"

int main(void) {
    printf("%s: caderno a R$ %d\n", LOJA, PRECO_CADERNO);
    return 0;
}
#define LOJA "Lojinha da Ana"
#define PRECO_CADERNO 12

3. #define: dar nome a um valor

A linha #define PRECO 12 ("defina PRECO como 12") cria uma macro: um nome que o pré-processador troca por um texto em todo o resto do arquivo, da linha do #define para baixo.

#define PRECO 12
#define
A diretiva. Como toda diretiva, começa com # e ocupa a linha inteira.
PRECO
O nome da macro. Por costume, macros são escritas EM MAIÚSCULAS, para que quem lê saiba na hora que aquilo não é uma variável.
12
O texto de troca: tudo o que vem depois do nome, até o fim da linha.
(sem = e sem ;)
Diretivas não são comandos de C. Não levam = nem ponto e vírgula: se você puser, eles entram no texto de troca (veja o que acontece logo abaixo).

Por que dar nome a um número? Imagine o preço do caderno aparecendo em dez lugares do programa. Quando o preço subir, você muda uma linha, e não dez. E PRECO * 3 diz muito mais para quem lê do que 12 * 3. Um número solto no meio do código, sem nome, é chamado de número mágico, e é bom evitar.

A troca respeita palavras inteiras e textos entre aspas

O pré-processador troca só a palavra inteira PRECO. Ele não mexe num nome maior que contém PRECO, e nem em textos entre aspas:

#include <stdio.h>

#define PRECO 12

int main(void) {
    printf("PRECO custa %d\n", PRECO);
    int PRECO_TOTAL = 36;
    printf("%d\n", PRECO_TOTAL);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra texto.c -o texto && ./texto
PRECO custa 12          « o PRECO das aspas ficou; o de fora virou 12
36

#undef: esquecer uma macro

#undef TAXA ("undefine") apaga a macro. Daí para baixo, o nome volta a ser desconhecido:

#include <stdio.h>

#define TAXA 5

int main(void) {
    printf("Taxa: %d\n", TAXA);
#undef TAXA
    printf("Taxa: %d\n", TAXA);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra undef.c -o undef
undef.c: In function ‘main’:
undef.c:8:26: error: ‘TAXA’ undeclared (first use in this function)
    8 |     printf("Taxa: %d\n", TAXA);
      |                          ^~~~
undef.c:8:26: note: each undeclared identifier is reported only once for each function it appears in

Repare que o #undef está no meio do main e funciona assim mesmo: o pré-processador não sabe o que é uma função. Ele lê o arquivo de cima para baixo, como texto. Na linha 8, TAXA não é mais macro, chega intacto ao compilador, e o compilador não conhece nenhuma variável com esse nome.

Experimente: rode. Mude o PRECO para 15 e rode de novo: só uma linha mudou, e as duas contas acompanharam. Depois acrescente #define DESCONTO 2 e subtraia o desconto do total.

#include <stdio.h>

#define LOJA "Lojinha da Ana"
#define PRECO 12

int main(void) {
    printf("%s\n", LOJA);
    printf("3 cadernos: R$ %d\n", PRECO * 3);
    printf("5 cadernos: R$ %d\n", PRECO * 5);
    return 0;
}

4. Nomes que já vêm prontos

O pré-processador já conhece alguns nomes, sem você definir nada. Eles começam e terminam com dois sublinhados (__):

#include <stdio.h>

void avisar(void) {
    printf("Estou em %s, na função %s, linha %d\n", __FILE__, __func__, __LINE__);
}

int main(void) {
    printf("Arquivo: %s\n", __FILE__);
    printf("Linha:   %d\n", __LINE__);
    printf("Função:  %s\n", __func__);
    printf("Compilado em %s às %s\n", __DATE__, __TIME__);
    printf("Versão do C: %ld\n", __STDC_VERSION__);
    avisar();
    return 0;
}
$ gcc -std=c23 -Wall -Wextra onde.c -o onde
$ ./onde
Arquivo: onde.c
Linha:   9
Função:  main
Compilado em Oct 17 2026 às 10:32:21
Versão do C: 202311
Estou em onde.c, na função avisar, linha 4
NomeViraTipo do valor
__FILE__o nome do arquivo que está sendo compiladotexto (%s)
__LINE__o número da linha em que ele apareceinteiro (%d)
__func__ C99o nome da função em que ele aparecetexto (%s)
__DATE__a data da compilação, em inglês: "Oct 17 2026"texto
__TIME__a hora da compilação: "10:32:21"texto
__STDC_VERSION__a versão do C usada: 199901L (C99), 201112L (C11), 201710L (C17), 202311L (C23)inteiro long (%ld)

Três detalhes importantes:

  • __DATE__ e __TIME__ não são o relógio. São a data e a hora em que o gcc rodou, gravadas no programa como texto. Se você rodar o ./onde amanhã, ele vai continuar dizendo "Oct 17 2026". Para a hora atual de verdade, veja Data e Hora.
  • __LINE__ é a linha onde ele está escrito. Na função avisar, deu 4, e não a linha do main que chamou a função.
  • __func__ não é bem uma macro. O pré-processador nem sabe o que é uma função; quem resolve o __func__ é o compilador, que cria um texto com o nome da função. Por isso ele só funciona dentro de uma função. No uso do dia a dia, ele anda junto com os outros dois.

Para que servem? Para mensagens de depuração que dizem onde o problema aconteceu. O assert (aula Testes) usa exatamente esses nomes para mostrar o arquivo, a linha e a função de quem falhou. Na aula Macros com Parâmetros você vai criar uma macro LOG que faz o mesmo.

No editor do guia, o __DATE__ e o __TIME__ mostram a hora em UTC (o horário de Greenwich, 3 horas à frente de Brasília), porque o compilador roda dentro do navegador sem saber o seu fuso. Na máquina da Ana o fuso é o de São Paulo.

5. #error e #warning: recados na hora de compilar

#error: pare tudo

Às vezes, um valor de #define não faz sentido, e é melhor descobrir isso antes de o programa existir. A Ana divide uma compra de R$ 120 em parcelas. Zero parcelas daria uma divisão por zero. Com #if (o "se" do pré-processador, assunto da aula Compilação Condicional) e #error, ela impede isso:

#include <stdio.h>

#define PARCELAS 0

#if PARCELAS == 0
#error "PARCELAS não pode ser zero: escolha 1 ou mais"
#endif

int main(void) {
    printf("Cada parcela: %d\n", 120 / PARCELAS);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra erro.c -o erro
erro.c:6:2: error: #error "PARCELAS não pode ser zero: escolha 1 ou mais"
    6 | #error "PARCELAS não pode ser zero: escolha 1 ou mais"
      |  ^~~~~
erro.c: In function ‘main’:
erro.c:10:38: warning: division by zero [-Wdiv-by-zero]
   10 |     printf("Cada parcela: %d\n", 120 / PARCELAS);
      |                                      ^
#if PARCELAS == 0
"Se PARCELAS for igual a 0". O pré-processador faz essa conta sozinho, antes de compilar.
#error "…"
"Erro": mostra a sua mensagem como um erro de compilação. O programa não é criado.
#endif
"Fim do if": fecha o bloco que o #if abriu.

Repare que o gcc mostrou o seu erro e continuou lendo o arquivo: por isso também apareceu o aviso de divisão por zero. Mesmo assim, como houve um erro, o programa erro não foi criado. Com #define PARCELAS 3, o #if é falso, a linha do #error é pulada e tudo compila.

#warning: só um lembrete C23

#warning ("aviso") mostra a mensagem como um aviso: o programa é criado normalmente. Serve para lembretes como "falta terminar isto":

#include <stdio.h>

#warning "Falta testar o desconto de aniversário"

int main(void) {
    printf("Lojinha aberta!\n");
    return 0;
}
$ gcc -std=c23 -Wall -Wextra aviso.c -o aviso
aviso.c:3:2: warning: #warning "Falta testar o desconto de aniversário" [-Wcpp]
    3 | #warning "Falta testar o desconto de aniversário"
      |  ^~~~~~~
$ ./aviso
Lojinha aberta!

O #warning existia há décadas no gcc e no Clang, mas só entrou na norma oficial no C23. Com um padrão antigo e a opção -pedantic (que reclama de tudo o que não está na norma), o gcc avisa:

$ gcc -std=c17 -Wall -Wextra -pedantic aviso.c -o aviso
aviso.c:3:2: warning: ‘#warning’ before C23 is a GCC extension [-Wpedantic]
    3 | #warning "Falta testar o desconto de aniversário"
      |  ^~~~~~~
aviso.c:3:2: warning: #warning "Falta testar o desconto de aniversário" [-Wcpp]

"#warning antes do C23 é uma extensão do GCC." Funciona do mesmo jeito; o aviso só lembra que um compilador muito estrito poderia não aceitar.

Exigir uma versão do C

Um uso clássico do #error: recusar um padrão antigo demais. Este programa usa bool sem #include, o que só funciona no C23:

#include <stdio.h>

#if __STDC_VERSION__ < 202311L
#error "Este programa precisa de C23 (use -std=c23)"
#endif

int main(void) {
    bool aberta = true;
    printf("Aberta? %d\n", aberta);
    return 0;
}
$ gcc -std=c17 -Wall -Wextra versao.c -o versao
versao.c:4:2: error: #error "Este programa precisa de C23 (use -std=c23)"
    4 | #error "Este programa precisa de C23 (use -std=c23)"
      |  ^~~~~
versao.c: In function ‘main’:
versao.c:8:5: error: unknown type name ‘bool’
    8 |     bool aberta = true;
      |     ^~~~
   «  … mais algumas linhas sobre bool e true …
$ gcc -std=c23 -Wall -Wextra versao.c -o versao && ./versao
Aberta? 1

A primeira mensagem já explica o que fazer, em português, escrita por quem fez o programa. Os erros seguintes (sobre bool) são consequência.

Missão: o programa compila e mostra as parcelas. Mude PARCELAS para 0 e rode: o #error deve impedir a compilação.

#include <stdio.h>

#define TOTAL 120
#define PARCELAS 3

#if PARCELAS == 0
#error "PARCELAS não pode ser zero: escolha 1 ou mais"
#endif

int main(void) {
    printf("%d parcelas de R$ %d\n", PARCELAS, TOTAL / PARCELAS);
    return 0;
}
#include <stdio.h>

#define TOTAL 120
#define PARCELAS 0

#if PARCELAS == 0
#error "PARCELAS não pode ser zero: escolha 1 ou mais"
#endif

int main(void) {
    printf("%d parcelas de R$ %d\n", PARCELAS, TOTAL / PARCELAS);
    return 0;
}

Formas diferentes: #define, const, constexpr ou enum?

O #define é o jeito mais antigo de dar nome a um valor, e você vai vê-lo em quase todo código C. Mas não é o único. Os quatro abaixo compilam juntos no gcc 15:

#include <stdio.h>

#define PRECO 12
constexpr int QTD = 3;
enum { DESCONTO = 2 };

int main(void) {
    printf("Total: %d\n", PRECO * QTD - DESCONTO);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra juntas.c -o juntas && ./juntas
Total: 34
FormaQuem cuidaTem tipo?Quando usar
#define PRECO 12pré-processador (troca de texto)nãoCódigo que precisa funcionar em qualquer versão do C; valores usados em #if; textos fixos
const int PRECO = 12;compiladorsimValores que não devem mudar dentro de uma função
constexpr int PRECO = 12; C23compiladorsimConstantes de verdade, com tipo, conhecidas na compilação (servem até para o tamanho de um array)
enum { PRECO = 12 };compiladorsim (int)Grupos de números inteiros com nome (veja Enums)

Qual usar? Em código novo com C23, prefira constexpr para números: o compilador conhece o tipo, mostra o nome nas mensagens de erro e o depurador enxerga a constante. Use #define quando precisar do pré-processador de verdade: valores testados com #if, macros com parâmetros e compilação condicional. A aula Constantes compara as formas em detalhe.


Quando dá errado

fatal error: lojas.h: Arquivo ou diretório inexistente
$ gcc -std=c23 -Wall -Wextra errado.c -o errado
errado.c:2:10: fatal error: lojas.h: Arquivo ou diretório inexistente
    2 | #include "lojas.h"
      |          ^~~~~~~~~
compilation terminated.

O que significa: o pré-processador não achou o arquivo do #include (em inglês, No such file or directory; no editor, 'lojas.h' file not found). Ou o nome está errado (aqui, lojas.h em vez de loja.h), ou o arquivo está em outra pasta, ou você usou < > para um arquivo seu.

Como consertar: confira o nome letra por letra (maiúsculas contam no Linux) e veja com ls se o arquivo está na mesma pasta do .c. Arquivo seu vai entre aspas. Se ele está em outra pasta, use -I pasta.

error: invalid type argument of unary ‘*’ (have ‘int’)
#include <stdio.h>
#define PRECO 12;

int main(void) {
    int total = PRECO * 3;
    printf("Total: %d\n", total);
    return 0;
}
$ gcc -std=c23 -Wall -Wextra pv.c -o pv
pv.c: In function ‘main’:
pv.c:5:23: error: invalid type argument of unary ‘*’ (have ‘int’)
    5 |     int total = PRECO * 3;
      |                       ^~~

O que significa: o ; do #define entrou no texto de troca. A linha 5 virou int total = 12; * 3;. A primeira parte é uma declaração completa; a segunda, * 3;, não faz sentido. O erro aponta uma linha que parece certa, porque o defeito está lá em cima, na linha do #define.

Como consertar: tire o ; do #define. Quando um erro esquisito aparece numa linha que usa uma macro, rode gcc -E -P e veja o texto de verdade.

error: expected expression before ‘=’ token + note: in expansion of macro ‘PRECO’
$ gcc -std=c23 -Wall -Wextra igual.c -o igual
igual.c: In function ‘main’:
igual.c:2:15: error: expected expression before ‘=’ token
    2 | #define PRECO = 12
      |               ^
igual.c:5:17: note: in expansion of macro ‘PRECO’
    5 |     int total = PRECO * 3;
      |                 ^~~~~

O que significa: foi escrito #define PRECO = 12, como numa variável. O = virou parte do texto e a linha 5 ficou int total = = 12 * 3;. A note ("nota") ajuda: "na expansão da macro PRECO".

Como consertar: #define NOME valor, só com espaço entre o nome e o valor.

error: invalid preprocessing directive #inclue; did you mean #include?
$ gcc -std=c23 -Wall -Wextra inclue.c -o inclue
inclue.c:1:2: error: invalid preprocessing directive #inclue; did you mean #include?
    1 | #inclue <stdio.h>
      |  ^~~~~~
      |  include
inclue.c: In function ‘main’:
inclue.c:4:5: error: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration]
   «  … mais linhas sobre o printf …

O que significa: "diretiva de pré-processamento inválida #inclue; você quis dizer #include?". Uma letra faltando. Como o stdio.h não foi incluído, vêm depois os erros do printf.

Como consertar: corrija a diretiva. Regra geral: conserte sempre o primeiro erro e compile de novo; muitas vezes os outros somem junto.

warning: ‘PRECO’ redefined
$ gcc -std=c23 -Wall -Wextra redef.c -o redef
redef.c:3:9: warning: ‘PRECO’ redefined
    3 | #define PRECO 15
      |         ^~~~~
redef.c:2:9: note: this is the location of the previous definition
    2 | #define PRECO 12
      |         ^~~~~

O que significa: o mesmo nome foi definido duas vezes com valores diferentes ("PRECO redefinido"; a nota diz "esta é a posição da definição anterior"). Vale a última, mas é quase sempre um engano, muitas vezes porque dois arquivos .h definem o mesmo nome.

Como consertar: deixe uma definição só. Se a troca é de propósito, faça #undef PRECO antes do novo #define.


Referência

EscritaO que faz
#include <arquivo.h>Cola um arquivo do sistema (procura em /usr/include e companhia)
#include "arquivo.h"Cola um arquivo seu (procura primeiro na pasta do .c)
#define NOME valorCria uma macro: NOME vira valor daí para baixo
#undef NOMEApaga a macro
#error "texto"Para a compilação com a mensagem
#warning "texto" C23Mostra um aviso e continua
__FILE__, __LINE__, __func__Arquivo, linha e função atuais
__DATE__, __TIME__Data e hora da compilação (texto)
__STDC_VERSION__Versão do C: 202311L no C23
gcc -E arquivo.cMostra o resultado do pré-processamento, com marcas de linha
gcc -E -P arquivo.cO mesmo, sem as marcas
gcc -I pasta …Acrescenta uma pasta à busca do #include

Exercícios

1. Ver o que o compilador recebe

Complete o comando que mostra o arquivo depois do pré-processamento, sem as marcas de linha:

gcc mini.c

2. Incluir um arquivo seu

Complete a linha que inclui o arquivo cardapio.h, que está na mesma pasta do seu .c:

#include

3. Uma constante

Complete para que MAX_ALUNOS vire 40 em todo o arquivo:

MAX_ALUNOS 40

4. Com #define PRECO 12; (com ponto e vírgula), em que se transforma int x = PRECO * 2;?

5. Um programa com printf("%s\n", __DATE__); foi compilado em 17 de outubro e rodado em 20 de outubro. O que ele mostra?

6. Qual é a diferença entre #error e #warning?

Missão final: a etiqueta da lojinha

Missão: o programa não compila: faltam as macros PRECO e QTD. Crie as duas com #define no lugar indicado (PRECO vale 15, QTD vale 4, sem = e sem ;) e rode. A última linha mostra, com __LINE__, a linha em que o printf dela está escrito. Sem avisos.

Lojinha da Ana
4 cadernos x R$ 15 = R$ 60
(etiqueta impressa pela linha 13 do programa)
#include <stdio.h>

#define LOJA "Lojinha da Ana"
// crie aqui PRECO (15)
// crie aqui QTD (4)

int main(void) {
    int total = PRECO * QTD;

    printf("%s\n", LOJA);
    printf("%d cadernos x R$ %d = R$ %d\n", QTD, PRECO, total);

    printf("(etiqueta impressa pela linha %d do programa)\n", __LINE__);
    return 0;
}
#include <stdio.h>

#define LOJA "Lojinha da Ana"
#define PRECO 15
#define QTD 4

int main(void) {
    int total = PRECO * QTD;

    printf("%s\n", LOJA);
    printf("%d cadernos x R$ %d = R$ %d\n", QTD, PRECO, total);

    printf("(etiqueta impressa pela linha %d do programa)\n", __LINE__);
    return 0;
}

Se a última linha mostrar outro número, é porque você acrescentou ou apagou linhas acima do printf: o __LINE__ sempre conta a linha em que está escrito. Volte o código a ter as macros nas linhas 4 e 5, no lugar dos comentários.


Resumo

  • O pré-processador roda antes do compilador. Ele segue as diretivas (linhas com #), apaga os comentários e entrega um texto C puro. Ele não entende C: só copia, troca e apaga texto.
  • gcc -E arquivo.c mostra esse texto (-P tira as marcas de linha). É o raio X para entender macros.
  • #include cola um arquivo inteiro. <stdio.h> procura nas pastas do sistema; "loja.h" procura primeiro na pasta do .c. Arquivo seu, entre aspas.
  • #define NOME valor cria uma macro, sem = e sem ;. A troca vale para palavras inteiras, fora de aspas, daí para baixo. #undef apaga.
  • __FILE__, __LINE__ e __func__ dizem onde você está; __DATE__ e __TIME__ guardam o momento da compilação; __STDC_VERSION__ diz a versão do C.
  • #error impede a compilação com uma mensagem sua; #warning (oficial no C23) só avisa.
  • Para números em código novo, constexpr (C23) costuma ser melhor que #define. A seguir: Macros com Parâmetros.