Compilação e Diretivas de pré-processamento

Você sabe quais os passos para se executar um programa em C++? Não? Então vamos olhar por baixo do capô para entender.

Tudo começa com o processo de compilação que gera um arquivo binário executável a partir do código fonte escrito pelo programador. Este processo envolve 3 programas: pré-processador, compilador e linker.

Diretivas de compilação

As diretivas são comandos que não são compilados e sempre iniciam com o caractere #.

Uma diretiva pode ser colocada em qualquer parte do programa e cada uma cumpre com uma tarefa (que veremos mais adiante), as principais são:

  • #include
  • #define
  • #undef
  • #ifdef
  • #ifndef
  • #if
  • #else
  • #elif
  • #endif
  • #pragma

Pré-processador

Seu papel é analisar o código-fonte e realizar modificações nele antes de passá-lo para o compilador. As diretivas dizem ao pré-processador se alguma parte do código deve ser compilada ou não, se o conteúdo de outro arquivo deve ser copiado para determinado local no código, se uma mensagem de erro será exibida em tempo de compilação, entre outras regras. Além de lidar com as diretivas ainda é responsável por remover comentários, substituir espaços em branco e junta linhas separadas por sequências de escape. Ufa!

Linguagem de máquina

A linguagem de máquina nada mais é do que uma sequência de bits (0s e 1s) decodificadas pela CPU na forma de instruções bem simples, tais como carregar dados na memória, somar dois valores, mover dados entre registradores.

Compilador

O compilador é o programa que traduz o código fonte em linguagem de máquina para que o computador entenda o que deve ser feito. O resultado desta tradução é um arquivo objeto com extensão .o ou .obj.

Linker

Mas você ainda não pode executar um arquivo objeto pois o conteúdo dele estará incompleto. Por exemplo, se você colocar um comando printf para exibir uma mensagem na tela, apesar de seu código ter sido compilado, o arquivo objeto gerado não saberá o que é printf, ele só possui uma referência a essa função, mas sua estrutura se encontra em uma biblioteca a parte. O linker é responsável linkar (ligar, unir) as instruções que estão em outras bibliotecas ou em outros arquivos objeto e gerar um único arquivo executável.

É importante notar que um executável não precisa conter todas as funções. Por exemplo, o Windows utiliza bibliotecas no mesmo formato de um executável, as famosas DLLs (Dynamic-link library). Essas bibliotecas contém códigos, dados, recursos e até outras bibliotecas a parte. A vantagem é que podem ser carregadas apenas uma vez na memória e utilizadas por outros programas que invocam suas funções, reduzindo o tamanho dos executáveis.

compilation-cpp

Diretivas de pré-processamento

Legal, agora que você já sabe como seu programa é compilado vamos dar uma olhada nas diretivas vistas acima.

#include

Para incluir um arquivo usamos a diretiva include. Ela diz ao compilador para copiar o conteúdo deste arquivo para alguma parte do seu código.

Existem certas coisas que você precisa saber quando for incluir um arquivo de cabeçalho:

  • Para importar uma biblioteca em C é preciso indicar a extensão. Exemplo: #include <stdio.h>, #include <stdlib.h>
  • Para importar uma biblioteca em C++ não é preciso indicar a extensão. Exemplo: #include <iostream>, #include <fstream>
  • Você pode encontrar arquivos de cabeçalho que utilizam uma dessas extensões dependendo da convenção utilizada .h, .h++, .hh, .hxx e .hpp, sendo mais comum usar “.hpp” para C++ e “.h” para C.
  • Existem dois modos de inclusão, entre colchetes angulares <> ou entre aspas  “”
    <> – o arquivo deve ser procurado no diretório padrão especificado pelo compilador
    “” – o arquivo deve ser procurado no mesmo diretório do arquivo que contém a instrução #include.

#define

Serve para darmos nome a uma constante:

#define PI 3.1415926

Ou definir uma função simples:

#define DIV(x,y) ((x)/(y))

Toda vez que o pré-processador encontra o nome da constante ou a expressão no código realiza a substituição.

#undef

Remove um nome criado com #define e ignora as ocorrências subsequentes. Exemplo:

#undef PI

#undef DIV

#ifdef e #ifndef

Essas diretivas verificam a presença (#ifdef) ou ausência (#ifndef) de identificadores definidos com #define antes de compilar o bloco de código. São úteis para compatibilidade com versões anteriores da linguagem. Exemplo:

#ifdef PI

valor = valor * PI;

#endif

#if, #elif, #else e #endif 

Compila blocos do código condicionalmente. Exemplo:

#if   expressão_1
código

#elif expressão_2
código

#else expressão_n
código

#endif

#pragma

Os pragmas são específicos do computador ou do sistema operacional e podem variar de um compilador para outro. Se o compilador não identificar o nome da diretiva irá ignorar o #pragma.

Um do mais comuns é o #pragma once utilizado para indicar que o arquivo atual deve ser incluído apenas uma vez durante o processo de compilação.

Deixe um comentário