CodeGym /Cursos /C++ SELF /Toolchain: GCC/Clang/MSVC, padrões e suporte a C++23

Toolchain: GCC/Clang/MSVC, padrões e suporte a C++23

C++ SELF
Nível 33, Lição 0
Disponível

1. Por que conhecer o toolchain

Quando você trabalha em uma IDE, ela se parece com um “mordomo esperto”: chama o compilador, define flags, e em segundo plano executa o linker. Por causa disso, o iniciante facilmente cai na armadilha: parece que “a mágica do build” está embutida na linguagem. Mas assim que você sai da IDE (ou o projeto de repente não compila na máquina do colega), fica claro que a mágica é só um conjunto de ferramentas e configurações.

O termo toolchain é um nome conveniente para o “conjunto de ferramentas e chaves” que transforma seus arquivos .cpp em um programa executável. Se você entende o que faz parte do toolchain, vai distinguir mais rápido “meu código está quebrado” de “minhas ferramentas estão configuradas de forma diferente”, e vai parar de consertar por tentativa e erro.

Toolchain na prática: compilação, linkagem e std::vector

Na prática, toolchain não é só “o compilador”. São, no mínimo, três grandes partes: compilador, linker e biblioteca padrão. E um ponto importante: quando você diz “estou usando o GCC”, muitas vezes na verdade está se referindo a um conjunto — compilador, linker e biblioteca — que funcionam bem juntos.

Abaixo está um esquema útil para ter em mente. Não como desenho de prova, mas como “mapa da região” para saber onde procurar um problema.

flowchart LR
    A["Origem: .cpp/.hpp"] --> B["Compilador (compiler)"]
    B --> C["Objetos: .o/.obj"]
    C --> D["Linker (linker)"]
    D --> E["Executável (exe/app)"]

    subgraph StdLib["Biblioteca padrão + runtime"]
      S1["Headers: <vector>, <string>, ..."]
      S2["Parte binária: libstdc++/libc++/MS STL"]
    end

    S1 --> B
    S2 --> D

Os headers da biblioteca padrão participam da compilação (porque contêm declarações). A parte binária da biblioteca padrão participa da linkagem (porque contém os “pedaços prontos” que precisam ser ligados ao seu programa). Essa é uma das razões pelas quais o termo toolchain é mais amplo que compiler.

Famílias de toolchain: GCC, Clang/LLVM e MSVC

Um erro comum de iniciantes é pensar que “C++23” garante comportamento idêntico em todos os compiladores. O padrão define regras da linguagem e da biblioteca, mas o mundo real é feito de implementações — cada uma com seu ritmo de adoção de features, suas mensagens de warning, e suas particularidades. Isso é normal: o padrão é um só, as implementações são várias.

Na prática você vai encontrar três famílias com frequência:

GCC (GNU Compiler Collection) — historicamente muito popular no universo Linux. Para C++ costuma-se usar o driver g++.

Clang/LLVM — infraestrutura de compilação moderna. Clang é apreciado por mensagens de erro amigáveis, boas diagnósticas e fácil integração. Para C++ costuma-se usar clang++.

MSVC (Microsoft Visual C++) — compilador do Visual Studio. Seu “rosto” em console é cl.exe, e o linker costuma ser link.exe. No Windows essa é uma ecossistema muito comum.

Importante: os três compilam C++, mas cada um tem suas flags, valores de alguns macros, e ocasionalmente diferentes níveis de suporte a partes do C++23 (especialmente na biblioteca).

2. Modo da linguagem e como verificar o padrão

Driver g++/clang++ e por que isso importa

Quando você executa g++ ou clang++, pense neles como “maestros”. Eles não fazem tudo com um único binário necessariamente — normalmente invocam várias etapas: compilação, montagem e linkagem. Mas o principal: o driver para C++ por padrão acerta o ambiente C++, incluindo a biblioteca padrão na etapa de link.

Isso explica por que em C++ costuma-se usar g++/clang++ em vez de gcc/clang. Formalmente, gcc pode compilar arquivos C++, mas o conforto típico de ter a biblioteca e o runtime C++ ligados automaticamente costuma vir ao usar g++.

No mundo MSVC o papel de “driver” é feito por cl.exe, mas o estilo de flags e o formato do ambiente são historicamente diferentes (especialmente porque o Windows tende a querer tudo em lugares específicos e ser executado a partir do “Developer Command Prompt”).

Por que C++23 é uma flag de build, não uma propriedade do arquivo

É tentador acreditar que o arquivo main.cpp “por si só” é C++23. Infelizmente: um arquivo é só texto, e as regras que dizem como interpretar esse texto são decididas pelo compilador via flags (modo).

Ou seja, o “modo da linguagem” faz parte do comando de build. Isso importa por duas razões.

A primeira é pragmática: diferentes ambientes podem ter padrões “default” distintos. Em algum lugar o padrão padrão pode ser C++14, em outro C++17, e em outro “algum C++2a”. Se você não fixa o padrão, está se preparando para o bug futuro “funciona aqui, não funciona ali”.

