Encontrei um bug interessante hoje ao compilar um projeto C++. Um enum que deveria retornar o valor 2 estava retornando 0.
enum ENUM {A, B, C};
ENUM c = ENUM::C;
int x = static_cast<int>(c);
// Esperado: x = 2
// Resultado: x = 0
A variável c foi explicitamente inicializada com ENUM::C, mas o cast para int retornava 0 (valor de A) ao invés de 2.
A Solução Temporária
Modificando a declaração para especificar o tipo subjacente, o problema desapareceu:
enum ENUM : int {A, B, C};
ENUM c = ENUM::C;
int x = static_cast<int>(c);
// Agora: x = 2 (correto)
Mas por que isso fez diferença?
A Causa Real
O verdadeiro culpado era a mistura de flags de compilação. Eu estava linkando parte do projeto com /MT (runtime estática) enquanto o restante usava /MD (runtime dinâmica).
Quando você mistura /MT e /MD em diferentes partes do mesmo projeto, acaba com múltiplas cópias da runtime do C++. Cada runtime pode ter decisões diferentes sobre layout de memória, alinhamento e escolha de tipos subjacentes para enums.
O cenário típico:
Módulo A (compilado com /MD):
- Define e inicializa o enum
- Escolhe unsigned char como tipo subjacente
- Escreve C = 2 na memória
Módulo B (compilado com /MT):
- Lê o mesmo enum
- Assume int como tipo subjacente
- Interpreta os bytes de forma diferente
- Resultado: lê como A = 0
Por Que Especificar o Tipo Funciona
Ao declarar explicitamente o tipo subjacente do enum:
enum ENUM : int {A, B, C};
Você força ambas as runtimes a usarem a mesma representação binária, eliminando a ambiguidade. Sem especificar o tipo, o compilador escolhe automaticamente (pode ser int, unsigned int, char, etc.), e diferentes módulos podem fazer escolhas diferentes.
A Solução Correta
Especificar o tipo do enum resolve o sintoma, mas não a causa. A solução correta é nunca misturar /MT e /MD no mesmo projeto.
No Visual Studio, configure a runtime library:
- Propriedades do projeto
- C/C++ → Geração de Código → Biblioteca de Runtime
- Release: Multi-threaded DLL (/MD)
- Debug: Multi-threaded Debug DLL (/MDd)
- Aplique para todos os projetos da solução
Depois, faça um rebuild completo:
Limpar Solução → Recompilar Tudo
Outros Problemas da Mistura de Runtimes
O bug de enum é apenas um sintoma. Misturar /MT e /MD pode causar problemas mais graves:
- Corrupção de heap ao alocar memória em uma runtime e liberar em outra
- Crashes aleatórios difíceis de debugar
- Memory leaks silenciosos
- Comportamento indefinido com variáveis estáticas e singletons
- Violações de acesso aparentemente aleatórias
Boas Práticas
Mesmo após corrigir o problema de runtime, é recomendado especificar o tipo subjacente dos enums:
// Recomendado
enum Status : int { OK, Error, Warning };
enum class Color : uint8_t { Red, Green, Blue };
// Evite
enum Status { OK, Error, Warning };
Especificar o tipo garante tamanho previsível, melhor compatibilidade binária e previne bugs sutis de layout.
C++17 e Direct-List-Initialization
C++17 introduziu uma melhoria para inicialização de enums:
enum class Color : uint32_t { Red, Green, Blue };
// C++17: permite inicialização direta com chaves
Color c{2};
// Ainda requer cast explícito para conversões implícitas
Color c = 2; // Erro
int x = Color::Red; // Erro
Conclusão
Um valor de enum aparentemente simples retornando incorretamente revelou um problema de configuração de build. Bugs desse tipo podem passar despercebidos em testes e aparecer apenas em produção, causando problemas intermitentes difíceis de rastrear.
A lição: sempre verifique as configurações de compilação e mantenha consistência nas flags de runtime em todo o projeto.