Linguagem C++ / Módulo 1: Introdução e Ecossistema / Compiladores

Compiladores

Três ferramentas que muita gente confunde

Antes de falar sobre compiladores especificamente, vale separar três conceitos que costumam aparecer juntos e que muita gente trata como sinônimos: o compilador, o sistema de build e a IDE. São ferramentas completamente diferentes, com responsabilidades distintas, mas utilizadas em conjunto. Nos próximos capítulos aprenderemos sobre sistemas de build e IDE, e como eles fazem uso dos compiladores.

O que você já tem instalado

Se você passou pelo nosso curso de C, você já tem um compilador de C++ na sua máquina — e provavelmente não sabia.

No Linux, quando instalamos o build-essential, no Windows, quando instalamos o MinGW, ou no Mac quando instalamos o gcc, não estávamos instalando somente o compilador da linguagem C. Estávamos instalando uma coleção de diferentes compiladores: a GNU Compiler Collection (GCC).

Essa coleção é distribuída pela Free Software Foundation e é peça fundamental de diversos projetos, como o Kernel Linux e os projetos GNU — que reúnem diversos softwares gratuitos. O GCC é um dos maiores programas gratuitos que existem, com mais de 15 milhões de linhas de código.

Atualmente o GCC é capaz de compilar códigos das linguagens C, C++, Ada, Go, D, Modula-2, Rust, COBOL, entre outras.

gcc, g++, gfortran, gccgo: por que tantos nomes

Se o GCC compila várias linguagens diferentes, como o sistema sabe que você quer compilar C++ e não C? A resposta está no tipo de arquivo: .c é interpretado como C, .cpp (ou .cc, .cxx) como C++, .f90 como Fortran, e assim por diante. É a extensão do arquivo que diz ao GCC qual frontend interno usar para interpretar o código-fonte — e isso vale tanto para o comando gcc quanto para o g++, gfortran (Fortran), gccrs (Rust) ou gccgo (Go). Ou seja: gcc main.cpp, assim como gfortran main.cpp compilam o arquivo como C++ de verdade, usando o mesmo analisador de código que o comando g++ usaria.

Isso é possível porque um compilador não é um bloco único — ele é dividido em etapas, e as três mais relevantes aqui são o frontend, o backend e o linker. O frontend é a parte responsável por ler o código-fonte de uma linguagem específica (C, C++, Fortran, Go, ...), entender sua sintaxe e suas regras, e traduzir tudo para uma representação intermediária genérica. O backend pega essa representação e gera código Assembly otimizado para a arquitetura do processador alvo — o Assembly de um processador Intel, de um ARM e de um RISC-V são diferentes entre si —, que então é convertido em código de máquina. É aqui que entra o linker: ele une os diferentes trechos de código de máquina gerados pelo backend (o seu programa e as bibliotecas que ele usa) em um único executável.

O GCC é uma arquitetura com um único backend compartilhado e múltiplos frontends, um para cada linguagem suportada. É por isso que ele consegue compilar C, C++, Fortran, Rust e tantas outras: cada linguagem tem seu próprio frontend (cc1 para C, cc1plus para C++, f951 para Fortran), mas todos entregam a mesma representação intermediária para o mesmo backend. Quando você instala o gfortran — que não vem por padrão junto com o build-essential —, você não está instalando um compilador novo do zero: está adicionando o frontend de Fortran a uma instalação de GCC que já tinha o backend.

Voltando à pergunta original: se gcc, g++ e gfortran acionam o frontend correto com base na extensão do arquivo, qual é a diferença entre eles? A diferença está na linkagem. Veja o que acontece se você usar o gfortran para compilar este main.c:

#include <stdio.h>
 
int main(void) {
    printf("Hello, World!\n");
    return 0;
}
gfortran main.c -o programa

Como a extensão é .c, o GCC aciona o frontend de C — o mesmo que o gcc usaria — e a compilação em si funciona sem erro. Mas o gfortran, como o próprio nome sugere, foi pensado para linkar as bibliotecas do Fortran por padrão, não a libc, que contém o stdio.h onde printf está definido. O resultado é um erro de linkagem, mesmo com o código C perfeitamente válido.

A mesma lógica se aplica entre gcc e g++. Considere este main.cpp, agora usando iostream — uma biblioteca equivalente a stdio.h que usávamos em C:

#include <iostream>
 
int main() {
    std::cout << "Hello, World!\n";
    return 0;
}
gcc main.cpp -o programa

Esse comando compila corretamente, mas falha na linkagem pelo mesmo motivo do exemplo anterior: o gcc não linka a libstdc++ por padrão. Já o g++:

g++ main.cpp -o programa

Linka a libstdc++ automaticamente, resolvendo esses símbolos sem esforço extra. Por isso, mesmo que o gcc interprete corretamente um arquivo .cpp, o comando correto para compilar e linkar um programa em C++ é sempre g++.

