fbpx

Um compilador é um programa especial que processa instruções escritas em uma linguagem de programação particular e a transforma em linguagem ou código de máquina.

Geralmente, um programador escreve instruções em uma linguagem de alto nível como Pascal ou C, usando um editor de textos. O arquivo que é criado contém o que chamamos de fonte do programa.

O programador então executa o compilador de linguagem apropriado, especificando o nome do arquivo que contém as instruções fonte.

Quando executado, o compilador primeiro analisa todas as instruções da linguagem sintaticamente uma após a outra e então, em um ou mais estágios ou “passos” sucessivos, constrói o código de saída.

Tradicionalmente, a saída da compilação é chamada de código objeto ou algumas vezes de módulo objeto. O código objeto é o código de máquina que o processador pode executar.

Tradicionalmente em alguns sistemas e linguagens, um passo adicional é necessário após a compilação.

Este passo é destinado a resolver o local relativo de instruções e dados quando mais de um módulo objeto é executado ao mesmo tempo e eles possuírem referências cruzadas de um para o outro. Este processo é conhecido como ligação.

O compilador mais utilizado no Linux é a Coleção de Compiladores GNU – GCC. Ele compila códigos C ANSI, bem como C++, Java e Fortran. O GCC suporta vários níveis de checagem de erros nos códigos-fonte, produz informações de debug e pode ainda otimizar o arquivo objeto produzido.

A compilação envolve até quatro estágios: pré-processamento, compilação propriamente dita, assembly e ligação, sempre nesta ordem.

O GCC é capaz de pré-processar e compilar vários arquivos em um ou mais arquivos em assembler.

O arquivo de assembler gera um ou mais arquivos chamados de objeto e estes são ligados às bibliotecas (linking) para tornarem um arquivo executável.

fig001 Compilando programas no Linux com o GCC

PRÉ-PROCESSAMENTO

O pré-processamento é responsável por expandir as macros e incluir os arquivos de cabeçalho no arquivo fonte. O resultado é um arquivo que contém o código fonte expandido.

COMPILAÇÃO

O próximo estágio chamado de “compilação propriamente dita” é responsável por traduzir o código fonte pré-processado em linguagem assembly (linguagem de máquina) para um processador específico.

ASSEMBLER

O estágio seguinte é chamado de assembler. Nesta etapa o GCC converte o código de máquina de um processador específico em um arquivo objeto.

Se neste código existirem chamadas externas de funções, o gcc deixa seus endereços indefinidos para serem preenchidos posteriormente pelo estágio de ligação.

LINKER

O último estágio chamado de ligação, ou linker, é responsável por ligar os arquivos objeto para criar um arquivo executável.

Ele faz isto preenchendo os endereços das funções indefinidas nos arquivos objeto com os endereços das bibliotecas externas do sistema operacional.

Isto é necessário porque os arquivos executáveis precisam de muitas funções externas do sistema e de bibliotecas do C para serem executados.

As bibliotecas podem ser ligadas ao executável preenchendo os endereços das bibliotecas nas chamadas externas ou de forma estática quando as funções das bibliotecas são copiadas para o executável.

No primeiro caso, o programa utilizará bibliotecas de forma compartilhada e ficará dependente delas para funcionar.

Este esquema economiza recursos, pois, uma biblioteca utilizada por muitos programas precisa ser carregada somente uma vez na memória. O tamanho do executável será pequeno.

Porém, se a biblioteca instalada for de uma versão diferente da que o executável necessita, o programa não será executado até que a versão apropriada da biblioteca seja instalada.

No segundo caso, o programa é independente, uma vez que as funções de que necessita estão no seu código.

Este esquema permite que quando houver uma mudança de versão de biblioteca o programa não será afetado. A desvantagem será o tamanho do executável e necessidades de mais recursos.

O GCC – GNU Compiler Collection

O GNU Compiler Collection – coleção de compiladores GNU – são compiladores completos da linguagem C ANSI com suporte para o K&R C, C++, Objective C, Java, e Fortran. O GCC oferece também diferentes níveis de checagem de erros de código-fonte e informações para depuração e otimizações do programa objeto.

Ele também suporta a moderna plataforma de processadores Intel IA-64. A versão 7.0 inclui novas APIs e bibliotecas C++. O manual também foi substancialmente reescrito e melhorado.

Pré-Processamento

O pré-processamento é responsável por expandir as macros e incluir os arquivos de cabeçalho no arquivo fonte. O resultado é um arquivo que contém o código fonte expandido.

O pré-processamento do simples exemplo abaixo irá gerar um arquivo em C com mais de 800 linhas de código expandido.

Veja como fica este simples programa estilo Hello World que Imprime Certificação Linux na tela:

#include <stdio.h>

int main()

{

        printf(“Certificação Linux!\n”);

};

