Por que C existe e o que ele quer de você

[338] Por que C existe e o que ele quer de você

Em 1972, reescrever o Unix a cada máquina nova era trabalho hercúleo — foi dessa frustração que Ritchie tirou o C. Daqui saem as quatro etapas entre o arquivo .c e o executável, o primeiro programa dissecado linha a linha, e a barganha da linguagem: controle total, responsabilidade total.
Linguagem C

8 min de leitura

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.

  1. Pré-processador — Ele lê seu arquivo e resolve tudo que começa com #. Substitui #include pelo conteúdo dos cabeçalhos, expande as macros de #define. O resultado é um arquivo .c "achatado", ainda em C, mas sem diretivas.

  2. Compilador — Traduz esse C para Assembly, a linguagem específica do seu processador.

  3. Montador (assembler) — Converte o Assembly em código de máquina binário, gerando um arquivo-objeto (.o), ainda incompleto.

  4. 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

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.

Comentários

Mais em Linguagem C

Processos: fork, exec e wait
Processos: fork, exec e wait

A linha do fork é executada uma vez e retorna duas — e é do valor diferente em…

Variáveis, Tipos e a Memória por Trás Deles
Variáveis, Tipos e a Memória por Trás Deles

Um int não tem 4 bytes garantidos: o padrão fixa apenas mínimos e uma ordem, e…

Unions e Enums: Economia e Clareza
Unions e Enums: Economia e Clareza

Uma struct reserva espaço para todos os campos ao mesmo tempo; a union, para…