Logo Passei Direto
Buscar

Paradigmas de Linguagens de Programação

Ferramentas de estudo

Questões resolvidas

e-Book 4 MSC Rafael De Moura Moreira PARADIGMAS DE LINGUAGENS DE PROGRAMAÇÃO Sumário INTRODUÇÃO COMPILAÇÃO INTERPRETAÇÃO PROCESSOS HÍBRIDOS CONSIDERAÇÕES FINAIS REFERÊNCIAS BIBLIOGRÁFICAS & CONSULTADAS pouco sobre o processo de interpretação, e por fim, estudaremos exemplos de linguagens híbridas, que utilizam em maior ou menor grau ambos os processos, trazendo consigo vantagens de ambos e atenuando as desvantagens. Vamos lá? Bons estudos! COMPILAÇÃO O primeiro processo que estudaremos é a compilação. Neste processo, o programa é traduzido uma única vez para a linguagem nativa da máquina, e ao final um programa executável é gerado. Utilizaremos a linguagem C como exemplo para ilustrar esse processo. Introdução Em outra oportunidade, estudamos que computadores possuem uma linguagem nativa, conhecida por linguagem de máquina. Essa linguagem é representada por números binários – sequências de dígitos 0 e 1 – representando diferentes instruções que devem ser executados por seus circuitos internos. Esses dígitos representam a passagem ou ausência de sinal elétrico, já que os circuitos digitais são formados por dispositivos que realizam o chaveamento de eletricidade. Para contornar as dificuldades óbvias da escrita de programas complexos utilizando apenas números, foram criadas as linguagens de montagem, chamadas de linguagens assembly. Elas dão um nome curto para cada uma das instruções, conhecido como mnemônico. Isso facilita bastante a programação, mas ainda estamos bastante próximos da máquina, lidando quase diretamente com seu hardware. Quando utilizamos uma linguagem de nível mais alto, mais próxima da forma humana de raciocinar e de se expressar, os códigos precisam ser traduzidos para essa linguagem de máquina. Quando esse processo ocorre uma única vez, gerando ao seu final um programa em linguagem de máquina, ele é chamado de compilação. O processo de montagem do programa final possui várias etapas. Sem entrar em detalhes específicos de análise sintática e semântica, podemos visualizar algumas etapas mais gerais, como o pré-processamento, a compilação em si e o linking. Pré-processamento O pré-processamento é uma etapa de preparação antes do acionamento do compilador em si. Ele “prepara” o código – que, a princípio, provavelmente foi escrito de maneira a ser legível por outros seres humanos – para ser mais facilmente compreendido pelo compilador. Vamos tomar como exemplo a linguagem C. É muito comum que um programa escrito em C tenha logo em seu início uma ou mais linhas iniciadas com a expressão “#include”. Essa expressão indica que gostaríamos de importar códigos presentes em outros arquivos. O pré-processador fará o papel de buscar esses arquivos e copiar seu conteúdo para o programa que será compilado. De fato, todas as linhas iniciadas pelo caractere “#” em C são diretivas de compilação. Isso significa que elas não são instruções do programa, e sim instruções para o pré-processador que irão afetar a compilação do programa. Por exemplo, temos diretivas de compilação condicional. Essas irão analisar certas condições (como a declaração ou não de certas definições) para determinar se um bloco de código será compilado ou ignorado. Isso pode ser útil quando estamos escrevendo um código que deverá ser compilado para vários sistemas diferentes, com muitas partes em comum, porém também algumas partes específicas para cada sistema. A existência de certas definições pode ser usada para determinar para qual sistema estamos compilando, e o pré-processador descartará o código que não se enquadra. É comum também a criação de macros. Essas macros podem ser apenas valores ou podem ser uma espécie de “função de uma linha”. No primeiro caso, após a diretiva “#define”, determina-se um nome, e em seguida, opcionalmente, um valor. Esse nome representará um “valor constante”. No pré-processamento, todas as ocorrências desse nome serão substituídas pelo valor. No caso das macros estilo função, é possível especificar parâmetros e um pequeno trecho de código. Porém, ao contrário de funções convencionais, essas macros não virarão funções em linguagem de máquina. Ao invés disso, todos os trechos onde elas foram chamadas também serão substituídas durante o pré-processamento pelo código definido. Outro ponto que também é tratado pelo pré-processamento são os comentários do código. Na realidade, comentários não são ignorados pelo compilador. O que realmente ocorre é que o pré-processador identifica os comentários e os substitui por espaço em branco. Essas são algumas das várias tarefas realizadas pelo pré-processador para facilitar a vida do compilador, mas há outras inclusive mais complexas. Uma vez finalizado o pré-processamento, a etapa de compilação pode iniciar. Compilação A etapa de compilação é, possivelmente, a etapa mais complexa. Enquanto em assembly cada linha de código corresponde diretamente a uma instrução de máquina, o mesmo não pode ser dito sobre linguagens de alto nível. Uma linha de código em linguagem de alto nível geralmente corresponde a várias instruções de máquina. O compilador realiza diversas análises sobre o código. Para termos uma noção da complexidade, ele precisa não apenas identificar os comandos sendo utilizados, mas também variáveis criadas pelo programador, funções etc. Também é comum que uma linha de código afete o trabalho de uma linha anterior a ela, o que faz com que normalmente um compilador analise o código mais de uma vez, já que na segunda ele já sabe o que vem a frente. Ao fazermos uma tradução de uma linguagem humana para outra, por exemplo do inglês para o português, a simplicidade de um texto em uma não implica, necessariamente, que o texto traduzido será simples. O tradutor pode usar sinônimos mais simples ou mais complexos, bem como organizar as frases de maneiras mais convencionais ou não, preservando o mesmo significado. O mesmo ocorre com o compilador. Há várias maneiras de se implementar em linguagem de máquina um programa escrito em linguagem de alto nível. Um programa bem escrito em alto nível pode ter mau desempenho se o compilador adotar estruturas e construções pouco eficientes em assembly. Por conta disso, vários compiladores realizam diversas otimizações. Por exemplo, uma função muito curta pode não se tornar uma sub-rotina em assembly, e sim ter seu conteúdo “copiado” para todos os pontos onde foi chamada, economizando o tempo e gasto de memória típicos de uma chamada de função – uma técnica conhecida como inlining. Malhas de repetição podem ter algumas de suas instruções multiplicadas, e o número total de repetições da malha em si reduzido, uma técnica conhecida como loop unrolling, que também tem por objetivo economizar recursos gastos na verificação da condição de parada da malha. Por fim, além das otimizações “padrão”, compiladores podem realizar algumas otimizações disponíveis especificamente em um ou outro tipo de hardware. Alguns compiladores mais conhecidos, como o gcc (GNU Compiler Collection), permitem que o programador opte por fazer ou não várias dessas otimizações individualmente, dependendo do resultado desejado ou de restrições do projeto. Ao final de todo o trabalho de compilação, teremos um conjunto de instruções em linguagem de máquina (binário), ou em assembly, que logo em seguida será convertido para binário por um assembler. Chamamos esse binário de código objeto. Linking Para que o código objeto esteja pronto para ser utilizado e se torne útil, ele será ligado ou unido a outros códigos objeto (como bibliotecas) para finalmente se tornar um programa executável ou outra biblioteca. O termo “biblioteca” é um termo um pouco “guarda-chuva” que utilizamos em alguns contextos diferentes. Um deles é quando temos códigos escritos na mesma linguagem (ou em linguagens compatíveis) que podem ser importados para utilizarmos na escrita de nosso código. Outro significado de biblioteca, mais interessante no contexto que estamos estudando, é de um código já compilado, bastante similar a um programa, mas com uma diferença: ela não possui um ponto de entrada, ou uma função principal. Esse código é uma coleção de recursos prontos, como funções, exatamente como as bibliotecas que incluímos em nossos códigos, mas com a diferença de que já estão compilados. Há duas maneiras que nosso programa pode ser ligado a essas bibliotecas: estaticamente ou din

