C é uma linguagem compilada: nada roda até que um compilador tenha traduzido seu código-fonte para código de máquina da sua CPU e do seu sistema operacional específicos. Essa etapa é um comando só, mas saber o que ela faz transforma a maioria das mensagens de erro de misteriosas em óbvias.
Os dois comandos
Salve isto como hello.c:
#include <stdio.h>
int main(void) {
printf("Olá, C!\n");
return 0;
}
Depois, na mesma pasta:
gcc hello.c -o hello
./hello
O primeiro comando compila. O segundo executa o resultado. No Windows o comando de execução é hello no Prompt de Comando ou .\hello no PowerShell - não há o prefixo ./, porque o Windows procura no diretório atual por padrão e os shells Unix deliberadamente não procuram.
Se você omitir o -o hello, o gcc dá ao resultado o nome a.out (ou a.exe), que é por que tutoriais mais antigos terminam com ./a.out. Sempre dê nome à sua saída; custa quatro caracteres e evita confusão.
Aqui está o mesmo programa no editor do navegador, que faz as duas etapas para você:
O que "compilar" realmente faz
gcc hello.c -o hello parece uma etapa só. São quatro, e cada uma pode falhar com seu próprio tipo de erro.
1. Pré-processamento. Antes de qualquer C ser compilado, o pré-processador trata toda linha que começa com #. #include <stdio.h> é literalmente substituído pelo conteúdo daquele arquivo de cabeçalho; macros #define são expandidas; blocos #ifdef são mantidos ou apagados. A saída é um grande arquivo C sem nenhuma linha com #. Dá para ver:
gcc -E hello.c
Isso imprime centenas de linhas - quase tudo é o stdio.h sendo colado ali.
2. Compilação. O C pré-processado é analisado, tem os tipos verificados e é traduzido em assembly para a sua CPU. É daqui que vêm os erros de sintaxe, os erros de tipo e os avisos.
3. Montagem. O assembly vira um arquivo objeto - código de máquina com espaços reservados onde ficam as chamadas a funções de outros arquivos.
gcc -c hello.c # produz hello.o, para antes da ligação
4. Ligação. Os arquivos objeto são costurados junto com a biblioteca padrão do C, cada espaço reservado é preenchido com um endereço real, e o resultado é um executável. É daqui que vêm os erros de "undefined reference".
A lição prática: um erro de compilação aponta para uma linha do seu código-fonte, e um erro de ligação não, porque a ligação acontece depois que toda linha já foi aceita.
Flags que valem a pena desde o primeiro dia
O gcc arquivo.c -o prog pelado aceita muito código perigoso em silêncio. Quatro flags mudam isso.
gcc -std=c17 -Wall -Wextra -g hello.c -o hello
-Wallliga os avisos comuns. Apesar do nome, não são "todos" os avisos - é o conjunto sensato.-Wextraacrescenta mais, incluindo parâmetros não usados e alguns erros de comparação.-std=c17fixa o padrão da linguagem para que seu código signifique a mesma coisa em toda máquina. Use-std=c99se estiver seguindo material mais antigo.-gmantém informações de depuração, para que ogdbou olldbpossam mostrar suas linhas de código reais quando algo travar.
Mais duas que você vai querer eventualmente:
-O2liga a otimização para compilações de produção. Deixe desligada enquanto aprende: código otimizado é mais difícil de depurar, e os avisos às vezes mudam.-fsanitize=address,undefined(gcc e clang) faz o programa abortar com uma mensagem clara no instante em que ele lê fora dos limites ou cai em comportamento indefinido. Essa é a flag mais útil para aprender C.
Experimente os avisos você mesmo. Este programa compila e executa, mas tem dois bugs de verdade:
count nunca recebe um valor, então o programa imprime os bytes que por acaso estavam ali. Com -Wall o compilador diz isso: 'count' is used uninitialized. Avisos em C quase nunca são ruído - trate-os como erros que você ainda não encontrou.
Compilar mais de um arquivo
Programas reais são divididos em arquivos. Passe todos para o gcc:
gcc -std=c17 -Wall main.c utils.c -o myprog
Ou compile cada um separadamente e ligue no fim, que é o que os sistemas de build fazem para que mudar um arquivo não recompile tudo:
gcc -c main.c # -> main.o
gcc -c utils.c # -> utils.o
gcc main.o utils.o -o myprog
Algumas bibliotecas precisam de uma flag de ligação explícita. A biblioteca de matemática é a que todo iniciante encontra:
gcc calc.c -o calc -lm
Sem -lm, usar sqrt de math.h compila normalmente e depois falha na ligação com undefined reference to sqrt - a declaração estava no cabeçalho, mas o código estava numa biblioteca que ninguém pediu.
Lendo os erros
Os erros do C são secos, mas consistentes. Três exemplos cobrem a maior parte do que você vai ver no começo.
Um ponto e vírgula faltando é reportado na linha seguinte, porque o compilador continuou lendo:
hello.c:5:5: error: expected ';' before 'return'
A correção pertence à linha 4, não à linha 5. Sempre que um erro apontar para uma linha que parece correta, confira a linha anterior.
Um cabeçalho faltando parece um mistério sobre uma função que você claramente escreveu certo:
hello.c:4:5: warning: implicit declaration of function 'printf'
Isso significa que o compilador nunca viu uma declaração de printf, ou seja, você esqueceu o #include <stdio.h>. No C99 e posteriores isso é um erro, não só um aviso.
Uma falha de ligação não tem número de linha nenhum:
/usr/bin/ld: main.o: in function `main':
main.c:(.text+0x1a): undefined reference to `helper'
O compilador acreditou em você que helper existe em algum lugar; o ligador procurou e não existe. Ou você nunca escreveu a definição, ou escreveu o nome de outro jeito, ou esqueceu de passar ao gcc o arquivo que a contém.
Corrija sempre o primeiro erro. Erros em C caem em cascata - uma linha ruim pode gerar vinte mensagens, e dezenove delas somem quando a primeira é corrigida.
Códigos de saída
O return 0 do main não é enfeite. É o status de saída do programa, e o shell consegue lê-lo:
Depois de executar, echo $? no macOS/Linux (ou echo %errorlevel% no Windows) imprime esse número. Scripts e ferramentas de build o usam para decidir se continuam. A convenção é absoluta: 0 é sucesso, e diferente de zero é um código de erro à sua escolha. O stdlib.h define EXIT_SUCCESS e EXIT_FAILURE se você preferir os nomes.
Desde o C99, escorregar até o fim do main sem um return é tratado como return 0 - mas escrevê-lo é mais claro, e ele é obrigatório para todas as outras funções.
Perguntas frequentes
Como eu compilo e executo um programa em C?
Salve o código como hello.c, depois rode gcc hello.c -o hello para compilar e ./hello para executar (só hello no Windows). Se o gcc não for encontrado, você precisa instalar um compilador primeiro.
O que o gcc -o faz?
-o dá nome ao arquivo de saída. gcc hello.c -o hello produz um executável chamado hello. Sem -o, o gcc escreve em a.out (ou a.exe no Windows), que é por que tantos tutoriais antigos rodam ./a.out.
Quais flags do gcc eu deveria usar sempre?
gcc -std=c17 -Wall -Wextra -g seuarquivo.c -o seuprograma. -Wall -Wextra ligam os avisos que pegam bugs de verdade, -std=c17 fixa a versão da linguagem e -g mantém os símbolos de depuração para um depurador poder mostrar seu código-fonte. Acrescente -O2 quando quiser velocidade numa compilação de produção.
O que significa "undefined reference to" em C?
É um erro do ligador: o compilador aceitou uma chamada a uma função, mas nenhuma definição dessa função foi encontrada. As causas comuns são um erro de digitação no nome, esquecer de compilar um segundo arquivo .c, ou usar uma função matemática sem ligar a biblioteca de matemática (-lm).