O que é um compilador?
O problema da tradução
Computadores só entendem código de máquina: instruções binárias brutas específicas para uma arquitetura de CPU (x86, ARM, etc.). Mas humanos escrevem código em linguagens de alto nível como Ruby, C ou Go. Algo precisa fazer essa ponte.
Um compilador é um programa que traduz código-fonte escrito em uma linguagem (a linguagem fonte) para outra linguagem (a linguagem alvo) (tipicamente código de máquina ou uma representação intermediária) antes do programa ser executado.
flowchart LR
S["Código fonte<br/>hello.c<br/>escrito uma vez pelo desenvolvedor"] --> C["Compilador (gcc)<br/>lexer · parser · otimizador · geração de código"] --> B["Código de máquina<br/>hello (binário) 01001010...<br/>roda direto na CPU, muitas vezes"]
A ideia central: a compilação acontece uma vez, a execução acontece muitas vezes. É por isso que programas compilados são rápidos: o custo da tradução é pago antecipadamente.
O pipeline de compilação
Um compilador tipicamente trabalha em fases, cada uma transformando o código mais adiante:
flowchart TB
S["Código fonte"] --> L["1. Análise léxica<br/>quebra o código em tokens"] --> P["2. Parsing<br/>constrói uma AST a partir dos tokens"] --> SA["3. Análise semântica<br/>checa tipos, escopos, variáveis não declaradas"] --> O["4. Otimização<br/>eliminação de código morto, constant folding, inlining"] --> CG["5. Geração de código<br/>código de máquina, bytecode..."] --> B["Binário executável"]
Vamos explorar cada fase em detalhe nas próximas aulas. Por agora, entenda que um compilador não é um único passo monolítico: é um pipeline de transformações.
Linguagens compiladas na prática
Algumas linguagens compiladas conhecidas:
| Linguagem | Compilador | Alvo | Caso de Uso |
|---|---|---|---|
| C | gcc, clang | Código de máquina | Kernels de SO, sistemas embarcados |
| C++ | g++, clang++ | Código de máquina | Jogos, navegadores, bancos de dados |
| Go | go build | Código de máquina | Servidores, ferramentas CLI |
| Rust | rustc | Código de máquina | Programação de sistemas, segurança |
| Java | javac | Bytecode (JVM) | Enterprise, Android |
| Swift | swiftc | Código de máquina | Apps iOS/macOS |
Compilação em C: um exemplo concreto
// hello.c
#include <stdio.h>
int main() {
printf("Hello, world!\n");
return 0;
}
# Compilar: código fonte → código de máquina
gcc -o hello hello.c
# O resultado é um binário independente
file hello
# hello: Mach-O 64-bit executable arm64
# Rodar o binário — nenhum compilador necessário em tempo de execução
./hello
# Hello, world!
Observe: após a compilação, o binário roda sem o gcc instalado. O trabalho do compilador está feito. Isso é fundamentalmente diferente de linguagens interpretadas.
Ahead-of-time (AOT) vs just-in-time (JIT)
Compiladores tradicionais são Ahead-of-Time (AOT): compilam tudo antes da execução:
flowchart LR
A["Compilar TUDO<br/>início lento"] --> B["Rodar o binário<br/>execução rápida"]
Mas alguns sistemas usam compilação Just-in-Time (JIT): compilam código durante a execução:
flowchart LR
A["Começar a rodar<br/>início rápido"] --> B["Identificar código quente<br/>perfilar"] --> C["Compilar o caminho quente<br/>otimizar"] --> D["Rodar mais rápido"]
Compiladores JIT podem às vezes produzir código mais rápido que AOT porque possuem informações de tempo de execução (quais branches são tomados, quais tipos são usados). Vamos explorar JIT em profundidade na última aula.
Por que isso importa para desenvolvedores Ruby
Ruby é frequentemente chamada de "linguagem interpretada", mas a realidade é mais nuançada:
- CRuby (MRI) compila seu código para bytecode, depois interpreta esse bytecode na máquina virtual YARV
- YJIT (Ruby 3.1+) adiciona um compilador JIT que compila bytecode quente para código de máquina em tempo de execução
- TruffleRuby usa o JIT do GraalVM para otimização agressiva
- o mruby pode pré-compilar scripts para bytecode antes da execução (com o
mrbc) para sistemas embarcados
flowchart LR
R["seu_codigo.rb"] --> L["Lexer"] --> P["Parser"] --> C["Compilador"] --> B["Bytecode"] --> V["VM YARV"]
V --> J["YJIT"] --> M["Código de máquina"]
Entender compilação ajuda você a entender:
- Por que Ruby é "mais lento" que C (overhead de interpretação)
- Por que YJIT torna Ruby mais rápido (eliminando esse overhead para caminhos quentes)
- Por que
requireé lento (parsing + compilação em cada boot) - Por que
bootsnapajuda (cacheia bytecode compilado)
Compilação cruzada (cross-compilation)
Um cross-compiler produz código para uma plataforma diferente daquela em que ele roda:
flowchart LR
M["Seu Mac (ARM64)<br/>o cross-compiler roda aqui"] -->|binário| S["Servidor Linux (x86_64)<br/>roda aqui"]
Isso é comum em Go:
# Compilar no Mac para Linux
GOOS=linux GOARCH=amd64 go build -o myapp
Gems Ruby com extensões nativas (como pg, nokogiri) contêm código C que precisa ser compilado para cada plataforma. Muitas trazem versões pré-compiladas para as plataformas comuns; quando a sua não está coberta, o bundle install compila do fonte, e esse passo às vezes falha numa arquitetura diferente.
Resumo
| Conceito | Descrição |
|---|---|
| Compilador | Traduz código-fonte para código alvo antes da execução |
| Pipeline de compilação | Lexing → Parsing → Análise Semântica → Otimização → Geração de Código |
| Compilação AOT | Compilar tudo antes de rodar (C, Go, Rust) |
| Compilação JIT | Compilar código quente durante a execução (YJIT, JVM) |
| Código de máquina | Instruções binárias que a CPU executa diretamente |
| Compilação cruzada | Compilar em uma plataforma para rodar em outra |
| Bytecode | Código intermediário para uma máquina virtual (não código de máquina bruto) |
Continue com a aula 2
O que é um interpretador?
Explica interpretadores tree-walking, o espectro entre compilador e interpretador, REPLs, recursos dinâmicos do Ruby e o bootsnap.
Com uma conta grátis você também tem os quizzes, seu progresso e suas anotações, e a discussão.