Saltar para o conteúdo
Topos Uranos

Resumo

Esta aula explica o que é um programa e o que faz um compilador: a máquina só executa operações elementares sobre números na memória, e o código-fonte precisa ser traduzido para código de máquina antes de ser executado. Com um programa de dois arquivos, percorre as três etapas dessa tradução em C++ (pré-processamento, compilação e ligação) e distingue o que ocorre ao compilar do que ocorre ao executar, com um erro de cada tipo. Compara as linguagens compiladas com as interpretadas, resume a história de C++ até os padrões C++23 e C++26 e precisa qual C++ este curso ensina.

Objetivos de aprendizagem

  1. Explicar, com um esquema, o percurso de um arquivo-fonte até um executável: pré-processamento, compilação e ligação, com o que entra e o que sai de cada etapa.
  2. Classificar um acontecimento como próprio da compilação, da ligação ou da execução, como um erro de sintaxe, a falta de uma função ou uma divisão por zero.
  3. Comparar uma linguagem compilada com uma interpretada segundo o momento em que os erros são detectados e o que o programador vê da máquina.
  4. Situar no tempo os padrões da linguagem e explicar por que o curso usa C++23.

O que é um programa

Um programa é uma sucessão de instruções que uma máquina pode executar para transformar uns dados em uns resultados. Na aula anterior, Do problema ao algoritmo, viu-se que antes do programa está o algoritmo, o método que resolve o problema, escrito para que uma pessoa o siga. O programa é esse mesmo método escrito para uma máquina, que não entende frases nem intenções, mas ordens elementares: copiar um número de um lugar da memória para outro, somar dois números, compará-los, saltar para outra instrução conforme o resultado.

A memória do computador é uma longa fila de casas numeradas, cada uma com um número de 0 a 255 (um byte). Tudo o que a máquina manipula está ali codificado como números: os dados e também as próprias instruções. Se isso é assim, um programa executável é uma sucessão de números que o processador interpreta como ordens, o código de máquina, e depende do processador: os da família x86-64, comuns nos computadores de mesa, e os ARM, comuns nos celulares e nos Mac recentes, não entendem os mesmos números.

Ninguém escreve hoje em código de máquina. Escreve-se em uma linguagem de alto nível, como C++, cujas instruções se parecem com a notação matemática, em um arquivo de texto chamado código-fonte (ou simplesmente fonte). Daí se segue a necessidade de um tradutor que produza, a partir da fonte, o código de máquina equivalente: em C++, o compilador.

ExemploUm programa e o que a máquina executa

Este é um programa completo em C++. Ainda não é preciso entendê-lo: cada uma de suas linhas será explicada na aula 1.5. Basta ver que é texto, que pode ser lido, e que abaixo aparece o que ele escreveu ao ser executado.

Programa em C++
// Será explicado linha por linha na aula 1.5.#include <print>​int main(){    std::println("Olá, mundo");    std::println("2 + 3 = {}", 2 + 3);}
Saída
Olá, mundo2 + 3 = 5

Para ver o outro extremo da tradução, considere-se uma função que só soma dois inteiros (as funções serão estudadas na unidade 5). Não é um programa completo, porque lhe falta main, o ponto onde a execução começa, mas o compilador pode traduzi-la por si só.

Código C++
int add(int a, int b){    return a + b;}

O programa objdump mostra o código de máquina que o GCC 15 produziu para ela em um computador x86-64 com Linux: em cada linha, a posição da instrução, seus bytes em hexadecimal e seu nome legível.

Terminal
$ g++ -std=c++23 -O1 -c add.cpp$ objdump -d add.o0000000000000000 <_Z3addii>:   0:	f3 0f 1e fa          	endbr64   4:	8d 04 37             	lea    (%rdi,%rsi,1),%eax   7:	c3                   	ret

A soma inteira se reduziu a oito bytes. A primeira instrução, endbr64, é uma marca de segurança que não calcula nada; a segunda, lea, soma os dois números que a função recebeu em dois registradores do processador (%rdi e %rsi) e deixa o resultado em um terceiro (%eax); a terceira, ret, volta ao ponto de onde a função foi chamada. O nome _Z3addii é a forma como o compilador codifica “função add com dois parâmetros int”.

Duas observações decorrem do exemplo. Primeira: o código de máquina não conserva os nomes a e b, nem a indentação, nem os comentários; tudo isso existia para quem lê a fonte, e o compilador o usou para decidir que instruções escrever. Segunda: o que o programador de C++ escreve se traduz em poucas instruções e sem intermediários; essa proximidade da máquina é a razão da eficiência de C++ e, como se verá no curso, também de alguns de seus perigos.