A opção “-E” do GCC diz ao compilador para fazer apenas o pré-processamento do código fonte.

# gcc –E teste.c –o teste.i

Veja que o arquivo preprocessado teste.i gerou 841 linhas de código pré-processado:

# cat teste.i | wc

    841    2074   16879

Compilação no GCC

O próximo estágio chamado de “compilação propriamente dita” é responsável por traduzir o código-fonte pré-processado em linguagem assembly (linguagem de máquina) para um processador específico.

Para que você veja o resultado do estágio de compilação, a opção “-S” (maiúsculo) do GCC gera o código de máquina para um processador específico.

O código abaixo é o exemplo acima já pré-processado que foi compilado em assembly para o processador Intel Xeon:

# gcc –S teste.i  

# cat teste.s

.file “teste.c”

.section .rodata

.LC0:

.string “Certifica\303\247\303\243o Linux!”

.text

.globl main

.type main, @function

main:

.LFB0:

.cfi_startproc

pushq %rbp

.cfi_def_cfa_offset 16

.cfi_offset 6, -16

movq %rsp, %rbp

.cfi_def_cfa_register 6

movl $.LC0, %edi

call puts

popq %rbp

.cfi_def_cfa 7, 8

ret

.cfi_endproc

.LFE0:

.size main, .-main

.ident “GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-28)”

.section .note.GNU-stack,””,@progbits

Assembler do Código no gcc

O estágio seguinte é chamado de assembler. Nesta etapa o GCC converte o código de máquina de um processador específico em um arquivo objeto. Se neste código existirem chamadas externas de funções, o gcc deixa seus endereços indefinidos para serem preenchidos posteriormente pelo estágio de ligação.

O seguinte comando irá fazer a compilação do código assembly do exemplo anterior para o código de máquina:

# as teste.s -o teste.o

O comando as é um compilador assembler disponível no pacote GNU GCC.

O resultado será um arquivo “teste.o” que contém instruções de máquina do exemplo com a referência indefinida para a função externa printf.

A linha “call printf” do código assembly informa que esta função está definida em uma biblioteca e deverá ser chamada durante o processamento.

Linker do código com as bibliotecas

O último estágio chamado de ligação, ou linker,  é responsável por ligar os arquivos objeto para criar um arquivo executável.

Ele faz isto preenchendo os endereços das funções indefinidas nos arquivos objeto com os endereços das bibliotecas externas do sistema operacional.

Isto é necessário porque os arquivos executáveis precisam de muitas funções externas do sistema e de bibliotecas do C para serem executados.

As bibliotecas podem ser ligadas ao executável preenchendo os endereços das bibliotecas nas chamadas externas ou de forma estática quando as funções das bibliotecas são copiadas para o executável.

No primeiro caso, o programa utilizará bibliotecas de forma compartilhada e ficará dependente delas para funcionar.

Este esquema economiza recursos, pois uma biblioteca utilizada por muitos programas precisa ser carregada somente uma vez na memória. O tamanho do executável será pequeno.

No segundo caso, o programa é independente, uma vez que as funções que necessita estão no seu código.

Este esquema permite que quando houver uma mudança de versão de biblioteca o programa não será afetado. A desvantagem será o tamanho do executável e necessidades de mais recursos.

Internamente a etapa de ligação é bem complexa, mas o GCC faz isto de forma transparente através do comando:

# gcc teste.o –o teste

O resultado será um executável chamado teste.

# ./teste

Certificação Linux!

Este programa foi ligado às bibliotecas de forma compartilhada. O comando ldd informa quais são as bibliotecas ligadas ao executável:

# ldd teste

linux-vdso.so.1 =>  (0x00007ffd69cff000)

libc.so.6 => /lib64/libc.so.6 (0x00007f8ee7991000)

/lib64/ld-linux-x86-64.so.2 (0x00007f8ee7d5e000)

As bibliotecas libc.so.6 e ld-linux.so.2 são referenciadas dinamicamente pelo programa teste.

A opção “-static” do compilador copia as funções externas das bibliotecas para o executável:

# gcc –static teste.c –o teste1

O comando ldd informará que o executável teste1 não tem ligações dinâmicas:

# ldd teste1

not a dynamic executable

A diferença no tamanho dos executáveis é muito grande. O programa teste que utiliza ligações dinâmicas tem 8.279 bytes.

O teste1 tem 1.884.604 bytes. Portanto, somente utilize a cópia das bibliotecas para o executável se for estritamente necessário.

Gostou desta aula? Conheça todos os outros comandos do curso de LPIC 101. E veja como passar no exames da LPIC-1

Aprenda muito mais sobre Linux em nosso curso online. Você pode fazer a matrícula aqui com trial de 7 dias grátis. Se você já tem uma conta, pode acessar aqui.

Gostou? Compartilhe 🙂

Compilando programas no Linux com o GCC
Classificado como:                

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.