Versão da linguagem vs. versão do compilador

Existem duas versões diferentes rolando aqui ao mesmo tempo, e vale não confundi-las. Uma é a versão do padrão da linguagem C++ — C++17, C++20, C++23 — definida pelo comitê ISO, que especifica quais recursos a linguagem deve ter. A outra é a versão do próprio GCC — GCC 11, GCC 12, GCC 13 —, que é o software que implementa esses padrões.

Quando o comitê publica um novo padrão, ele não aparece magicamente em todo compilador. Cada compilador (GCC, Clang, MSVC) precisa implementar os recursos daquela versão da linguagem, e isso leva tempo — geralmente anos. Por isso, o suporte a um padrão recente costuma ser parcial nas primeiras versões do compilador que o adotam, e só fica completo (ou "quase completo") em versões posteriores. Um GCC mais antigo pode não reconhecer uma flag como -std=c++23 simplesmente porque na sua versão atual ainda não implementou esse padrão, ou pode aceitar a flag mas implementar só uma parte dos recursos daquela versão da linguagem.

Por isso que os binários do gcc costumam vir versionados no sistema, como gcc-11, gcc-12, gcc-13 — cada número corresponde a uma versão do GCC, com um nível de suporte diferente aos padrões mais recentes das linguagens. Isso permite ter várias versões do GCC instaladas ao mesmo tempo e escolher qual usar por projeto, o que é especialmente útil quando um projeto antigo depende do comportamento de uma versão específica, enquanto outro já pode aproveitar os recursos mais novos.

g++-13 main.cpp -o programa

Usando o g++ na prática

Para confirmar que ele está disponível e ver qual versão está instalada:

g++ --version

Para compilar um arquivo .cpp:

g++ -std=c++23 main.cpp -o programa

A flag -std=c++23 instrui o compilador a usar o padrão C++23. Se quiser ver quais padrões o seu g++ suporta (no Linux):

g++ --help=c++ | grep std=c++

Os três grandes compiladores

Existem três compiladores de C++ que você vai encontrar no mercado:

GCC / G++ — o mais utilizado em ambientes de desenvolvimento de código aberto. É o compilador padrão do nosso curso.

Clang / Clang++ — desenvolvido pela LLVM Foundation, com foco em mensagens de erro mais claras e em velocidade de compilação. Para instalá-lo no Ubuntu:

sudo apt install clang

Para compilar com ele:

clang++ -std=c++23 main.cpp -o programa

MSVC (Microsoft Visual C++) — o compilador da Microsoft, exclusivo para Windows, distribuído junto com o Visual Studio. É o padrão na maioria dos projetos Windows corporativos e em jogos desenvolvidos para a plataforma.

Os três compiladores seguem o mesmo padrão ISO, então um código C++ válido deve compilar nos três — mas cada um tem suas peculiaridades em relação a extensões, otimizações e mensagens de erro. Em projetos grandes, é comum testar nos três para garantir portabilidade.

Compilando sem instalar nada

Se você quiser experimentar um trecho de código rapidamente, sem configurar nada na sua máquina, existem compiladores online que rodam diretamente no navegador:

  • OnlineGDB — interface mais simples, com suporte a debugging. Boa opção para quem está começando e quer algo mais próximo de uma IDE.
  • Compiler Explorer (godbolt.org) — Permite escolher entre dezenas de compiladores e versões, ver o assembly gerado e comparar a saída de GCC, Clang e MSVC lado a lado. Muito usado para entender como o compilador transforma o seu código.

Esses ambientes são úteis para aprendizado e para compartilhar exemplos, mas não substituem um ambiente local para projetos reais.

Qual padrão usar

Os exemplos deste curso utilizam C++20 como linha de base, com menções a recursos do C++23 e C++26 quando relevante. O C++20 é o padrão mais recente com suporte estável em todas os compiladores mencionados, e é o que você vai encontrar na maioria dos projetos modernos hoje.

Para garantir que o compilador aplique o padrão correto, use sempre a flag explícita:

g++ -std=c++20 main.cpp -o programa

Sem essa flag, o g++ não compila necessariamente com o padrão mais novo que ele suporta — cada versão do GCC traz embutido um padrão específico como opção padrão, que varia de release para release (por exemplo, o GCC 11 usa C++17 como padrão implícito, enquanto o GCC 13 já usa C++20). Depender desse comportamento implícito é arriscado: o mesmo comando g++ main.cpp, sem a flag -std, pode compilar com padrões diferentes dependendo de qual GCC está instalado na máquina. Um código que usa recursos de uma versão mais nova da linguagem pode falhar silenciosamente ou gerar erros confusos nesse cenário, quando compilado numa máquina com uma versão de compilador mais antiga.

No próximo capítulo vamos falar sobre sistemas de build — a camada que fica entre você e o compilador em projetos com múltiplos arquivos.