C# e C++ compartilham uma letra e uma sintaxe com chaves, mas ficam em níveis diferentes. C++ (1985) compila diretamente para código de máquina e permite controlar exatamente onde cada objeto vive e quando ele morre. C# (2002) compila para uma linguagem intermediária que o runtime do .NET transforma em código de máquina em tempo de execução, e um garbage collector recupera a memória para você. Quase todas as diferenças abaixo vêm dessa única escolha.
Visão geral
| C# | C++ | |
|---|---|---|
| Execução | Gerenciada: IL, compilada com JIT pelo CLR | Nativa: compilada antecipadamente para código de máquina |
| Memória | Garbage collector | Manual, RAII, smart pointers |
| Ponteiros | Referências; ponteiros brutos só em código unsafe | Ponteiros brutos e referências em todo lugar |
| Segurança | Arrays com verificação de limites, sem referências pendentes | Comportamento indefinido fora dos limites, uso depois de liberar |
| Modelo de build | Projetos e assemblies, sem cabeçalhos | Cabeçalhos, pré-processador, unidades de tradução, linker |
| Genéricos | Genéricos, verificados uma vez, resolvidos em tempo de execução | Templates, instanciados em tempo de compilação |
| Biblioteca padrão | Grande: coleções, HTTP, JSON, arquivos, threads | Menor: containers, algoritmos, threads |
| Velocidade de compilação | Rápida | Lenta em bases de código grandes |
| Jogos | Unity, Godot | Unreal, a maioria dos motores AAA próprios |
| Outros usos principais | Backends web, desktop, nuvem, ferramentas | Motores, navegadores, sistemas operacionais, embarcados, trading |
Memória: garbage collector vs RAII
Em C++, um objeto com armazenamento automático é destruído quando sai de escopo, e o destrutor executa nesse exato momento. Objetos no heap pertencem a smart pointers (std::unique_ptr, std::shared_ptr) ou são gerenciados à mão com new e delete. Esse padrão, Resource Acquisition Is Initialization (RAII), dá uma limpeza determinística da memória e de todos os outros recursos.
// C++
#include <memory>
#include <fstream>
void save() {
std::ofstream file("log.txt"); // opened here
auto buffer = std::make_unique<char[]>(4096);
file << "saved\n";
} // file closed and buffer freed here, in reverse order
Em C#, toda instância de classe vive no heap gerenciado e o garbage collector a libera em algum momento posterior, quando nada mais a referencia. Você nunca escreve delete e nunca consegue liberar algo que ainda está em uso. A memória está resolvida; o que o GC não trata na hora são os outros recursos: arquivos, sockets, conexões de banco de dados. Para esses, C# tem IDisposable e a instrução using, que chama Dispose no fim de um bloco, o mais perto de um destrutor:
Saída:
open db
open cache
db <- SELECT 1
cache <- PING
close cache
close db
done
A diferença é que em C++ a limpeza está ligada ao escopo para todo objeto local e todo objeto que pertence a um smart pointer, enquanto em C# ela é automática para a memória e opcional (via using) para todo o resto. Mais sobre isso na página da instrução using.
Segurança: exceções em vez de comportamento indefinido
Ler além do fim de um array em C++ é comportamento indefinido: o programa pode imprimir lixo, quebrar ou continuar executando com a memória corrompida, e o resultado pode mudar entre builds. O mesmo erro em C# lança uma exceção na linha exata.
// C++: compiles, and the behavior is undefined
int scores[3] = {90, 85, 77};
int x = scores[5]; // reads whatever is in memory there
Saída:
Index 5 is outside an array of length 3
name was null
Toda a classe de bugs de corrupção de memória (buffer overflows, uso depois de liberar, liberação dupla, ponteiros pendentes) não existe em C# seguro. Isso explica boa parte de por que código C# é mais rápido de escrever e de revisar.
Ponteiros e código unsafe
C# tem ponteiros, mas só dentro de blocos unsafe, e o projeto precisa habilitá-los com <AllowUnsafeBlocks>true</AllowUnsafeBlocks>. Objetos no heap gerenciado podem se mover durante a coleta de lixo, então você os fixa com fixed antes de pegar o endereço:
// C#, requires AllowUnsafeBlocks
unsafe
{
int[] data = { 1, 2, 3 };
fixed (int* p = data)
{
*(p + 1) = 20; // data is now { 1, 20, 3 }
}
}
Código unsafe é usado para interoperabilidade com bibliotecas nativas e em alguns laços críticos. A maior parte do C# de baixo nível hoje usa Span<T>, variáveis locais ref e stackalloc, que dão desempenho parecido com o de ponteiros mantendo a verificação de limites.
Desempenho
C++ entrega ao compilador o programa inteiro antecipadamente e não acrescenta nada em tempo de execução: sem garbage collector, sem JIT, sem verificação de limites a menos que você peça. Isso o torna a escolha onde cada microssegundo ou cada byte conta, e onde pausas são inaceitáveis.
C# paga pela segurança com um runtime, o aquecimento do JIT na inicialização e pausas ocasionais do GC. Em throughput, ele normalmente fica a um fator pequeno do C++, e a distância diminuiu a cada versão do .NET: JIT em camadas com otimização guiada por perfil, structs e Span<T> para evitar alocações, intrínsecos de hardware para SIMD e Native AOT para compilar antecipadamente em um único executável nativo. Em APIs web, ferramentas e lógica de negócio, o banco de dados e a rede dominam e a diferença entre as linguagens raramente aparece.
Desenvolvimento de jogos: Unity vs Unreal
É aqui que a maioria das pessoas encontra a pergunta. A Unity é programada em C#: código de gameplay, interface e ferramentas são classes C# ligadas aos objetos do jogo, enquanto o núcleo do motor é em C++. A Unreal Engine é escrita em C++, e o gameplay é C++ mais o sistema de script visual Blueprints. O Godot suporta GDScript e C#.
C# com Unity é mais rápido de aprender e de iterar, e é por isso que é tão comum em jogos indie e mobile. Unreal e C++ são o padrão em estúdios AAA, e programadores de motores em todo lugar trabalham em C++. Um caminho comum é começar na Unity e aprender C++ se você for para a Unreal ou para trabalho de motor.
Modelo de build
Um programa C++ é dividido em cabeçalhos (.h, declarações) e arquivos-fonte (.cpp, definições). O pré-processador cola os cabeçalhos em cada arquivo-fonte, cada arquivo é compilado separadamente e o linker junta os resultados. Os templates são instanciados em cada arquivo que os usa, um dos motivos de builds grandes em C++ serem lentos.
C# não tem nada disso. Um projeto é um conjunto de arquivos .cs compilados juntos em um assembly (.dll); a ordem das declarações e dos arquivos não importa, e um tipo em um arquivo pode usar um tipo em outro sem include. Bibliotecas vêm como pacotes NuGet. A diretiva using importa um namespace, não um arquivo.
Diferenças de sintaxe que você vai notar
- Objetos.
auto p = std::make_unique<Player>();ep->Jump();em C++;var p = new Player();ep.Jump();em C#. C# usa.para tudo. - Strings.
std::stringé um valor mutável; astringde C# é um tipo de referência imutável. - Herança múltipla. C++ permite que uma classe herde de várias classes; C# permite uma classe base mais qualquer número de interfaces.
- Templates vs genéricos. Templates de C++ são geração de código em tempo de compilação e permitem metaprogramação; genéricos de C# são verificados uma vez, com restrições explícitas (
where T : IComparable<T>). - Biblioteca padrão. A de C# inclui HTTP, JSON, expressões regulares, I/O de arquivos, compressão e criptografia; em C++ muito disso vem de bibliotecas de terceiros.
Qual aprender
Aprenda C# se você quer construir aplicações, backends web, ferramentas ou jogos na Unity e ver resultados rápido. Aprenda C++ se o seu alvo são motores de jogos, gráficos, sistemas embarcados, sistemas operacionais, navegadores ou qualquer coisa em que o controle no nível do hardware e a latência previsível são o objetivo. C# é a primeira linguagem mais tranquila; C++ vale a pena como segunda, porque ensina o que o runtime de C# está fazendo por você.
Perguntas frequentes
Qual é a principal diferença entre C# e C++?
C# é gerenciada: compila para uma linguagem intermediária que o runtime do .NET compila com JIT, e um garbage collector libera a memória. C++ compila direto para código de máquina, e você controla quando os objetos são criados e destruídos. Essa troca dá ao C++ mais controle e previsibilidade, e ao C# mais segurança e desenvolvimento mais rápido.
C# é mais fácil que C++?
Sim, para a maioria das pessoas. C# não tem gerenciamento manual de memória, nem arquivos de cabeçalho, nem comportamento indefinido em código seguro, e tem erros de compilação mais claros. C++ é uma linguagem maior, com mais formas de cometer erros sutis, como ponteiros pendentes, buffer overflows e uso de memória depois de liberada, que o compilador não pega.
C++ é mais rápido que C#?
C++ bem escrito normalmente é mais rápido e, mais importante, mais previsível, porque não há pausa de garbage collector nem aquecimento do JIT. O C# moderno diminui a distância com structs, Span<T>, SIMD e compilação antecipada, e em aplicações de negócio a diferença raramente é o gargalo. Para motores, drivers e trading de alta frequência, C++ continua sendo o padrão.
Devo aprender C# ou C++ para desenvolvimento de jogos?
Nos seus primeiros jogos, C# com Unity (ou Godot) permite construir e publicar mais rápido. A Unreal Engine usa C++ (mais Blueprints), e estúdios AAA que escrevem código de motor contratam programadores C++. Muitos desenvolvedores começam com Unity e C# e aprendem C++ quando precisam de trabalho no nível do motor.
C# tem ponteiros?
Sim, dentro de blocos de código unsafe, que precisam ser habilitados com a configuração de projeto AllowUnsafeBlocks. O C# comum usa referências, que o garbage collector acompanha e que não podem apontar para memória liberada. ref, Span<T> e stackalloc cobrem a maioria dos casos em que você recorreria a um ponteiro.