Na ciência da computação , um compilador é um programa que transforma o código-fonte em código - objeto . Geralmente, o código fonte é escrito em uma linguagem de programação (a linguagem fonte ), é de alto nível de abstração e facilmente compreendido por humanos. O código-objeto é geralmente escrito em uma linguagem de nível inferior (chamada linguagem de destino ), por exemplo, uma linguagem assembly ou linguagem de máquina , a fim de criar um programa executável por uma máquina.
Um compilador executa as seguintes operações: análise lexical , pré-processamento ( pré-processamento ), análise sintática ( análise sintática ), análise semântica e geração de código otimizada . A compilação geralmente é seguida por uma etapa de edição de link , para gerar um arquivo executável. Quando o programa compilado (código-objeto) é executado em um computador cujo processador ou sistema operacional é diferente daquele do compilador, é chamado de compilação cruzada .
Existem duas opções de compilação:
Um programa fonte em linguagem C
o código de montagem correspondente
o programa após a compilação - linguagem de máquina exibida em hexadecimal
Os primeiros softwares de computador foram escritos em linguagem assembly . O nível mais alto das linguagens de programação (em camadas de abstração ) não foi inventado até que os benefícios da capacidade de reutilizar software em diferentes tipos de processadores se tornaram mais importantes do que o custo de escrever para um compilador. A capacidade de memória muito limitada dos primeiros computadores também colocava vários problemas técnicos no desenvolvimento de compiladores.
No final da década de 1950 , surgiram pela primeira vez as linguagens de programação independentes de máquina. Posteriormente, vários compiladores experimentais são desenvolvidos. O primeiro compilador, A-0 System (para a linguagem A-0) foi escrito por Grace Hopper em 1952. Acredita-se que a equipe FORTRAN liderada por John Backus da IBM tenha desenvolvido o primeiro compilador completo em 1957. COBOL , desenvolvido em 1959 e amplamente baseado nas idéias de Grace Hopper, é a primeira linguagem a ser compilada em várias arquiteturas.
Em vários campos de aplicação , A ideia de usar uma linguagem com maior nível de abstração se espalhou rapidamente. Com o aumento da funcionalidade suportada por linguagens de programação mais recentes e a crescente complexidade da arquitetura do computador, os compiladores tornaram-se cada vez mais complexos.
Em 1962, o primeiro compilador " auto-hospedado " - capaz de compilar em código-objeto, seu próprio código-fonte expresso em linguagem de alto nível - foi criado para Lisp por Tim Hart e Mike Levin no Massachusetts Institute of Technology (MIT). A partir da década de 1970 , tornou-se muito comum desenvolver um compilador na linguagem que se pretendia compilar, tornando Pascal e C as linguagens de desenvolvimento muito populares.
Também podemos usar uma linguagem ou um ambiente especializado no desenvolvimento de compiladores: falamos durante as ferramentas de meta-compilação, e usamos por exemplo um compilador compilador . Este método é particularmente útil para fazer o primeiro compilador de uma nova linguagem; o uso de uma linguagem adaptada e rigorosa então facilita o desenvolvimento e a evolução.
A principal tarefa de um compilador é produzir o código-objeto correto que será executado em um computador. A maioria dos compiladores permite otimizar o código, ou seja, buscará melhorar a velocidade de execução ou reduzir a ocupação da memória do programa.
Em geral, o idioma de origem é de "nível superior" que o idioma de destino, ou seja, apresenta um nível de abstração superior. Além disso, o código-fonte do programa geralmente é distribuído em vários arquivos.
Um compilador funciona por análise-síntese: em vez de substituir cada construção do idioma de origem por uma série equivalente de construções do idioma de destino, ele começa analisando o texto de origem para construir uma representação intermediária que por sua vez traduz para o idioma de destino. .
O compilador é separado em pelo menos duas partes: uma parte frontal (ou frontal), às vezes chamada de “esboço”, que lê o texto fonte e produz a representação intermediária; e uma parte posterior (ou final), que atravessa esta representação para produzir o texto de destino. Em um compilador ideal, a parte da frente é independente do idioma de destino, enquanto a parte de trás é independente do idioma de origem. Alguns compiladores realizam processamento substancial na parte intermediária, tornando-se uma parte central por si só, independente da linguagem de origem e da máquina de destino. Podemos, portanto, escrever compiladores para uma ampla gama de linguagens e arquiteturas, compartilhando a parte central, à qual anexamos uma parte frontal por linguagem e uma parte traseira por arquitetura.
As etapas de compilação incluem:
A análise lexical, sintática e semântica, a passagem por uma linguagem intermediária e a otimização formam a parte frontal. A geração e a vinculação do código são a parte final.
Essas diferentes etapas significam que os compiladores ainda são objeto de pesquisa.
A implementação (realização concreta) de uma linguagem de programação pode ser interpretada ou compilada. Essa realização é um compilador ou um interpretador , e uma linguagem de programação pode ter uma implementação compilada e outra interpretada.
Falamos de compilação se a tradução for feita antes da execução (o princípio de um loop é então traduzido uma vez), e de interpretação se a tradução for concluída passo a passo, durante a execução (os elementos de um loop são então examinados para cada uso) .
A interpretação é útil para depuração ou se os recursos forem limitados. A compilação é preferível em operação.
Os primeiros compiladores foram escritos diretamente em linguagem assembly , uma linguagem simbólica elementar correspondente às instruções do processador de destino e algumas estruturas de controle ligeiramente mais evoluídas. Esta linguagem simbólica deve ser montada (não compilada) e vinculada para obter uma versão executável. Por causa de sua simplicidade, um programa simples é suficiente para convertê-lo em instruções de máquina.
Os compiladores atuais são geralmente escritos na linguagem que pretendem compilar; por exemplo, um compilador C é escrito em C, SmallTalk em SmallTalk, Lisp em Lisp, etc. Na realização de um compilador, um passo decisivo é dado quando o compilador para a linguagem X está suficientemente completo para se compilar: ele então não depende mais de outra linguagem (mesmo do montador) para ser produzido.
É difícil detectar um bug do compilador. Por exemplo, se um compilador C tem um bug, os programadores C naturalmente tendem a questionar seu próprio código-fonte, não o compilador. Pior, se este compilador com erros (versão V1) compilar um compilador sem erros (versão V2), o executável compilado (por V1) do compilador V2 pode ser bugado. No entanto, seu código-fonte é bom. O bootstrap, portanto, requer que os programadores de compiladores contornem os bugs dos compiladores existentes.
A classificação dos compiladores pelo número de passagens deve-se à falta de recursos de hardware dos computadores. Compilar é um processo caro, e os primeiros computadores não tinham memória suficiente para armazenar um programa que fazia esse trabalho. Os compiladores foram então divididos em subprogramas, cada um lendo da fonte para completar as várias fases da análise lexical , de análise sintática e semântica .
A capacidade de combinar tudo em uma única passagem foi vista como uma vantagem, pois simplifica a escrita do compilador, que geralmente é executado mais rápido do que um compilador de várias passagens. Assim, devido aos recursos limitados dos primeiros sistemas, muitas linguagens foram projetadas especificamente para que pudessem ser compiladas em uma única passagem (por exemplo, a linguagem Pascal ).
Estrutura não linear do programaEm alguns casos, um recurso específico da linguagem requer que seu compilador execute mais de uma passagem. Por exemplo, considere uma declaração na linha 20 da fonte que afeta a tradução de uma declaração na linha 10 . Nesse caso, a primeira passagem deve coletar informações sobre as declarações, enquanto a tradução real ocorre apenas em uma passagem subsequente.
OtimizaçõesDividir um compilador em pequenos programas é uma técnica usada por pesquisadores interessados em produzir compiladores eficientes. Isso ocorre porque a desvantagem da compilação de passagem única é que ela não permite a execução da maioria das otimizações sofisticadas necessárias para gerar código de alta qualidade. Então, torna-se difícil contar exatamente o número de passagens que um compilador de otimização executa.
Dividindo a demonstração de correçãoDemonstrar a exatidão de uma série de pequenos programas geralmente requer menos esforço do que demonstrar a exatidão de um único programa equivalente maior.
Um compilador compilador é um programa que pode gerar qualquer uma ou todas as partes de um compilador. Por exemplo, você pode compilar os fundamentos de uma linguagem e, em seguida, usar os fundamentos da linguagem para compilar o resto.
Dependendo do uso e da máquina que executará um programa, você pode querer otimizar a velocidade de execução, ocupação de memória, consumo de energia, portabilidade para outras arquiteturas ou tempo de compilação.
Compilação cruzada refere-se a cadeias de compilação capazes de traduzir o código- fonte em código - objeto cuja arquitetura de processador difere daquela em que a compilação é realizada. Essas cadeias são usadas principalmente em TI industrial e em sistemas embarcados .
Alguns compiladores traduzem uma linguagem fonte em linguagem de máquina virtual (chamada linguagem intermediária), ou seja, em código (geralmente binário) executado por uma máquina virtual : um programa que emula as principais funcionalidades de um computador. Diz-se que essas linguagens são semi-compiladas. Portar um programa, portanto, requer apenas a portabilidade da máquina virtual, que na verdade será um intérprete ou um segundo tradutor (para compiladores de múltiplos alvos). Assim, os compiladores traduzem Pascal em P-Code, Modula 2 em M-Code, Simula em S-code ou, mais recentemente, código Java em bytecode Java (código objeto).
Um pequeno programa em Scala.
O byte-code Java resultante, executável na máquina virtual.
Quando a compilação é baseada em código de bytes, falamos de compilação em tempo real . Em seguida, usamos máquinas virtuais, como a máquina virtual Java, com a qual podemos compilar o Scala de maneira notável . É possível em algumas linguagens usar uma biblioteca que permite a compilação em tempo real do código inserido pelo usuário, por exemplo em C com libtcc.
Outros compiladores traduzem o código de uma linguagem de programação para outra. Eles são chamados de transcompiladores , ou mesmo por anglicismo, transpiladores ou transpiladores. Por exemplo, o software LaTeX permite, a partir de um código-fonte em LaTeX, obter um arquivo em formato PDF (com por exemplo o comando pdflatex no Ubuntu ) ou HTML . Outro exemplo, o LLVM é uma biblioteca que ajuda a construir compiladores, também usada pela AMD para desenvolver "HIP", um transcompilador de código CUDA (linguagem específica da NVIDIA e amplamente utilizada) para rodá-lo em processadores gráficos AMD.
O código-fonte.
O código obtido após a compilação.
Visualização do documento pdf.
Alguns compiladores traduzem, de forma incremental ou interativa, o programa-fonte (inserido pelo usuário) em código de máquina. Podemos citar como exemplo algumas implementações de Common Lisp (como SBCL (en) ).