Em várias plataformas modernas, o programa em execução terá alguns elementos em comum. Sua memória total conterá uma área de código e uma área de dados, e esta será subdividida em duas regiões: o stack (pilha) e o heap (monte). A área de código é onde suas instruções de máquina serão armazenadas. Ao abrirmos um programa, suas instruções são copiadas para a memória e executadas. Porém, o programa também manipula dados frequentemente e para isso possui uma área designada para armazená-los. O stack é onde ocorre a alocação estática de memória: variáveis convencionais criadas dentro de funções são armazenadas nessa região. Quando funções são chamadas, seus parâmetros são copiados para essa região também. O nome dessa estrutura indica seu comportamento: ao empilharmos objetos sobre uma mesa, o último objeto a ser colocado na pilha é o primeiro a ser removido, e o primeiro objeto será o último a ser removido. A pilha sempre irá crescer em um único sentido e diminuir no sentido oposto, tornando essa região bastante limitada. Quando temos várias chamadas recursivas sem controle, por exemplo, é comum nos depararmos com o erro de estouro de pilha: usamos mais memória dessa região do que poderíamos. A outra região, o heap, é mais flexível, porque pode ser utilizada para a alocação dinâmica de memória. Quando não sabemos previamente quanta memória necessitaremos e vamos pedir durante a execução do programa por mais memória para o sistema, essa memória extra pertence ao heap. Quando estamos perto de lotar o heap alocado para nosso programa, o sistema operacional pode aumentar seu tamanho, disponibilizando mais memória para o programa. Porém, ao contrário da pilha, o monte não é esvaziado sozinho. Memória alocada no heap em linguagens como C fica sob responsabilidade do programador. Se o programa não informar ao sistema que acabou de a usar, ela ficará marcada como ocupada, gerando um problema conhecido como vazamento de memória. Algumas linguagens (geralmente orientadas a objeto) oferecem um recurso chamado de coletor de lixo, que identifica automaticamente dados na heap que não serão mais utilizados e os liberam automaticamente.

Erros e tratamento de exceção. Considere, por exemplo, os erros de sintaxe. Um erro de sintaxe é aquele na qual cometemos um erro no uso da linguagem, como digitar errado o nome de um comando da linguagem ou utilizar incorretamente algum operador ou símbolo (como abrir um parêntese e não lembrar de fechá-lo). Esse tipo de erro é capturado pelo próprio compilador, pois, ao passar pelo programa, ele irá notar um comando não reconhecido ou a falta de algum símbolo que era esperado. O processo de compilação será interrompido e uma mensagem de erro será exibida.

Polimorfismo. Na programação orientada a objeto há o conceito de polimorfismo: um objeto de uma certa classe pode ser tratado como pertencente a outra classe ou a algum outro tipo de categoria desde que sejam respeitadas certas condições.

Quais são as vantagens da utilização de máquinas virtuais e bytecode na execução de programas?

Material
páginas com resultados encontrados.
páginas com resultados encontrados.

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Escolha uma das opções e acesse esse e outros materiais sem bloqueio. 🤩

Cadastre-se ou realize login

Ao continuar, você aceita os Termos de Uso e Política de Privacidade

Questões resolvidas

e-Book 4 MSC Rafael De Moura Moreira PARADIGMAS DE LINGUAGENS DE PROGRAMAÇÃO Sumário INTRODUÇÃO COMPILAÇÃO INTERPRETAÇÃO PROCESSOS HÍBRIDOS CONSIDERAÇÕES FINAIS REFERÊNCIAS BIBLIOGRÁFICAS & CONSULTADAS pouco sobre o processo de interpretação, e por fim, estudaremos exemplos de linguagens híbridas, que utilizam em maior ou menor grau ambos os processos, trazendo consigo vantagens de ambos e atenuando as desvantagens. Vamos lá? Bons estudos! COMPILAÇÃO O primeiro processo que estudaremos é a compilação. Neste processo, o programa é traduzido uma única vez para a linguagem nativa da máquina, e ao final um programa executável é gerado. Utilizaremos a linguagem C como exemplo para ilustrar esse processo. Introdução Em outra oportunidade, estudamos que computadores possuem uma linguagem nativa, conhecida por linguagem de máquina. Essa linguagem é representada por números binários – sequências de dígitos 0 e 1 – representando diferentes instruções que devem ser executados por seus circuitos internos. Esses dígitos representam a passagem ou ausência de sinal elétrico, já que os circuitos digitais são formados por dispositivos que realizam o chaveamento de eletricidade. Para contornar as dificuldades óbvias da escrita de programas complexos utilizando apenas números, foram criadas as linguagens de montagem, chamadas de linguagens assembly. Elas dão um nome curto para cada uma das instruções, conhecido como mnemônico. Isso facilita bastante a programação, mas ainda estamos bastante próximos da máquina, lidando quase diretamente com seu hardware. Quando utilizamos uma linguagem de nível mais alto, mais próxima da forma humana de raciocinar e de se expressar, os códigos precisam ser traduzidos para essa linguagem de máquina. Quando esse processo ocorre uma única vez, gerando ao seu final um programa em linguagem de máquina, ele é chamado de compilação. O processo de montagem do programa final possui várias etapas. Sem entrar em detalhes específicos de análise sintática e semântica, podemos visualizar algumas etapas mais gerais, como o pré-processamento, a compilação em si e o linking. Pré-processamento O pré-processamento é uma etapa de preparação antes do acionamento do compilador em si. Ele “prepara” o código – que, a princípio, provavelmente foi escrito de maneira a ser legível por outros seres humanos – para ser mais facilmente compreendido pelo compilador. Vamos tomar como exemplo a linguagem C. É muito comum que um programa escrito em C tenha logo em seu início uma ou mais linhas iniciadas com a expressão “#include”. Essa expressão indica que gostaríamos de importar códigos presentes em outros arquivos. O pré-processador fará o papel de buscar esses arquivos e copiar seu conteúdo para o programa que será compilado. De fato, todas as linhas iniciadas pelo caractere “#” em C são diretivas de compilação. Isso significa que elas não são instruções do programa, e sim instruções para o pré-processador que irão afetar a compilação do programa. Por exemplo, temos diretivas de compilação condicional. Essas irão analisar certas condições (como a declaração ou não de certas definições) para determinar se um bloco de código será compilado ou ignorado. Isso pode ser útil quando estamos escrevendo um código que deverá ser compilado para vários sistemas diferentes, com muitas partes em comum, porém também algumas partes específicas para cada sistema. A existência de certas definições pode ser usada para determinar para qual sistema estamos compilando, e o pré-processador descartará o código que não se enquadra. É comum também a criação de macros. Essas macros podem ser apenas valores ou podem ser uma espécie de “função de uma linha”. No primeiro caso, após a diretiva “#define”, determina-se um nome, e em seguida, opcionalmente, um valor. Esse nome representará um “valor constante”. No pré-processamento, todas as ocorrências desse nome serão substituídas pelo valor. No caso das macros estilo função, é possível especificar parâmetros e um pequeno trecho de código. Porém, ao contrário de funções convencionais, essas macros não virarão funções em linguagem de máquina. Ao invés disso, todos os trechos onde elas foram chamadas também serão substituídas durante o pré-processamento pelo código definido. Outro ponto que também é tratado pelo pré-processamento são os comentários do código. Na realidade, comentários não são ignorados pelo compilador. O que realmente ocorre é que o pré-processador identifica os comentários e os substitui por espaço em branco. Essas são algumas das várias tarefas realizadas pelo pré-processador para facilitar a vida do compilador, mas há outras inclusive mais complexas. Uma vez finalizado o pré-processamento, a etapa de compilação pode iniciar. Compilação A etapa de compilação é, possivelmente, a etapa mais complexa. Enquanto em assembly cada linha de código corresponde diretamente a uma instrução de máquina, o mesmo não pode ser dito sobre linguagens de alto nível. Uma linha de código em linguagem de alto nível geralmente corresponde a várias instruções de máquina. O compilador realiza diversas análises sobre o código. Para termos uma noção da complexidade, ele precisa não apenas identificar os comandos sendo utilizados, mas também variáveis criadas pelo programador, funções etc. Também é comum que uma linha de código afete o trabalho de uma linha anterior a ela, o que faz com que normalmente um compilador analise o código mais de uma vez, já que na segunda ele já sabe o que vem a frente. Ao fazermos uma tradução de uma linguagem humana para outra, por exemplo do inglês para o português, a simplicidade de um texto em uma não implica, necessariamente, que o texto traduzido será simples. O tradutor pode usar sinônimos mais simples ou mais complexos, bem como organizar as frases de maneiras mais convencionais ou não, preservando o mesmo significado. O mesmo ocorre com o compilador. Há várias maneiras de se implementar em linguagem de máquina um programa escrito em linguagem de alto nível. Um programa bem escrito em alto nível pode ter mau desempenho se o compilador adotar estruturas e construções pouco eficientes em assembly. Por conta disso, vários compiladores realizam diversas otimizações. Por exemplo, uma função muito curta pode não se tornar uma sub-rotina em assembly, e sim ter seu conteúdo “copiado” para todos os pontos onde foi chamada, economizando o tempo e gasto de memória típicos de uma chamada de função – uma técnica conhecida como inlining. Malhas de repetição podem ter algumas de suas instruções multiplicadas, e o número total de repetições da malha em si reduzido, uma técnica conhecida como loop unrolling, que também tem por objetivo economizar recursos gastos na verificação da condição de parada da malha. Por fim, além das otimizações “padrão”, compiladores podem realizar algumas otimizações disponíveis especificamente em um ou outro tipo de hardware. Alguns compiladores mais conhecidos, como o gcc (GNU Compiler Collection), permitem que o programador opte por fazer ou não várias dessas otimizações individualmente, dependendo do resultado desejado ou de restrições do projeto. Ao final de todo o trabalho de compilação, teremos um conjunto de instruções em linguagem de máquina (binário), ou em assembly, que logo em seguida será convertido para binário por um assembler. Chamamos esse binário de código objeto. Linking Para que o código objeto esteja pronto para ser utilizado e se torne útil, ele será ligado ou unido a outros códigos objeto (como bibliotecas) para finalmente se tornar um programa executável ou outra biblioteca. O termo “biblioteca” é um termo um pouco “guarda-chuva” que utilizamos em alguns contextos diferentes. Um deles é quando temos códigos escritos na mesma linguagem (ou em linguagens compatíveis) que podem ser importados para utilizarmos na escrita de nosso código. Outro significado de biblioteca, mais interessante no contexto que estamos estudando, é de um código já compilado, bastante similar a um programa, mas com uma diferença: ela não possui um ponto de entrada, ou uma função principal. Esse código é uma coleção de recursos prontos, como funções, exatamente como as bibliotecas que incluímos em nossos códigos, mas com a diferença de que já estão compilados. Há duas maneiras que nosso programa pode ser ligado a essas bibliotecas: estaticamente ou din