A segunda é um pouco filosófica: o padrão não é só sintaxe — é também disponibilidade de partes da biblioteca padrão, conjunto de feature-test macros, e às vezes até o comportamento de certos headers. Então a disciplina é simples: declare sempre explicitamente o padrão na build se você quer reprodutibilidade.

__cplusplus: como descobrir com qual padrão você realmente compilou

Entre os macros pré-definidos em C++ existe um que mostra “o modo da linguagem”: __cplusplus. Ele não existe por decoração, mas como um farol diagnóstico — “qual versão do padrão está ativa”.

Historicamente o valor de __cplusplus mudou com as versões do padrão. O importante não é decorar números, e sim entender: este é o mecanismo oficial que deve refletir o modo selecionado.

Mini-demo: imprimindo __cplusplus

Preparação simples: antes de discutir “isso é C++23 mesmo”, pergunte ao compilador.

#include <iostream>

int main() {
    std::cout << "__cplusplus = " << __cplusplus << '\n';
    return 0;
}
// exemplo de saída (o valor depende do modo de compilação):
// __cplusplus = 202302

O número impresso é conveniente de entender como “ano+mes do padrão no formato YYYYMM”, mas não é necessário decorar — basta saber o princípio: se você muda o padrão, esse número deve mudar.

Observação importante sobre MSVC

O MSVC tem uma peculiaridade histórica: o valor de __cplusplus por muito tempo não refletiu o modo real do padrão sem configuração adicional (normalmente uma flag de compatibilidade). Então no Windows você pode ver “features quase como C++20”, enquanto __cplusplus parece “bem antigo”. Isso não é loucura — é um compromisso histórico de compatibilidade.

Conclusão prática: no MSVC para diagnosticar o modo costuma-se usar não só __cplusplus, mas também _MSC_VER e as flags corretas do compilador.

5. Suporte a C++23: linguagem, biblioteca e checagens

Dizer “meu compilador suporta C++23” soa simples, mas na prática isso se divide em duas perguntas.

Primeira: o compilador suporta as features da linguagem? Isso é sobre sintaxe e semântica: quais palavras-chave existem, quais regras aplicam.

Segunda: a biblioteca padrão implementa as features de C++23? Isso é sobre se em <print> existem as funções esperadas, se o conjunto de algoritmos está implementado, etc.

Aí começa o interessante: às vezes o compilador “sabe a linguagem”, mas a biblioteca ainda não implementou parte das features. Ou o contrário: a biblioteca tem algo, mas a configuração não ativa os macros/checagens corretas.

Feature-test macros: checagem “à prova de adulto”

No padrão C++ (e nas bibliotecas) existe a ideia de feature-test macros: macros __cpp_* e __cpp_lib_* que permitem saber se uma feature está disponível.

Não vamos catalogar todos os macros aqui, mas mostramos um truque prático: incluir <version> e verificar se um macro de biblioteca está definido.

#include <iostream>
#include <version>

int main() {
#ifdef __cpp_lib_print
    std::cout << "std::print is available\n";   // std::print is available
#else
    std::cout << "std::print is NOT available\n"; // std::print is NOT available
#endif
}

A ideia: em vez de “confiar” apenas na flag -std=c++23, podemos perguntar com segurança ao ambiente: “essa funcionalidade da biblioteca está realmente disponível?”. Isso é especialmente útil quando você escreve código portátil ou estuda em máquinas diferentes.

Resumo prático de comandos de build

Ao ver comandos de build para diferentes toolchains parece que existem três linguagens diferentes de linha de comando. Na verdade a lógica é a mesma: escolher o modo da linguagem, passar arquivos de entrada, obter o resultado. Mudam as “palavras” que você usa.

Família Comando típico para C++ Flag do padrão Como normalmente definir o nome do resultado
GCC
g++
-std=c++23
-o app
Clang
clang++
-std=c++23
-o app
MSVC
cl.exe
/std:c++20
ou
/std:c++latest
(dependendo da versão)
/Fe:app.exe
(frequentemente), ou via configuração do linker

Esta tabela não existe para você decorar tudo agora. Ela serve para quebrar a barreira psicológica: “sim, as flags são diferentes, mas o objetivo é o mesmo”.

6. Exemplo prático: build info no Tasker

Vamos dar um pequeno, mas útil passo em nossa aplicação de console em evolução. Suponha um app simples chamado Tasker (mini-agendador), que já existe em alguns arquivos .cpp/.hpp e já consegue iniciar e imprimir ajuda. Vamos adicionar uma função que imprime informações de build: qual compilador, qual modo da linguagem, quais macros importantes.

Parece supérfluo enquanto tudo funciona. Mas assim que você compilar o mesmo projeto em toolchains diferentes, o build info vira seu melhor amigo: ele diz claramente em que as builds diferem.

