Processo de compilação

Ao produzir um executável a partir de um arquivo de código-fonte C, o processo de compilação na verdade passa por quatro estágios separados e cada um gera um novo arquivo:

  • Pré-processamento - O pré-processador substitui todas as diretivas de pré-processamento no arquivo “.c” do código-fonte original pelo código da biblioteca que implementa essas diretivas. Por exemplo, a parte do código onde se encontram as diretivas #include <lib> será substituída pelo código da biblioteca que se deseja incluir. O arquivo gerado contendo as substituições tem formato de texto e geralmente possui uma extensão “.i”.

  • Tradução - O compilador traduz as instruções de alto nível do arquivo “.i” em instruções de linguagem Assembly de baixo nível. O arquivo gerado contendo a tradução tem formato de texto e normalmente tem uma extensão “.s”.

  • Assembling - O assembler converte as instruções de texto da linguagem Assembly criada no arquivo “.s” em código de máquina. O arquivo de objeto gerado contendo a conversão tem formato binário e geralmente tem uma extensão “.o”.

  • Vinculação (Linking) - O linker combina um ou mais arquivos de objeto binário “.o” em um único arquivo executável. O arquivo gerado tem formato binário e geralmente tem uma extensão de arquivo “.exe”.

Estritamente falando, “compilação” descreve os três primeiros passos citados acima, que utilizam um único arquivo de código-fonte C e geram um único arquivo de objeto binário. Se em algum lugar do código-fonte do programa for encontrado erros de sintaxe, como um ponto e vírgula ausente ou um parêntese ausente, eles serão relatados pelo compilador e a compilação falhará.

O vinculador (linker), por outro lado, pode utilizar vários arquivos de objeto e gerar um único arquivo executável. Isso permite a criação de programas grandes a partir de arquivos de objetos modulares que podem conter funções reutilizáveis. Se o linker encontrar uma função com o mesmo nome, definida em mais de um arquivo de objeto, ele relatará um erro e o arquivo executável não será criado.

Normalmente, os arquivos temporários criados durante os estágios intermediários do processo de compilação são excluídos automaticamente, mas eles podem ser salvos incluindo a opção -save-temps no comando do compilador.

Vamos ver tudo isso acontecendo na prática.

Na prática

Para esse exemplo criei um código bem simples que somente mostra a mensagem “Hello World!” na tela.

Então compilei o programa usando a opção -save-temps e como pode ser visto na imagem abaixo, todos os 4 arquivos foram criados.

O primeiro arquivo criado foi o “hello.i”, nele é possível ver que as diretivas de pré-processamento foram substituídas. Então, no local do #include <stdio.h> foi inserido todo o código presente na biblioteca stdio.

Agora que as substituições foram feitas, o próximo passo é realizar a tradução desse código para a linguagem assembly, então a partir do arquivo “hello.i” a tradução foi feita e o resultado foi salvo no arquivo “hello.s”.

Com as instruções em assembly criadas, é a hora do assembler converter essas instruções para a linguagem de máquina, e o resultado é salvo no arquivo “hello.o”. Esse arquivo possui o formato binário, então, não é possível visualizar o seu conteúdo.

Agora que o arquivo objeto foi criado, basta o linker combinar o arquivo “hello.o” em um objeto executável, nesse exemplo o nome escolhido para esse executável foi “hello.exe”.

Após todo esse processo, temos como resultado um programa funcional.