Em várias plataformas modernas, o programa em execução terá alguns elementos em comum. Sua memória total conterá uma área de código e uma área de dados, e esta será subdividida em duas regiões: o stack (pilha) e o heap (monte). A área de código é onde suas instruções de máquina serão armazenadas. Ao abrirmos um programa, suas instruções são copiadas para a memória e executadas. Porém, o programa também manipula dados frequentemente e para isso possui uma área designada para armazená-los. O stack é onde ocorre a alocação estática de memória: variáveis convencionais criadas dentro de funções são armazenadas nessa região. Quando funções são chamadas, seus parâmetros são copiados para essa região também. O nome dessa estrutura indica seu comportamento: ao empilharmos objetos sobre uma mesa, o último objeto a ser colocado na pilha é o primeiro a ser removido, e o primeiro objeto será o último a ser removido. A pilha sempre irá crescer em um único sentido e diminuir no sentido oposto, tornando essa região bastante limitada. Quando temos várias chamadas recursivas sem controle, por exemplo, é comum nos depararmos com o erro de estouro de pilha: usamos mais memória dessa região do que poderíamos. A outra região, o heap, é mais flexível, porque pode ser utilizada para a alocação dinâmica de memória. Quando não sabemos previamente quanta memória necessitaremos e vamos pedir durante a execução do programa por mais memória para o sistema, essa memória extra pertence ao heap. Quando estamos perto de lotar o heap alocado para nosso programa, o sistema operacional pode aumentar seu tamanho, disponibilizando mais memória para o programa. Porém, ao contrário da pilha, o monte não é esvaziado sozinho. Memória alocada no heap em linguagens como C fica sob responsabilidade do programador. Se o programa não informar ao sistema que acabou de a usar, ela ficará marcada como ocupada, gerando um problema conhecido como vazamento de memória. Algumas linguagens (geralmente orientadas a objeto) oferecem um recurso chamado de coletor de lixo, que identifica automaticamente dados na heap que não serão mais utilizados e os liberam automaticamente.

Erros e tratamento de exceção. Considere, por exemplo, os erros de sintaxe. Um erro de sintaxe é aquele na qual cometemos um erro no uso da linguagem, como digitar errado o nome de um comando da linguagem ou utilizar incorretamente algum operador ou símbolo (como abrir um parêntese e não lembrar de fechá-lo). Esse tipo de erro é capturado pelo próprio compilador, pois, ao passar pelo programa, ele irá notar um comando não reconhecido ou a falta de algum símbolo que era esperado. O processo de compilação será interrompido e uma mensagem de erro será exibida.

Polimorfismo. Na programação orientada a objeto há o conceito de polimorfismo: um objeto de uma certa classe pode ser tratado como pertencente a outra classe ou a algum outro tipo de categoria desde que sejam respeitadas certas condições.

Quais são as vantagens da utilização de máquinas virtuais e bytecode na execução de programas?

Prévia do material em texto