Header build_info.hpp

Objetivo simples: no header mantemos a declaração para que main.cpp possa chamar a função sem conhecer a implementação.

#pragma once
#include <string>

namespace tasker {
    std::string BuildInfo();
}

Implementação build_info.cpp

Aqui o objetivo é montar uma string a partir de alguns macros e retorná-la. Observação: não usamos nada mais complexo que strings e diretivas #if.

#include "build_info.hpp"

namespace tasker {

std::string BuildInfo() {
#if defined(__clang__)
    return "compiler=clang, __cplusplus=" + std::to_string(__cplusplus);
#elif defined(__GNUC__)
    return "compiler=gcc, __cplusplus=" + std::to_string(__cplusplus);
#elif defined(_MSC_VER)
    return "compiler=msvc, __cplusplus=" + std::to_string(__cplusplus);
#else
    return "compiler=unknown, __cplusplus=" + std::to_string(__cplusplus);
#endif
}

} // namespace tasker

Se você pensou “onde está o #include de <string>?” — ele já está no header, e o .cpp o puxa via build_info.hpp. Essa é uma das razões para headers limpos: menos duplicação.

Uso em main.cpp

A ideia: ter um argumento --build-info que imprime o "passaporte" da build. Isso ajuda você (e o instrutor) a ver rapidamente em qual modo o binário foi compilado.

#include <iostream>
#include <string>
#include "build_info.hpp"

int main(int argc, char** argv) {
    if (argc >= 2 && std::string(argv[1]) == "--build-info") {
        std::cout << tasker::BuildInfo() << '\n';
        return 0;
    }
    std::cout << "Tasker: run with --build-info\n"; // Tasker: run with --build-info
}

Ainda não é um planejador de tarefas completo, mas é um hábito de engenharia importante: saber explicar o que seu binário realmente é.

7. Quando o toolchain “complica a cabeça” do iniciante

Primeira situação típica: “o código está ok, mas no computador do colega não compila”. Muitas vezes a causa não é o código, e sim que vocês têm padrões default diferentes ou bibliotecas diferentes. Se você fixa o padrão com uma flag e consegue imprimir o build info, reduz muito o escopo da busca.

Segunda situação: “parece que C++23 está ativado, mas o header/função que eu preciso não existe”. Isso costuma ser diferença entre “linguagem vs biblioteca”. O compilador pode ter o modo da linguagem, mas a biblioteca naquela distribuição pode não ter implementado a parte que você precisa. A solução é: não usar a feature, ou proteger com feature-test macro e fornecer um fallback.

8. Erros típicos

Erro #1: considerar C++23 como propriedade do fonte em vez de configuração de build.
Se você não fixa o padrão, cedo ou tarde vai enfrentar “funciona comigo, não funciona com você”. Não é mistério — é apenas modo de compilador diferente. O hábito de especificar explicitamente o padrão não é frescura, é proteção contra discrepâncias.

Erro #2: presumir que suporte a C++23 implica automaticamente todas as novidades da biblioteca.
Linguagem e biblioteca evoluem juntas, mas nem sempre sincronizadas. Resultado: o modo da linguagem pode estar ativo, mas certa feature da biblioteca pode faltar. A saída é checar os feature-test macros e tratar a biblioteca como parte separada do toolchain, não como “mágica dentro do compilador”.

Erro #3: tentar diagnosticar o modo da linguagem por inspeção visual, sem verificar __cplusplus.
__cplusplus existe para evitar adivinhação. Historicamente seu valor foi atualizado com os padrões, tornando-o um marcador útil. Se estiver em dúvida, imprima __cplusplus uma vez em vez de perder tempo discutindo.

Erro #4: esperar que o MSVC se comporte exatamente como GCC/Clang em flags e macros.
MSVC é uma ecossistema separada com história de compatibilidade, suas próprias flags e comportamento de alguns macros. Se você tentar portar comandos literalmente de GCC para MSVC, provavelmente vai ter erros estranhos. O caminho certo é entender qual toolchain você tem e falar com ele “na sua língua”.

Erro #5: confundir compilador com biblioteca padrão ao dizer “eu uso GCC”.
Na prática importa não apenas o compilador, mas também qual biblioteca padrão está sendo usada (e qual linker). Então “eu uso GCC” é útil, mas incompleto. Muito melhor é poder imprimir o build info para ver o modo da linguagem e a família do compilador direto do seu programa.

1
Tarefa
C++ SELF, nível 33, lição 0
Bloqueado
Passaporte do padrão
Passaporte do padrão
1
Tarefa
C++ SELF, nível 33, lição 0
Bloqueado
Família do compilador
Família do compilador
1
Tarefa
C++ SELF, nível 33, lição 0
Bloqueado
Verificação de recurso
Verificação de recurso
1
Tarefa
C++ SELF, nível 33, lição 0
Bloqueado
Tela de compilação
Tela de compilação
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION