Ponto V!

Home C/C++ Conceitos Básicos Compilação de programas C/C++
Bruno Crivelari Sanches
Compilação de programas C/C++Imprimir
Escrito por Bruno Crivelari Sanches

A compilação de um programa C/C++ envolve três passos principais: pré-processador, compilação e linkagem, neste artigo vamos ver o trabalho que cada um desses passos realiza.

Quando invocamos a compilação na nossa IDE favorita ou usando um makefile para cada arquivo C/C++ (arquivos .c, cpp, cc, etc) é executado o pré-processador (que executa as diretivas #ifdef, #include, substituições de macros, etc), o resultado disso é um arquivo temporário com o código fonte pré-processado Em seguida é invocado o compilador que processa este arquivo, caso não ocorram erros é gerado o arquivo objeto (geralmente extensão obj ou .o). Após a geração de todos arquivos objetos é invocado o linker, que faz a linkagem de todos os arquivos objeto gerando finalmente o executável (exe no caso do windows) ou biblioteca dinâmica (dlls no Windows).

Pré Processador

O pré-processador é a primeira etapa da compilação, pode ser ou não um programa separado do compilador, mas o que importa mesmo é que ele é o primeiro a “tocar” o código e cuida de algumas tarefas como:

  • Junta linhas que foram separadas por sequências de escape.
  • Remove comentários e os substitui por espaços em branco
  • Expande macros
  • Processa diretivas de pré-processamento

O comando de pré-processamento que provavelmente é mais conhecido pelos desenvolvedores é o include, que é resolvido pelo pré-processador. O include consiste em apenas abrir o arquivo indicado, copiar o seu conteúdo e colar em cima do comando include, sendo que o include pode ter duas formas:

  • #include <stdio.h>
  • #include “objeto.h”

Na primeira forma, usando-se < e > o pré-processador procura pelo arquivo indicado apenas nos diretórios de include que o compilador estiver configurado, já na segunda opção com aspas, o pré-processador procura pelo arquivo no diretório dos fontes do programa.

Vale lembrar que arquivos de header (extensões h, hpp, etc) não são compilados diretamente pelo compilador, mas apenas quando são incluídos em algum arquivo de código (c, cpp, cc, etc).

Sobre a compilação condicional é quando usamos comandos como #ifdef, #if, etc. Estes comandos são bem uteis quando queremos compilar certas partes do código em apenas algumas situações, exemplo:

FILE *abre_arquivo(const char *fileName)
{
    #ifdef DEBUG
    printf("Abrindo %s\n", fileName);
    #endif

    return fopen(fileName, "rb");
}

No código acima o printf é executado apenas quando DEBUG é definido, que pode ser feito usando-se “#define DEBUG” em algum ponto do código ou configurando o compilador para fazer isso automaticamente, no caso do visual, clique com botão direito no projeto (na aba “Solution Explorer”), secione Propriedades, depois C/C++ –> Pre-Processor e finalmente coloque na caixa de texto “Preprocecssor Definitions” o nome do seu macro.

O comando #ifdef é útil também na hora da portabilidade, podemos forçar a inclusão de determinados arquivos em diferentes plataformas, ligar e desligar testes extras em compilações para debug, etc.

Um detalhe do pré processador que passa despercebido pela maioria no inicio é que os macros não são nada além de substituição de texto, exemplo:

#define TAM 100

int meuArray[TAM];

O código acima após o pré-processamento deve ficar idêntico código abaixo:

int meuArray[100];

Sendo assim, este é o código que o pré-processador vai gerar, ou seja, para o compilador C/C++ TAM nunca existiu, pois quando o compilador foi processar o código fonte TAM e os demais macros já vão ter sido removidos e substituídos onde tiver sido necessário.

No caso do C++ é recomendável usar "const" ao invés de macros para definir constantes, pois estas minimizam a chance de erros, principalmente pelo fato que vão ser compiladas pelo compilador C++ e este poderá fazer verificações, como, por exemplo, se os tipos estão corretos

Compilador C/C++

Agora que já temos uma noção melhor do trabalho do pré-processador vamos entender o papel do compilador. O compilador processa o resultado do pré-processador e para cada unidade compilação gera um arquivo objeto.

O compilador C trata cada arquivo fonte como sendo uma unidade de compilação, isso quer dizer que ele compila cada um de maneira independente, ignorando a existência de qualquer outro arquivo. A única forma do compilador considerar um outro arquivo é através da diretiva include, que já foi processada no passo anterior pelo pré-processador.

Resumindo: se seu código em um arquivo a.c tenta acessar uma função que tenha sido declarada apenas em b.c isso gera um erro de compilação no arquivo a.c, este erro pode ser evitado declarando-se (note que declarar não é definir) novamente a função.

Com base no que foi explicado até o momento já é possível ver que seria possível programar em C/C++ sem usar includes, apenas declarando-se funções e o que mais for preciso em cada arquivo fonte, mas isso daria um trabalhão e não seria nada produtivo.

O pré-processador permite que o construtor do compilador possa focar seus esforços na principal tarefa deste, que consiste em verificar se o código esta bem construído segundo a gramática da linguagem. Após verificar que não existe qualquer erro sintático (como, por exemplo, um ; faltando) ou semântico (como, por exemplo, uma variável definida duas vezes) ele já pode então gerar o código em linguagem de maquina (note que essa é uma explicação extremamente simples das tarefas de um compilador) que resulta no arquivo objeto.

No arquivo objeto, quando o compilador precisa inserir uma chamada para uma função x definida em outro arquivo, este coloca apenas uma anotação que ali a função deve ser chamada, pois como ele não compilou esta função (tendo visto apenas uma declaração desta) ele não sabe dizer em qual endereço ela esta. Dessa forma as chamadas de funções e acesso a variáveis globais em arquivos objetos são apenas temporários.

Linker

Finalmente, após pré-processar, compilar cada arquivo fonte e gerar todos os arquivos objetos podemos invocar o linker. O linker ou programa de ligações é responsável por juntar todos os arquivos objetos e gerar o arquivo executável (pode ser usado também para gerar bibliotecas dinâmicas, como dlls).

Dessa forma a tarefa dele é substituir todos as chamadas de funções e acessos a variáveis em arquivos objeto pelo endereço real de onde esse item se encontra, vemos também que ele fica responsável por organizar cada função, variável dentro do espaço de memória do executável.

Perceba então que o linker é o responsável pelo erro que persegue muitos iniciantes, vamos ver um exemplo:

void proc();

int main(int argc, char* argv[])
{
    proc();

    return 0;
}

Compilando esse código com o visual temos a seguinte mensagem de erro:

  • error LNK2019: unresolved external symbol _proc referenced in function _main

O erro acima (do visual) indica que houve um erro de linkagem, pois o simbolo “_proc” que é referenciado em “_main” não foi encontrado. Detalhe que a compilação foi bem sucedida, pois para o compilador não interessa se a função “proc” realmente existe ou não, o que importa é que ela foi declarada. As funções compiladas pelo C são geradas com um “_” devido ao name mangling, que é usado para evitar conflitos de nomes.

Mas o linker não conseguiu encontrar a definição desta função, então temos o erro. É bem comum termos esse erro quando usamos alguma biblioteca externa e esquecemos de linkar os arquivos lib (ou .a) com o projeto.

Alias, falando em bibliotecas, é tarefa do linker juntar no executável final qualquer biblioteca extra que for especificada, mas como ele não possui super-poderes é preciso dizer a ele quais bibliotecas usar (e cada compilador e/ou IDE costumar ter a sua forma).

Por fim, se o linker conseguir encontrar todos os simbolos ele vai finalmente gerar o arquivo executavel e o programa poderá ser usado!

Notas Finais

Vimos aqui um resumo (não muito pequeno) de como funciona o processo de compilação de um programa C/C++. Infelizmente não é possível cobrir todos os tópicos em um único artigo (sem que este fique muito extenso), então para se aprofundar um pouco mais no assunto sugiro a leitura:


Comentários (35)
  • George Brayner  - Quem é vivo sempre aparece!
    avatar

    E aí guri! Ta na área né, como andam as coisas, depois da Ignis não nos comunicamos mais! To trabalhando num studio de outsourcing www.playlore.com dá uma olhada! abraços e boa sorte!

  • bruno  - otimo
    avatar

    show de bola o artigo.

  • Bruno Crivelari Sanches
    avatar

    Obrigado Bruno!

  • Carolina Fantini
    avatar

    Gostei muito do artigo Bruno, aprendi umas coisinhas básicas que eu não sabia. ^^

  • Bruno Crivelari Sanches
    avatar

    Que bom Carolina, fico feliz em saber! Obrigado! :)

  • Marcelo Pereira Diniz.  - Linguagem de programação.
    avatar

    Quero me tornar um xper na programação de computadores. Quais são os melhores livros pois eu acho as informações na internet um pouco fraca.

  • Alex Vital
    avatar

    Oi Bruno,

    1) Dessa forma a tarefa dele é substituir todos as chamadas de funções e acessos a variáveis em arquivos objeto pelo endereço real de onde esse item se encontra,

    2) vemos também que ele fica responsável por organizar cada função, variável dentro do espaço de memória do executável.

    A 1° parte eu entendi, o linker, ele substitui no arquivo objeto a chamada de uma função ou variável pelo seu endereço na memória.

    Que memória seria essa, a memória ram ou no HD?

    e na 2 parte o que seria ficar responsável por organizar cada função, variável dentro do espaço de memória do executável?

    e o que é espaço de memória do executável?

    Valeu.

  • Anônimo
    avatar

    Oi Alex,

    > Que memória seria essa, a memória ram ou no HD?

    Memória RAM, o executável depois de montado quando você executa ele teoricamente é copiado para RAM (não todo ou apenas partes, depende da plataforma). Mas conceitualmente é como se ele fosse inteiro para RAM, então cada parte de código vai estar em um determinado endereço.

    >>e na 2 parte o que seria ficar responsável por organizar cada função, variável dentro do espaço de memória do executável?

    >e o que é espaço de memória do executável?

    É justamente a memória RAM que seu executável vai usar, imagine que todo código dele (as instruções) vão estar na RAM, o linker é quem organiza isso e no final, o que você no arquivo exe é uma imagem do que vai para memória RAM.

  • Alex Vital
    avatar

    Então o linker atua em tempo de execução, é isso?

  • Ricardo Erick Rebêlo  - O que o linker faz
    avatar

    Não, as únicas coisas que o compilador não faz é criar a entrada e a saída do programa e encontrar os endereços de boa parte dos objetos dentro do programa.

    O linker "junta" o código, mas falta os endereços. Mas ele, ao juntar, foi esperto e anotou os endereços do código quando junto. Depois disso, ele coloca os endereços novos nos lugares referenciados pelo código.

    Por isso o nome "linker", porque ele sabe quem precisa, sabe os endereços (ele que uniu o código) e ele que coloca os endereços (links) nas referências corretas.

  • Anônimo
    avatar

    Não, ele é feito após a compilação e é ele quem gera o arquivo executável.

  • Ismael Saraiva  - Parabéns!
    avatar

    Parabéns sobre esta publicação. Ótima matéria. Vou te citar em meu PIM deste semestre. Parabéns e obrigado.

  • Anônimo
    avatar

    Obrigado Ismael!

  • Helen Paiva  - Excelente post
    avatar

    Texto muito bem escrito, parabéns, publicação com qualidade!

  • Anônimo
    avatar

    Obrigado Helen!

  • Almir  - Arquivo fonte não compilado.
    avatar

    Olá Bruno, tudo bem, não sei se você já usou o dev c++, ou se já passou por um problema que estou passando, o problema é o seguinte, instalei o
    dev no W8 e ao dar F9 não compila, aparece "arquivo fonte não compilado", se vc já passou por isso ou conhece alguém que já passou, e poder dar uma força, agradecerei, até mais.

  • Anônimo
    avatar

    Desinstale o devC++, apague qualquer vestigio desse lixo digital do seu computador e instale algo que funcione, tipo Visual C++, Qt Creator, Code Blocks, etc.

    O DevC++ é lixo digital e não serve para nada além de bugs. Parece que andaram atualizando, mas ficou abandonado por anos, usando um compilador defasado e duvido que preste para algo mesmo com atualizações.

    O que você vai mais achar em fóruns é pessoas pedindo ajuda porque o DevC++ não funciona.

    Se não bastasse ele ser o que é, ainda por cima é feito em Delphi, ou seja, quem desenvolve não usa e certamente não tem a menor noção das necessidades de um desenvolvedor C/C++.

  • Anônimo
    avatar

    Valeu cara vou instalar o code, novamente.

  • Ricardo Erick Rebêlo
    avatar

    O Orwell Dev-C++ é uma nova versão com um compilador pesadíssimo, mas bom. Serve pra projetos grandes. o Code::Blocks gera executáveis muito mais enxutos (vai de 28k a 6000k a diferença, porque a stdio do compilador do Orwell Dev-C++ deve fazer até boquete).

    Mas se seu Dev não for Orwell, jogue fora.

  • Guilherme Kodama  - Muito bom
    avatar

    Parabéns pelo trabalho , você explicou melhor que muitos professores meus da faculdade , continue com o bom trabalho!

  • Anônimo
    avatar

    Obrigado!

  • marcio  - ajuda
    avatar

    E ai Bruno blz,

    Vê se me ajuda ai, estou com um problema ao tentar compilar no codeblocks. Quando clico para compilar ele da uma mensagem de: Parece que este projeto não está construído. Quer construí-lo agora. E em baixo aparece esta msg:

    Checking for existence: E:\Meus documentos\MARCIO UNISINOS\oi\bin\Debug\oi.exe
    Executing: "C:\Arquivos de programas\CodeBlocks/cb_console_runner.exe" "E:\Meus documentos\MARCIO UNISINOS\oi\bin\Debug\oi.exe" (in E:\Meus documentos\MARCIO UNISINOS\oi\.)
    Process terminated with status -1073741510 (0 minutes, 4 seconds)

    O programa é simples mais não rola:

    #include

    int main()
    {
    printf( "Oi como vai vc";);
    }

    Se puder me ajudar

  • Anônimo
    avatar

    Marcio eu não uso o codeblocks, apens visual C++.

    A mensagem de erro não parece ser erro de compilação, pois ele diz apenas que não encontrou o executável e tenta construir. Não tem nenhuma outra mensagem além desse "Process terminated ... " ?

    No windows, eu sempre recomendo usar o visual.

    T+

  • marcio
    avatar

    Não aparece nenhuma outra mensagem, só esta tela de informação, sou iniciante em programação, pode ter algum erro de configuração do programa? Esse visual T+ é fácil de manusear?

  • Ricardo Erick Rebêlo
    avatar

    Tente colocar:

    #include

    no começo, esse #include sozinho aí é o problema.

  • Anônimo
    avatar

    Eu considero o Visual a melhor ferramenta para desenvolvimento C/C++, aqui no portal temos tutoriais de como instalar e usar: http://www.pontov.com.br/site/index.php/cpp/41-visual-c/59-como-utiliz ar-o-visual-c-parte-1

  • mauro lima
    avatar

    javascript:JOSC_emoticon(":cheer:";) :lol: nota 10 valeu

  • Ricardo Erick Rebêlo  - Compilação em separado
    avatar

    Já pensou em acrescentar algum conteúdo sobre compilação em separado? Em C/C++ existem formas de obter o arquivo compilado (.o, .obj em gcc, .a em algumas IDEs para Windows).

    Esses arquivos fazem a vez dos arquivos .c, mas já, digamos, compilados, sendo que criá-los é a parte mais demorada do processo de criação de um executável.

    Ao se utilizar de uma biblioteca (ex.: convNFe), se coloca a linha:

    include "convNFe.h"

    que, como tem TODAS as declarações, irá compilar. Mas dará erro na linkagem se não tivermos inserido, na lista das bibliotecas do link-editor, o arquivo convNFe.a (por exemplo).

    Mas, o que se ganha com isso? Imagina um projeto que use 10 bibliotecas que levam 20 minutos para compilarem. Mas o projeto, em si, leva apenas 2 minutos. Imagina que você quer entregar uma correção para um cliente. Ou imagine que você está vendendo uma biblioteca e o cliente não quer pagar pela disponibilização do código.

    Agora ficam claras as vantagens de compilar em separado. Por isso a link-edição é sempre planejada pra ter o mínimo de trabalho (em termos de consumo de tempo dentro do processo total de compilação).

    Eu criei um tutorial passo-a-passo (ao menos para as IDEs Code::Blocks e Orwell Dev-C++) que disponibilizo abaixo. Estou a disposição para trocar ideias e conteúdo.

    https://jacknpoeexplicabackup.wordpress.com/2013/11/13/criando-bibl iotecas-em-c-parte-1-arquivo-de-cabecalho/

  • Ricardo Erick Rebêlo
    avatar

    CORREÇÃO: https://jacknpoeexplicabackup.wordpress.com/2013/11/13/criando-bibliot ecas-em-c-parte-1-arquivo-de-cabecalho/

  • Ricardo Erick Rebêlo
    avatar

    AF: https://jacknpoeexplicabackup.wordpress.com/2013/11/13/criando-bibliot ecas-em-c-parte-1-arquivo-de-cabecalho/

  • Ricardo Erick Rebêlo
    avatar

    https://jacknpoeexplicabackup.wordpress.com/2013/11/13/criando-bibl iotecas-em-c-parte-1-arquivo-de-cabecalho/

  • Rogerio
    avatar

    Bom dia Bruno,tenho um livro que diz que o linker age no assembler.E as
    biblioteca são aí incluidas.Procede?

  • JULIANO COSTA  - preciso da resposta correta para a pergunta
    avatar

    Quais das definições abaixo melhor define compilador?
    Escolha somente UMA resposta

    A) Programa para busca de comandos referentes a uma linguagem específica

    B) É um programa que traduz uma linguagem de programação para uma linguagem que a máquina especifica para um sistema.

    C) É um programa que permite cálculos matemáticos no sistema

    AGUARDO RESPOSTA. GRATO DESDE JÁ

  • Anônimo  - duvidas
    avatar

    Boa noite Bruno
    Meu nome é Lúcia minha dúvida é que meu filho precisa fazer um trabalho sobre informatica usando programa CC + como somos leigos no assunto queremos saber se é esse artigo q vc descreveu acima.

    obg

Escrever um comentário
Your Contact Details:
Gravatar enabled
Comentário:
[b] [i] [u] [url] [quote] [code] [img]   
:angry::0:confused::cheer:B):evil::silly::dry::lol::kiss::D:pinch::(:shock:
:X:side::):P:unsure::woohoo::huh::whistle:;):S:!::?::idea::arrow:
Security
Por favor coloque o código anti-spam que você lê na imagem.
LAST_UPDATED2  

Busca

Linguagens

Twitter