e-Book 4
MSC Rafael De Moura Moreira
PARADIGMAS DE 
LINGUAGENS DE 
PROGRAMAÇÃO
Sumário
INTRODUÇÃO ������������������������������������������������� 3
COMPILAÇÃO ������������������������������������������������ 4
INTERPRETAÇÃO �����������������������������������������22
PROCESSOS HÍBRIDOS ������������������������������25
CONSIDERAÇÕES FINAIS ����������������������������34
REFERÊNCIAS BIBLIOGRÁFICAS & 
CONSULTADAS ��������������������������������������������37
3
INTRODUÇÃO
Olá estudante! Estudaremos os processos através 
dos quais o computador compreende os programas 
que escrevemos nessas linguagens.
Começaremos estudando o processo de compilação 
e seus processos relacionados, como o pré-pro-
cessamento e o linking, e conceitos relacionados, 
como código objeto e bibliotecas.
Em seguida estudaremos um pouco sobre o pro-
cesso de interpretação, e por fim, estudaremos 
exemplos de linguagens híbridas, que utilizam em 
maior ou menor grau ambos os processos, trazen-
do consigo vantagens de ambos e atenuando as 
desvantagens.
Vamos lá? Bons estudos!
44
COMPILAÇÃO 
O primeiro processo que estudaremos é a com-
pilação. Neste processo, o programa é traduzido 
uma única vez para a linguagem nativa da máqui-
na, e ao final um programa executável é gerado. 
Utilizaremos a linguagem C como exemplo para 
ilustrar esse processo.
Introdução
Em outra oportunidade, estudamos que computa-
dores possuem uma linguagem nativa, conhecida 
por linguagem de máquina. Essa linguagem é 
representada por números binários – sequências 
de dígitos 0 e 1 – representando diferentes instru-
ções que devem ser executados por seus circuitos 
internos. Esses dígitos representam a passagem 
ou ausência de sinal elétrico, já que os circuitos 
digitais são formados por dispositivos que realizam 
o chaveamento de eletricidade.
Para contornar as dificuldades óbvias da escrita de 
programas complexos utilizando apenas números, 
foram criadas as linguagens de montagem, chamadas 
de linguagens assembly. Elas dão um nome curto 
para cada uma das instruções, conhecido como 
mnemônico. Isso facilita bastante a programação, 
mas ainda estamos bastante próximos da máqui-
na, lidando quase diretamente com seu hardware.
55
Quando utilizamos uma linguagem de nível mais 
alto, mais próxima da forma humana de raciocinar 
e de se expressar, os códigos precisam ser tradu-
zidos para essa linguagem de máquina. Quando 
esse processo ocorre uma única vez, gerando ao 
seu final um programa em linguagem de máquina, 
ele é chamado de compilação.
O processo de montagem do programa final possui 
várias etapas. Sem entrar em detalhes específicos 
de análise sintática e semântica, podemos visualizar 
algumas etapas mais gerais, como o pré-proces-
samento, a compilação em si e o linking.
Pré-processamento
O pré-processamento é uma etapa de preparação 
antes do acionamento do compilador em si. Ele 
“prepara” o código – que, a princípio, provavelmente 
foi escrito de maneira a ser legível por outros seres 
humanos – para ser mais facilmente compreendido 
pelo compilador.
Vamos tomar como exemplo a linguagem C. É muito 
comum que um programa escrito em C tenha logo 
em seu início uma ou mais linhas iniciadas com a 
expressão “#include”. Essa expressão indica que 
gostaríamos de importar códigos presentes em 
outros arquivos. O pré-processador fará o papel 
de buscar esses arquivos e copiar seu conteúdo 
para o programa que será compilado.
66
De fato, todas as linhas iniciadas pelo caractere “#” 
em C são diretivas de compilação. Isso significa 
que elas não são instruções do programa, e sim 
instruções para o pré-processador que irão afetar 
a compilação do programa.
Por exemplo, temos diretivas de compilação condi-
cional. Essas irão analisar certas condições (como 
a declaração ou não de certas definições) para 
determinar se um bloco de código será compilado 
ou ignorado. Isso pode ser útil quando estamos 
escrevendo um código que deverá ser compilado 
para vários sistemas diferentes, com muitas partes 
em comum, porém também algumas partes espe-
cíficas para cada sistema. A existência de certas 
definições pode ser usada para determinar para 
qual sistema estamos compilando, e o pré-proces-
sador descartará o código que não se enquadra.
É comum também a criação de macros. Essas 
macros podem ser apenas valores ou podem ser 
uma espécie de “função de uma linha”. No primeiro 
caso, após a diretiva “#define”, determina-se um 
nome, e em seguida, opcionalmente, um valor. 
Esse nome representará um “valor constante”. No 
pré-processamento, todas as ocorrências desse 
nome serão substituídas pelo valor. No caso das 
macros estilo função, é possível especificar parâ-
metros e um pequeno trecho de código. Porém, ao 
contrário de funções convencionais, essas macros 
77
não virarão funções em linguagem de máquina. 
Ao invés disso, todos os trechos onde elas foram 
chamadas também serão substituídas durante o 
pré-processamento pelo código definido.
Outro ponto que também é tratado pelo pré-pro-
cessamento são os comentários do código. Na 
realidade, comentários não são ignorados pelo 
compilador. O que realmente ocorre é que o pré-pro-
cessador identifica os comentários e os substitui 
por espaço em branco.
Essas são algumas das várias tarefas realizadas 
pelo pré-processador para facilitar a vida do com-
pilador, mas há outras inclusive mais complexas. 
Uma vez finalizado o pré-processamento, a etapa 
de compilação pode iniciar.
Compilação
A etapa de compilação é, possivelmente, a etapa 
mais complexa. Enquanto em assembly cada linha 
de código corresponde diretamente a uma instru-
ção de máquina, o mesmo não pode ser dito sobre 
linguagens de alto nível. Uma linha de código em 
linguagem de alto nível geralmente corresponde 
a várias instruções de máquina.
O compilador realiza diversas análises sobre o 
código. Para termos uma noção da complexidade, 
ele precisa não apenas identificar os comandos 
88
sendo utilizados, mas também variáveis criadas 
pelo programador, funções etc. Também é comum 
que uma linha de código afete o trabalho de uma 
linha anterior a ela, o que faz com que normalmente 
um compilador analise o código mais de uma vez, 
já que na segunda ele já sabe o que vem a frente.
Ao fazermos uma tradução de uma linguagem 
humana para outra, por exemplo do inglês para o 
português, a simplicidade de um texto em uma não 
implica, necessariamente, que o texto traduzido 
será simples. O tradutor pode usar sinônimos mais 
simples ou mais complexos, bem como organizar 
as frases de maneiras mais convencionais ou não, 
preservando o mesmo significado.
O mesmo ocorre com o compilador. Há várias 
maneiras de se implementar em linguagem de 
máquina um programa escrito em linguagem de 
alto nível. Um programa bem escrito em alto nível 
pode ter mau desempenho se o compilador ado-
tar estruturas e construções pouco eficientes em 
assembly. Por conta disso, vários compiladores 
realizam diversas otimizações.
Por exemplo, uma função muito curta pode não 
se tornar uma sub-rotina em assembly, e sim ter 
seu conteúdo “copiado” para todos os pontos onde 
foi chamada, economizando o tempo e gasto de 
memória típicos de uma chamada de função – 
99
uma técnica conhecida como inlining. Malhas de 
repetição podem ter algumas de suas instruções 
multiplicadas, e o número total de repetições da 
malha em si reduzido, uma técnica conhecida 
como loop unrolling, que também tem por objeti-
vo economizar recursos gastos na verificação da 
condição de parada da malha.
Por fim, além das otimizações “padrão”, compila-
dores podem realizar algumas otimizações dis-
poníveis especificamente em um ou outro tipo de 
hardware. Alguns compiladores mais conhecidos, 
como o gcc (GNU Compiler Collection), permitem 
que o programador opte por fazer ou não várias 
dessas otimizações individualmente, dependendo 
do resultado desejado ou de restrições do projeto.
Ao final de todo o trabalho de compilação, tere-
mos um conjunto de instruções emlinguagem de 
máquina (binário), ou em assembly, que logo em 
seguida será convertido para binário por um as-
sembler. Chamamos esse binário de código objeto.
Linking
Para que o código objeto esteja pronto para ser 
utilizado e se torne útil, ele será ligado ou unido 
a outros códigos objeto (como bibliotecas) para 
finalmente se tornar um programa executável ou 
outra biblioteca.
1010
O termo “biblioteca” é um termo um pouco “guar-
da-chuva” que utilizamos em alguns contextos 
diferentes. Um deles é quando temos códigos 
escritos na mesma linguagem (ou em linguagens 
compatíveis) que podem ser importados para 
utilizarmos na escrita de nosso código. 
Outro significado de biblioteca, mais interessante no 
contexto que estamos estudando, é de um código 
já compilado, bastante similar a um programa, mas 
com uma diferença: ela não possui um ponto de 
entrada, ou uma função principal. Esse código é 
uma coleção de recursos prontos, como funções, 
exatamente como as bibliotecas que incluímos 
em nossos códigos, mas com a diferença de que 
já estão compilados.
Há duas maneiras que nosso programa pode 
ser ligado a essas bibliotecas: estaticamente ou 
dinamicamente. No caso da ligação dinâmica, a 
biblioteca será para sempre um arquivo apartado 
do nosso programa executável (como os arqui-
vos com extensão .DLL no Windows), e na hora 
do programa ser executado o sistema irá buscar 
a biblioteca e disponibilizar seu conteúdo para o 
programa.
Já no caso da ligação estática, o código objeto da 
biblioteca é incorporado ao do que acabamos de 
1111
gerar pelo linker, resultando em outra biblioteca 
ou em um programa executável.
Figura 1: O funcionamento do linker.
Fonte: QEF. Input and output file types of the linking process. 
2009. Disponível em https://en.wikipedia.org/wiki/Interpreter_
(computing)#/media/File:Linker.svg. Acesso em: 24 set. 2021.
O programa em execução
Em várias plataformas modernas, o programa em 
execução terá alguns elementos em comum. Sua 
memória total conterá uma área de código e uma 
área de dados, e esta será subdividida em duas 
regiões: o stack (pilha) e o heap (monte). 
A área de código é onde suas instruções de máquina 
serão armazenadas. Ao abrirmos um programa, 
suas instruções são copiadas para a memória e 
executadas. Porém, o programa também manipula 
1212
dados frequentemente e para isso possui uma área 
designada para armazená-los.
O stack é onde ocorre a alocação estática de 
memória: variáveis convencionais criadas den-
tro de funções são armazenadas nessa região. 
Quando funções são chamadas, seus parâmetros 
são copiados para essa região também. O nome 
dessa estrutura indica seu comportamento: ao 
empilharmos objetos sobre uma mesa, o último 
objeto a ser colocado na pilha é o primeiro a ser 
removido, e o primeiro objeto será o último a ser 
removido. A pilha sempre irá crescer em um único 
sentido e diminuir no sentido oposto, tornando essa 
região bastante limitada. Quando temos várias 
chamadas recursivas sem controle, por exemplo, 
é comum nos depararmos com o erro de estouro 
de pilha: usamos mais memória dessa região do 
que poderíamos.
A outra região, o heap, é mais flexível, porque pode 
ser utilizada para a alocação dinâmica de memória. 
Quando não sabemos previamente quanta memória 
necessitaremos e vamos pedir durante a execução 
do programa por mais memória para o sistema, 
essa memória extra pertence ao heap. Quando 
estamos perto de lotar o heap alocado para nosso 
programa, o sistema operacional pode aumentar 
seu tamanho, disponibilizando mais memória para 
o programa. Porém, ao contrário da pilha, o monte 
1313
não é esvaziado sozinho. Memória alocada no heap 
em linguagens como C fica sob responsabilidade 
do programador. Se o programa não informar ao 
sistema que acabou de a usar, ela ficará marcada 
como ocupada, gerando um problema conhecido 
como vazamento de memória.
Algumas linguagens (geralmente orientadas a 
objeto) oferecem um recurso chamado de coletor 
de lixo, que identifica automaticamente dados na 
heap que não serão mais utilizados e os liberam 
automaticamente. 
Tempo de compilação versus tempo de 
execução
É importante compreender quais ações são re-
alizadas durante o processo de compilação do 
programa – ou seja, uma única vez enquanto ele 
está sendo gerado – e quais ocorrem quando ele 
está sendo executado. Chamamos esses diferentes 
momentos da vida de um programa de “tempo de 
compilação” e “tempo de execução”.
Erros e tratamento de exceção
Considere, por exemplo, os erros de sintaxe. Um 
erro de sintaxe é aquele na qual cometemos um 
erro no uso da linguagem, como digitar errado o 
nome de um comando da linguagem ou utilizar 
incorretamente algum operador ou símbolo (como 
1414
abrir um parêntese e não lembrar de fechá-lo). Esse 
tipo de erro é capturado pelo próprio compilador, 
pois, ao passar pelo programa, ele irá notar um 
comando não reconhecido ou a falta de algum 
símbolo que era esperado. O processo de compi-
lação será interrompido e uma mensagem de erro 
será exibida.
Agora vamos supor que o seu programa irá ler 
diversos valores em uma planilha fornecida pelo 
usuário e realizar algumas operações matemáticas 
sobre esses valores. Porém, por alguma falha ou da 
modelagem feita pelo programador ou de digitação 
da planilha pelo usuário há uma combinação de 
valores que fará aparecer o número zero em um 
denominador. Isso também é um erro, mas note 
que ele ocorreu com o programa já executando, 
e não na hora de compilar, portanto, meramente 
mandar dividir variáveis não provoca erros.
Algumas linguagens fornecem meios para que o 
programador preveja antes da compilação erros 
que podem ocorrer na execução. Essa técnica é 
chamada de tratamento de exceções, e consiste 
em proteger uma região do código com algum 
comando indicando que é esperado que aquela 
região possa provocar erro, e em seguida outros 
blocos de códigos irão identificar qual o tipo de erro 
que ocorreu e executar outras ações corretivas, ao 
invés de simplesmente deixar o programa falhar, 
1515
travar ou ser fechado pelo sistema operacional. 
Em algumas linguagens, existe também o conceito 
de exceções em tempo de compilação, permitindo 
que um possível erro de compilação leve a uma 
ação alternativa ao invés da falha na compilação.
Polimorfismo
Na programação orientada a objeto há o conceito 
de polimorfismo: um objeto de uma certa classe 
pode ser tratado como pertencente a outra classe 
ou a algum outro tipo de categoria desde que sejam 
respeitadas certas condições.
Um exemplo bastante claro de polimorfismo ocorre 
na herança: quando uma classe é herdeira de outra 
classe, ela possui, por tabela, os métodos e atributos 
da classe mãe. É possível que os métodos sejam 
sobrescritos na classe filha, se comportando de 
maneira completamente distinta, mas eles existem 
com o mesmo nome. Sendo assim, código escrito 
para lidar com um objeto de uma certa classe ge-
ralmente também consegue lidar com objetos de 
suas classes filhas, sem fazer distinção da classe.
Outro exemplo é o que ocorre com as interfaces 
em Java. Uma interface não é uma classe, mas 
estabelece uma relação que lembra um pouco 
a herança. Uma interface define um conjunto de 
métodos. Ela não define a lógica interna dos mé-
todos, ou seja, sua implementação. Mas define 
1616
o que chamamos de assinatura de um método: 
seu nome, seu tipo de retorno e seus parâmetros. 
Quando uma classe implementa uma interface, ela 
assume o compromisso de possuir métodos que 
respeitem as assinaturas previstas. Portanto, um 
trecho de código feito para trabalhar com aquela 
interface aceitará objetos de qualquer classe que 
implemente aquela interface, também sem fazer 
distinção entre as classes.
Imagine um sistema de um marketplace virtual, 
onde diferentes pessoas podem se cadastrar e 
fazer login: compradores, vendedores e administra-
dores do sistema, cada um com diferentes níveis 
de segurança e passos de autenticação para logar. 
As trêsentidades podem ser modeladas com di-
ferentes classes: Vendedor, Comprador e Admin. 
Todas essas classes podem ser herdeiras de 
uma superclasse Usuario, que prevê um método 
de login. Cada uma das subclasses possui sua 
própria versão do método de login tratando suas 
peculiaridades.
O código para lidar com a autenticação pode ser 
uma função que irá receber um objeto Usuario 
e chama seu método de login. No momento da 
compilação, portanto, não está determinado qual 
função está sendo chamada ali. Durante a exe-
cução, dependendo de quem tentar se logar, um 
1717
objeto diferente será passado para aquela função, 
e, dependendo da classe, um método login diferente 
será executado. Chamamos esse tipo de caso de 
polimorfismo em tempo de execução, pois nem 
sequer o compilador sabia previamente qual ou 
quais métodos seriam chamados por aquela função.
Em algumas linguagens, existe uma outra forma 
de polimorfismo chamado de polimorfismo em 
tempo de compilação. Vamos mais uma vez 
tomar o Java como exemplo. É possível que em 
uma mesma classe contenha diversos métodos 
com o mesmo nome, mas diferentes quantidades 
de parâmetros.
Se em uma certa região do código o nome desse 
método aparecer, o compilador consegue deter-
minar previamente qual das variações será exe-
cutada, pois basta analisar os parâmetros sendo 
passados. Sendo assim, já no ato da compilação 
ocorre a ligação entre o método e a sua chamada.
Considerações sobre segurança
A diferença entre certas verificações ocorrerem 
em tempo de compilação ou de execução pode 
impactar dramaticamente o grau de segurança e 
confiabilidade do programa gerado.
Vamos considerar um erro comum: o estouro 
de um vetor. Vetores são estruturas de dados 
1818
contínuas na memória, geralmente armazenando 
diversos valores de um mesmo tipo. Eles podem 
ser acessados através de um índice, um número 
indicando qual das “casinhas” dentro da faixa 
contínua gostaríamos de acessar.
Na linguagem C o compilador não verifica a possi-
bilidade de estarmos acessando um índice inválido, 
ou seja, um índice que não pertence à faixa reser-
vada para aquele vetor. Isso abre caminho para 
erros em tempo de execução: o programa sendo 
executado poderá invadir uma faixa indevida de 
memória, provocando diversos problemas, como 
o funcionamento inadequado do programa (por 
sobrescrever dados de maneira não planejada), 
um fechamento abrupto do programa pelo sistema 
operacional ou até mesmo abrir vulnerabilidades 
para usuários mal-intencionados e tecnicamente 
qualificados tentarem injetar código malicioso ou 
acessar informações protegidas.
Algumas linguagens se comportam de maneira 
um pouco diferente. Por exemplo, na linguagem 
Rust, se o índice que será acessado depende de 
alguma interação que ocorrerá durante a execução 
(por exemplo, um valor digitado pelo usuário), ocor-
rerá um erro de tempo de execução. Porém, se o 
programador explicitamente digitar no código um 
índice superior ao comprimento do vetor, o próprio 
1919
compilador irá pegar o erro em potencial, evitando 
que o programa seja gerado com essa falha.
De maneira geral, quanto mais erros puderem ser 
capturados já em tempo de compilação, melhor, 
pois erros de compilação impedem a criação do 
programa até que estejam corrigidos. Erros não 
pegos na compilação estarão “escondidos” no 
programa e podem passar despercebidos até que 
seja tarde demais e eles acabem provocando algum 
grande estrago. 
Compilação cruzada
Um programa escrito em uma certa plataforma 
não precisa, necessariamente, ser projetado para 
executar na plataforma onde ele foi escrito. Vamos 
considerar um exemplo bastante claro: um aplica-
tivo para smartphone, que será executado em um 
aparelho com sistema iOS ou Android.
Apesar de não ser impossível, celulares hoje ainda 
são ferramentas pouco convenientes para a escri-
ta de código: suas telas são bastante pequenas, 
enquanto desenvolvedores frequentemente usam 
até dois ou três monitores diferentes para ter um 
bom campo visual. Além disso, a digitação atra-
vés de tela de toque não é tão fácil ou confortável 
quanto em um teclado físico que comporte todos 
os dedos simultaneamente.
2020
Desenvolvedores costumam programar utilizando 
PCs convencionais, como laptops ou desktops e 
sistemas operacionais como Windows, MacOS ou 
Linux – mesmo quando eles pretendem desenvol-
ver aplicativos para smartphones ou para qualquer 
tipo de computador embarcado.
Essa forma de trabalho é possível graças aos 
cross-compilers, ou compiladores cruzados. O 
compilador cruzado é desenvolvido para ser exe-
cutado em uma plataforma, mas gerar programas 
que serão executados em outra plataforma.
Por exemplo, você pode ter um kit de desenvol-
vimento para Android em um computador com 
Windows. Você escreverá seus programas em um 
processador com arquitetura Intel x86 no Windows 
e o compilador será executado no Windows, mas ao 
final do processo você terá um programa que roda 
em celulares Android com processadores ARM.
Trabalhar dessa maneira costuma ser um pouco 
mais complexo do que trabalhar diretamente na 
plataforma que irá executar os códigos, princi-
palmente na etapa de testes e debugging. Mas 
é comum que os kits de desenvolvimento que 
incluem os cross-compilers também tragam outras 
ferramentas para auxiliar nessas tarefas.
Uma dessas ferramentas costuma ser algum 
assistente para a comunicação com o dispositivo-
2121
-alvo. Através de um cabo ou de alguma forma de 
comunicação sem fio, o computador utilizado no 
desenvolvimento se comunica com o dispositivo 
sendo testado e solicita uma série de informações 
para monitorar o que está ocorrendo durante a 
execução do programa.
Outra ferramenta bastante popular é um emulador: 
um programa que é executado no computador 
de desenvolvimento imitando o comportamento 
do dispositivo-alvo, podendo inclusive executar 
o programa gerado, mas permitindo que diversas 
informações sobre a execução sejam monitoradas 
e o próprio fluxo de execução seja controlado ou 
alterado para testar diferentes casos.
22
INTERPRETAÇÃO
Quando um filme estrangeiro chega ao Brasil, é 
comum que seja oferecida a opção de assisti-lo 
dublado: uma equipe recebeu o filme na língua 
original bastante tempo antes da data de lançamen-
to, traduziu todas as suas falas para o português 
e uma equipe de atores especializados gravou 
essa tradução. Ao final desse processo, o filme 
está permanentemente disponível em português. 
O processo de compilação de um programa, que 
estudamos no capítulo anterior, é análogo a essa 
tradução.
Porém, quando estamos assistindo a um evento 
ao vivo em língua estrangeira, como uma entre-
vista com um político, atleta ou artista, é comum 
que haja tradução simultânea: um intérprete deve 
escutar o que o entrevistado está falando, traduzir 
mentalmente e imediatamente falar a tradução em 
voz alta. Linguagens de programação interpretadas 
funcionam dessa maneira.
Quando escrevemos um programa em uma lin-
guagem interpretada, ele não será traduzido para 
gerar um programa executável. Ao invés disso, 
ele será salvo como um arquivo de texto, mesmo 
que contenha o nosso programa da maneira que 
escrevemos. Todos que desejarem utilizar nosso 
programa precisarão ter em seus computadores 
23
um programa chamado de interpretador daquela 
linguagem. O interpretador irá abrir nosso código, 
ler cada linha e imediatamente realizar a instrução 
descrita naquela linha.
De maneira geral, trabalhar com uma linguagem 
interpretada tende a ser mais simples: não pre-
cisamos recompilar toda vez que fizermos uma 
modificação, tampouco compilar para diferentes 
máquinas ou sistemas. Todos os sistemas que 
possuírem um interpretador compatível poderão 
rodar o mesmo programa, sem alterações ou etapas 
adicionais por parte do programador.
Em contrapartida, linguagens interpretadas tendem 
a ser menos eficientes, e seu consumo de recursos 
computacionais (como tempo de processamento 
e uso de memória) tende a ser pior.Devemos fazer algumas considerações importan-
tes antes de prosseguir. A primeira delas é que, 
na prática, “não existe” linguagem interpretada 
ou linguagem compilada. A especificação de 
uma linguagem diz respeito aos seus comandos 
e comportamentos, mas estes podem ser coloca-
dos em um compilador ou em um interpretador. 
Porém, algumas implementações específicas de 
certas linguagens são tão populares que seu uso 
se confunde com o da própria linguagem, fazendo 
com que a linguagem em si seja muito fortemen-
24
te associada ao compilador ou interpretador em 
questão. 
Em outros casos, é uma questão cultural mesmo: 
a linguagem C, por exemplo, é considerada mais 
“difícil” de programar do que linguagens como 
Python, justamente porque aquela fornece recur-
sos para manipulação de hardware. Sendo assim, 
haveria pouca ou nenhuma vantagem em utilizar 
linguagem C em um ambiente interpretado, se 
comunicando com o interpretador ao invés de 
diretamente com o sistema físico. Já o Python, fre-
quentemente utilizado para pequenas automações, 
pode trazer mais vantagens sendo interpretado, já 
que pequenas modificações não implicam em um 
longo processo de recompilação, e seus códigos 
são compatíveis com diferentes sistemas.
Outra consideração importante é que atualmente 
são raras as linguagens puramente interpretadas, 
apesar de já ter sido comum. Conforme estuda-
remos a seguir, as principais implementações de 
linguagens que coloquialmente chamamos de 
“interpretadas” combinam diversos processos 
diferentes, incluindo uma espécie de “compilação” 
que garantirá um desempenho melhor. Esse é o 
caso, por exemplo, do Python. Estudaremos essa 
abordagem híbrida a seguir.
2525
PROCESSOS HÍBRIDOS 
Para atenuar os problemas de desempenho tipi-
camente associados às linguagens interpretadas, 
diversas implementações começaram a adotar 
alguns processos relacionados à compilação, 
enquanto se tenta preservar a linguagem ao me-
nos parcialmente interpretada para não perder as 
vantagens, como a portabilidade do programa (o 
mesmo programa poder rodar em vários sistemas 
sem a necessidade de gerar executáveis diferentes).
Citaremos algumas dessas técnicas mais comuns, 
utilizando como exemplo as linguagens Java e 
Python.
Bytecode e máquinas virtuais
No processo de compilação, diversas otimizações 
podem ser realizadas. Por exemplo, abordamos 
técnicas como loop unrolling e funções inline. Parte 
dessas otimizações pode ser aplicada porque o 
compilador pode, durante a compilação, analisar 
mais de uma vez o código, identificar as relações 
entre diferentes partes do código, a frequência com 
a qual se acredita que cada bloco de códigos deve 
ser executado, entre outras coisas. Um interpreta-
dor clássico não possui toda essa versatilidade.
Em contrapartida, ao final do processo de compi-
lação, temos um código binário, que é totalmente 
2626
dependente da arquitetura para a qual ele foi com-
pilado, e não é compatível com outros sistemas 
computacionais, enquanto um programa escrito 
em linguagem interpretada pode ser executado 
sem modificações em qualquer computador que 
possua o interpretador adequado.
Uma estratégia intermediária encontrada, que 
permite um certo grau de otimizações e o conse-
quente ganho de performance, mas preservando 
a portabilidade do código é a compilação para 
bytecode, que será executado em uma máquina 
virtual. 
Uma máquina virtual é um computador fictício, im-
plementado em software. Ela possui suas próprias 
instruções e recursos. Suas diferentes instruções 
podem ser representadas por um binário próprio, 
conhecido por bytecode. Esse bytecode é indepen-
dente da máquina real, física, na qual a máquina 
virtual está sendo executada.
Na prática, a máquina virtual é um interpretador de 
bytecode. Ela irá ler o código escrito em bytecode 
e executar as diferentes operações, resultando em 
uma espécie de “tradução ao vivo” de bytecode 
para binário nativo da máquina física. Uma van-
tagem dessa abordagem é que o bytecode é uma 
linguagem muito mais próxima da linguagem real 
de máquina do que uma linguagem de alto nível, 
2727
tornando a sua interpretação muito mais eficiente 
do que a interpretação direta de um programa em 
linguagem de alto nível.
Para chegarmos ao bytecode, é necessária uma 
etapa de compilação: um compilador irá realizar 
em nosso código de alto nível os mesmos proces-
sos que o compilador tradicional das linguagens 
compiladas fará, mas o resultado, ao invés de 
ser um código objeto escrito em binário nativo, 
será um bytecode, que posteriormente pode ser 
interpretado pela máquina virtual. Esses proces-
sos incluem possíveis otimizações, tornando o 
bytecode gerado muito mais eficiente do que o 
código de alto nível original. Esse é outro ganho de 
performance providenciado pela ideia de bytecode 
e máquina virtual.
A implementação de referência do Python, conhecida 
por CPython, segue esse processo. O interpretador 
de Python inclui um compilador de bytecode e uma 
máquina virtual própria. Na primeira execução de 
um programa, ele é compilado para bytecode, e nas 
execuções seguintes, essa etapa pode ser pulada, 
com o bytecode sendo chamado diretamente pela 
máquina virtual. 
Em programas Python com vários módulos, pode-
mos notar o resultado da compilação ao analisar 
a pasta do nosso projeto: se você importou vários 
2828
arquivos .py diferentes (a extensão padrão para 
códigos Python), perceberá que apareceram diver-
sos arquivos .pyc, que é a extensão para códigos 
Python compilados para bytecode. É possível 
também instruir explicitamente o Python a gerar 
e armazenar o bytecode, de modo que o código 
seja enviado já pré-compilado para os usuários.
Compilação just-in-time
Outra etapa que você deve se recordar que ocorre 
na compilação é a otimização específica para o 
hardware-alvo. Essa etapa, ao contrário das outras 
técnicas que mencionamos, não depende apenas 
de lógica, mas da presença de recursos específicos 
no hardware onde o código será executado.
Por exemplo, se o programa utiliza diversas ope-
rações sobre grandes conjuntos de números de 
ponto flutuante, o compilador pode utilizar módulos 
específicos da CPU otimizados para fazer essas 
operações em grande velocidade caso ela os pos-
sua. Se esses cálculos podem ocorrer de maneira 
independente entre si, o compilador pode utilizar 
recursos de paralelismo da CPU, caso ela permita.
Na abordagem de máquinas virtuais estudada, 
a princípio não é possível realizar esse nível de 
otimização. Afinal, o programa está sendo compi-
lado para uma máquina “imaginária” e não possui 
informações sobre a máquina física que está exe-
2929
cutando a máquina virtual, e por isso não pode se 
beneficiar de seus recursos.
Para contornar essa limitação surgiu o conceito 
de compilação just-in-time, frequentemente abre-
viada como JIT. Como o nome sugere, essa é uma 
compilação feita “em cima da hora”, quando o pro-
grama está prestes a ser executado – ao contrário 
do conceito tradicional de compilação, conhecido 
por compilação ahead-of-time, ou seja, realizada 
em uma etapa anterior à execução do programa.
Voltemos ao conceito anterior: máquinas virtuais 
interpretando bytecode. Algumas otimizações lógicas 
foram realizadas, e agora a máquina virtual deve 
interpretar todo o bytecode. Algumas máquinas 
virtuais trazem um compilador JIT junto consigo. 
Na primeira execução do bytecode, ela identifica 
trechos do código que poderiam ser compilados 
de bytecode para binário nativo e o faz imediata-
mente. Nessa etapa, é possível, inclusive, realizar 
otimizações de hardware – afinal, no momento da 
execução, a máquina física é conhecida. Essas partes 
pré-compiladas nessa primeira execução podem ser 
armazenadas, de modo que em execuções futuras 
a etapa de compilação pode ser pulada. Por isso, 
é comum que em linguagens com JIT a primeira 
execução seja significativamente mais lenta, e as 
execuções posteriores sejam mais rápidas.
3030
Um exemplo clássico é o Java.Para que seu com-
putador execute programas escritos em Java, você 
deve ter alguma implementação da Java Virtual 
Machine (JVM) instalada em seu computador – 
independentemente de qual seja sua CPU ou seu 
sistema operacional. O código Java é compilado 
para o bytecode da JVM, e em sua primeira exe-
cução o JIT irá compilar partes do código para 
binário nativo, resultando em um grande ganho 
de performance.
Módulos completos escritos em Java podem ser 
distribuídos em sua forma pré-compilada para by-
tecode, independentemente de qual a plataforma 
que irá executá-los e em qual plataforma eles foram 
originalmente compilados. Tanto a compilação de 
trechos específicos para binário nativo quanto a 
interpretação do restante do bytecode só ocorrerão 
no momento da execução, já na máquina-alvo.
Aplicações Android em geral são executadas por uma 
máquina virtual conhecida como ART desde a versão 
5.0, que substituiu uma máquina anterior conhecida 
como Dalvik.
Por isso diferentes aparelhos podem baixar aplicações 
da mesma loja, sem que os desenvolvedores precisem 
gerar múltiplas versões diferentes: o aplicativo baixado 
SAIBA MAIS
3131
pela loja é um bytecode que será processado pelo ART, 
podendo inclusive ser compilado para linguagem nativa 
da CPU do celular.
A linguagem padrão para desenvolvimento Android era 
Java, e atualmente, apesar dele ainda ser aceito, o Google 
tem incentivado a migração para a linguagem Kotlin.
Consulte a documentação do ART para saber mais sobre 
seus recursos: https://source.android.com/devices/tech/
dalvik?hl=pt-br. Acesso em 13 ago 2021.
Intercompatibilidade entre linguagens
A popularização da adoção de máquinas virtuais 
com seus próprios bytecodes acabou por trazer 
outra conveniência: linguagens diferentes inter-
compatíveis entre si.
Vamos tomar como exemplo novamente o Java. 
Sabemos que o resultado da primeira compilação 
de um programa em Java é um bytecode que 
pode ser interpretado pela JVM. O programa final 
é esse bytecode que será interpretado pela JVM 
e/ou compilado pelo JIT. O que aconteceria se 
um programa escrito em outra linguagem fosse 
compilado para bytecode da JVM? A JVM não 
“sabe” de onde surgiu o código que ela recebe. Ela 
apenas recebe um bytecode e executa. 
Várias linguagens atualmente, como Kotlin e Clojure, 
possuem compiladores para gerar bytecode para 
3232
JVM. Sendo assim, é perfeitamente possível um 
programa em Kotlin utilizar uma biblioteca escrita 
em Java e vice-versa, afinal, uma vez compilados, 
todos eles são apenas programas em bytecode. 
De maneira experimental, existe até mesmo Py-
thon compilando para JVM, uma implementação 
conhecida como Jython.
Algo parecido ocorre na família de linguagens 
.NET, da Microsoft. Linguagens distintas, como 
C#, VB.NET e F# utilizam a mesma Common Lan-
guage Infrastructure (Infraestrutura de Linguagem 
Comum) – CLI –, na qual o programa é compilado 
para uma linguagem intermediária (Common Inter-
mediate Language), e essa será executada sobre 
um ambiente virtual responsável por fazer a ponte 
com a máquina física.
Assim como no caso da Java Virtual Machine, 
outros desenvolvedores fornecem implementa-
ções de linguagens variadas gerando código em 
Common Intermediate Language, como o projeto 
IronPython, visando a compatibilidade dentre o 
Python e as linguagens .NET.
3333
Não é necessário que diferentes linguagens sejam 
“compatíveis” entre si ou sejam executadas em uma 
mesma máquina virtual para que programas escritos 
em diferentes programas se comuniquem e troquem 
recursos.
Uma API (Application Programming Interface – interfa-
ce de programação de dispositivos) pode ser utilizada 
para intermediar conversas entre programas que não 
se relacionariam de maneira alguma.
Ela consiste de um conjunto de regrinhas, como formato 
das mensagens que serão trocadas (ordem e tipo dos 
dados, por exemplo). Dessa maneira, um programa pode 
solicitar que outro programa distinto realize parte de seu 
trabalho sem ter acesso ao seu funcionamento interno.
Um caso comum são as APIs dos próprios sistemas 
operacionais: para trabalhar com diversos recursos, 
como funcionalidades gráficas, programação concor-
rente, comunicação de rede, entre outras, os sistemas 
operacionais possuem seus próprios programas e biblio-
tecas e fornecem uma documentação de como nossos 
programas podem acessá-los e solicitar certas tarefas.
Outro caso comum são as APIs HTTP: programas sen-
do executados em servidores na internet que recebem 
mensagens de outros programas rodando nas chamadas 
máquinas-cliente solicitando acesso a dados. É assim 
que o aplicativo de rede social do seu smartphone so-
licita aos servidores da rede o conteúdo que irá exibir 
na tela do seu aparelho.
FIQUE ATENTO
34
CONSIDERAÇÕES FINAIS
Nesta unidade, focamos nas diferentes formas 
como um programa escrito em uma linguagem de 
programação de alto nível – isto é, uma linguagem 
mais conveniente para nós, seres humanos – pode 
ser traduzido para instruções em linguagem de 
máquina, a linguagem nativa do computador.
Começamos pela ideia mais básica, a compilação, 
também chamada em alguns locais de compilação 
ahead-of-time. Nesse processo, o programa é tra-
duzido uma única vez, após passar por uma série 
de análises, otimizações e ligações com outros 
códigos. Ao final do processo, temos um programa 
executável, ou ao menos uma biblioteca binária que 
pode ser utilizada por outros programas. Ela tem 
a vantagem de poder gerar programas bastante 
eficientes e otimizados, e evitar que o usuário pre-
cise instalar ferramentas da linguagem ou compilar 
sua própria versão. Como desvantagem, devemos 
fazer uma compilação específica para cada tipo de 
sistema que irá usar nosso programa, e recompilar 
sempre que fizermos uma modificação.
Também estudamos como um programa executável 
é organizado na memória durante o seu funciona-
mento, as diferentes maneiras que ele pode interagir 
com uma biblioteca – um conjunto de códigos já 
prontos – e a importância de compreender quais 
35
processos ocorrem durante a compilação e quais 
ocorrem durante a execução, como a verificação 
de certos tipos de erro ou a identificação de quais 
funções serão executadas em uma certa região 
do código.
Em seguida, estudamos a interpretação, uma 
ideia semelhante a uma “tradução simultânea”: 
um programa, chamado de interpretador, abre o 
código-fonte escrito em linguagem de alto nível e 
executa as instruções conforme as lê. Essa aborda-
gem é bastante ineficiente e pouco otimizada, mas 
garante o máximo de portabilidade para o código, 
ou seja, o mesmo código pode ser executado em 
vários sistemas diferentes, desde que cada um 
possua um interpretador.
Concluímos verificando que uma ideia muito for-
te é uma abordagem híbrida: os programas são 
compilados, de modo a garantir eficiência e otimi-
zações, mas ao invés de serem traduzidos para a 
linguagem da máquina física, eles são traduzidos 
para um binário conhecido como bytecode, que é 
especificado por uma máquina virtual. Ela trabalha 
como uma espécie de interpretador, traduzindo o 
bytecode para linguagem nativa. Assim, os bytecode 
compilados são portáveis por serem independentes 
do sistema real, mas ao mesmo tempo compactos 
e otimizados por terem passado pelo processo 
de compilação. Por fim, estudamos que algumas 
36
linguagens levam a ideia a um patamar adicional, 
acrescentando um compilador just-in-time ao 
interpretador que decide durante a execução se 
certas partes do bytecode devem ser compiladas 
para a máquina nativa para ganhar desempenho 
ou se serão apenas interpretadas mesmo.
Referências Bibliográficas 
& Consultadas
BENNETT, J. An introduction to Python bytecode. 
Opensource.com, 2018. Disponível em: https://
opensource.com/article/18/4/introduction-python-
bytecode. Acesso em: 24 de setembro de 2021.
CORREA, A. G. D. Programação I. São Paulo: 
Pearson, 2015. [Biblioteca Virtual].
DEITEL, H. M.; DEITEL, P. J; Java: como 
programar. 8. ed. São Paulo:Pearson, 2010. 
[Biblioteca Virtual].
DEITEL, P; DEITEL, H; Java: como programar. 
10. ed. São Paulo: Pearson Education do Brasil, 
2016. [Biblioteca Virtual].
DOWNEY, A. B. Pense em Python: Pense como 
um cientista da computação. 2. ed. São Paulo: 
Editora Novatec, 2016.
FELIX, R. (Org.). Programação orientada a 
objetos. São Paulo: Pearson, 2016. [Biblioteca 
Virtual].
IRONPYTHON. .NET Integration. IronPython.
net. Disponível em: https://ironpython.net/
documentation/dotnet/. Acesso em: 24 de 
setembro de 2021.
JUNEAU, J.; BAKER, J.; NG, V.; SOTO, L.; 
WIERZBICKI, F. The Definitive Guide to Jython. 
Readthedocs.io, 2010. Disponível em: https://
jython.readthedocs.io/en/latest/. Acesso em: 24 
de setembro de 2021.
KLABNIK, S; NICHOLS, C. The Rust Programming 
Language. No Starch Press. Disponível em 
https://doc.rust-lang.org/book/. Acesso em: 24 
de setembro de 2021.
MICROSOFT. Common Language Runtime 
overview. Microsoft Docs, 2020. Disponível 
em https://docs.microsoft.com/en-us/dotnet/
standard/clr. Acesso em: 24 de setembro de 
2021.
LEAL, G. C. L. Linguagem, programação e 
banco de dados: guia prático de aprendizagem. 
Curitiba: Intersaberes, 2015. [Biblioteca Virtual]. 
SANTOS, M. G. dos; SARAIVA, M. de O.; 
GONÇALVES, P. de F. Linguagem de 
programação. Porto Alegre: SAGAH, 2018. 
[Minha Biblioteca].
SEBESTA, R. W. Conceitos de linguagens de 
programação. 11. ed. Porto Alegre: Bookman, 
2018. [Minha Biblioteca]. 
SILVA, E. A. da. Introdução às linguagens de 
programação para CLP. São Paulo: Blucher, 
2016. [Biblioteca Virtual].
SILVA, F. M. da; LEITE, M. C. D.; OLIVEIRA, D. B. 
de. Paradigmas de programação. Porto Alegre: 
SAGAH, 2019. [Minha Biblioteca].
TUCKER, A.; NOONAN, R. Linguagens de 
programação: princípios e paradigmas. 2. ed. 
Porto Alegre: AMGH, 2014. [Minha Biblioteca].
	Introdução
	Compilação 
	Interpretação
	Processos híbridos 
	Considerações finais
	Referências Bibliográficas & Consultadas

Mais conteúdos dessa disciplina