Toda linguagem de programação nasce de uma frustração. E poucas nasceram de uma frustração tão concreta quanto a do C. No início dos anos 1970, nos laboratórios da Bell, Dennis Ritchie e Ken Thompson tentavam construir um sistema operacional novo — o Unix. O problema era que reescrever um sistema operacional inteiro em Assembly, a cada nova máquina, era um trabalho hercúleo e intransferível. Precisavam de algo que fosse quase tão rápido quanto Assembly, mas portável entre máquinas diferentes. Em 1972, Ritchie criou o C para resolver exatamente isso.
O experimento deu tão certo que, em 1973, o Unix foi reescrito quase inteiramente em C. Pela primeira vez na história, um sistema operacional podia ser transportado para um novo computador recompilando o código, em vez de reescrevê-lo. Essa foi uma revolução silenciosa — e é a razão pela qual, mais de cinquenta anos depois, você ainda está lendo sobre C.
Por que aprender C hoje
É tentador achar que uma linguagem dos anos 70 seja peça de museu. É o oposto. O kernel do Linux, o do Windows e o do macOS têm C no seu coração. O Python que você talvez já conheça é, ele mesmo, escrito em C. Bancos de dados como PostgreSQL e SQLite, o interpretador do Ruby, o motor do Git, o firmware do seu micro-ondas, do seu carro e do satélite lá em cima — tudo isso é C, ou depende diretamente dele.
Mas há um motivo ainda mais forte para aprender C hoje: ele te obriga a entender o computador. Linguagens modernas escondem a memória de você, e por boas razões. C não esconde nada. Ao aprendê-lo, você vai entender o que realmente acontece quando um programa roda — como a memória é organizada, o que é um ponteiro, por que um programa trava. Esse conhecimento torna você melhor em qualquer linguagem que aprender depois.
O que C quer de você
Se o Rust quer que você prove ao compilador que seu código é seguro, o C quer outra coisa: ele quer confiar em você. C parte do princípio de que você sabe o que está fazendo. Ele te dá acesso direto à memória, ponteiros brutos, e quase nenhuma rede de proteção. Se você somar mal um índice e escrever fora de um vetor, C não vai reclamar — ele simplesmente vai escrever onde você mandou, e o resultado pode ser um bug misterioso três funções adiante.
Isso não é um defeito. É a filosofia da linguagem: controle total, com responsabilidade total. Aprender C é aprender a ser cuidadoso, disciplinado e consciente de cada byte. Essa disciplina é o verdadeiro presente da linguagem.
Como um programa em C vira um executável
Antes do primeiro código, você precisa entender uma coisa que a maioria dos cursos ignora: o que acontece entre escrever programa.c e rodar ./programa. Esse caminho tem quatro etapas, e conhecê-las vai te poupar horas de confusão no futuro.
-
Pré-processador — Ele lê seu arquivo e resolve tudo que começa com
#. Substitui#includepelo conteúdo dos cabeçalhos, expande as macros de#define. O resultado é um arquivo.c"achatado", ainda em C, mas sem diretivas. -
Compilador — Traduz esse C para Assembly, a linguagem específica do seu processador.
-
Montador (assembler) — Converte o Assembly em código de máquina binário, gerando um arquivo-objeto (
.o), ainda incompleto. -
Ligador (linker) — Junta seu arquivo-objeto com as bibliotecas necessárias (como a que contém o
printf) e produz finalmente o executável.
Quando você roda gcc programa.c -o programa, o GCC executa essas quatro etapas de uma vez. Mas cada uma pode falhar por um motivo diferente — e saber em qual delas o erro aconteceu é meio caminho para resolvê-lo.
Seu primeiro programa
Vamos ao clássico. Você vai precisar de um compilador; nos sistemas Linux e macOS, o GCC ou o Clang geralmente já estão instalados (ou disponíveis via sudo apt install gcc / Xcode Command Line Tools). No Windows, recomendo o MinGW-w64 ou o WSL.
Sem instalar nada, por enquanto: se quiser rodar este primeiro programa antes de montar o ambiente, cole-o no Compiler Explorer — ele compila e executa no navegador e mostra o Assembly ao lado, o que torna concretas as quatro etapas que acabamos de descrever. Para só compilar e rodar, o OnlineGDB basta. Vale como muleta nas primeiras aulas; da Fase 6 em diante, com Valgrind e sanitizers, você vai precisar da sua própria máquina.
Crie um arquivo chamado ola.c:
#include <stdio.h>
int main(void) {
printf("Olá, C. Vamos até o fim.\n");
return 0;
}
Compile e execute:
gcc ola.c -o ola
./ola
A saída:
Olá, C. Vamos até o fim.
Dissecando cada linha
Esse programa minúsculo já carrega quase toda a filosofia da linguagem. Vamos por partes.
#include <stdio.h> — Uma diretiva de pré-processador. Ela traz para o seu arquivo o cabeçalho stdio.h (standard input/output), que declara funções como o printf. Sem essa linha, o compilador não saberia o que é printf.
int main(void) — Todo programa em C começa a executar pela função main. O int diz que ela devolve um número inteiro ao sistema operacional ao terminar. O void entre parênteses indica explicitamente que ela não recebe argumentos.
printf("...\n") — Imprime texto na tela. O \n é uma sequência de escape que representa uma quebra de linha. Diferente de outras linguagens, o C não pula a linha sozinho — se você esquecer o \n, a saída sai grudada.
return 0; — Devolve o valor 0 ao sistema operacional. Por convenção, 0 significa "terminou com sucesso". Qualquer outro número sinaliza um erro. Esse detalhe é a base de como scripts e outros programas sabem se o seu funcionou.
Repare no ponto e vírgula ao fim de cada instrução. Em C, ele não é opcional — é o que separa uma instrução da outra. Esquecê-lo é o erro de compilação mais comum de quem está começando.
Para a próxima aula
Hoje entendemos de onde o C veio, por que ele ainda importa, e o caminho que um programa percorre até virar executável. No artigo Variáveis, Tipos e a Memória por Trás Deles, vamos ver variáveis e tipos — e você vai descobrir que, ao contrário do Rust, em C as variáveis são mutáveis por padrão, e que o próprio tamanho de um int pode mudar de máquina para máquina. Vamos aprender a lidar com isso.
Até lá, instale seu compilador, escreva esse primeiro programa e faça-o rodar. Sinta o ciclo: escrever, compilar, executar. É esse ciclo que vamos repetir por um ano inteiro.
A jornada de um ano começa com um único int main.
Fontes e leituras recomendadas
- The C Programming Language (K&R), Kernighan & Ritchie — o livro definitivo, escrito pelo próprio criador — Cap. 1
- Modern C, Jens Gustedt — livro gratuito e atualizado (C17/C23) — https://gustedt.gitlabpages.inria.fr/modern-c/
- GCC Documentation — o manual do compilador que usaremos — https://gcc.gnu.org/onlinedocs/
- cppreference — C reference — referência técnica completa da linguagem — https://en.cppreference.com/w/c
- Dennis Ritchie, The Development of the C Language — o relato histórico do próprio autor — https://www.bell-labs.com/usr/dmr/www/chist.html
Exercícios
Exercício 1
Modifique o programa ola.c para imprimir seu nome em uma linha e sua cidade na linha seguinte, usando um único printf.
Ver resposta
✓ Resposta: Basta usar duas sequências \n em uma só chamada:
#include <stdio.h>
int main(void) {
printf("Marcelo Fontes\nCuritiba\n");
return 0;
}
O \n no meio da string quebra a linha; o do fim garante que o cursor termine numa linha nova.
Exercício 2
O que acontece se você remover o \n do printf? Compile, rode e observe a diferença na saída.
Ver resposta
✓ Resposta: Sem o \n, o texto é impresso e o cursor não pula de linha. O prompt do terminal aparece grudado logo após a saída (algo como Olá, C.$). C nunca adiciona quebras de linha automaticamente — a responsabilidade é sempre sua.
Exercício 3
Explique, com suas palavras, a diferença entre a etapa de compilação e a etapa de ligação (linking).
Ver resposta
✓ Resposta: A compilação traduz o seu código C em código de máquina, gerando um arquivo-objeto (.o) que ainda tem "buracos": ele usa funções como printf sem saber onde elas estão. A ligação preenche esses buracos, juntando seu objeto com as bibliotecas que contêm essas funções, e produz o executável final. Um erro de compilação é de sintaxe/tipo; um erro de ligação é "encontrei a chamada, mas não achei a implementação".
Exercício 4
O que o número em return 0; significa para o sistema operacional? O que mudaria se fosse return 1;?
Ver resposta
✓ Resposta: O valor de retorno de main é o código de saída do programa. Por convenção, 0 significa sucesso e qualquer valor diferente de zero significa erro. Com return 1;, o sistema operacional (e qualquer script que tenha chamado seu programa) entenderia que ele falhou. É assim que, por exemplo, um programa && echo ok decide se roda ou não o echo.
Exercício 5
Compile o programa apenas até a etapa de pré-processamento com gcc -E ola.c e observe o resultado. Por que o arquivo ficou tão grande, se você escreveu só seis linhas?
Ver resposta
✓ Resposta: O -E para o processo logo após o pré-processador. O arquivo fica enorme porque o #include <stdio.h> foi substituído pelo conteúdo inteiro do cabeçalho stdio.h (e de tudo que ele, por sua vez, inclui) — centenas de declarações de funções e tipos. Você escreveu seis linhas; o pré-processador colou milhares antes de entregar ao compilador.