No artigo Do Comando Único ao Projeto Real — Construindo com CMake estruturamos projetos com CMake. Mas um projeto que compila e roda pode estar silenciosamente errado — cheio de bugs que não aparecem na compilação nem nos testes casuais, e que explodem em produção. Ao longo do curso, alertei sobre muitos desses perigos: acessos a memória fora dos limites (Fase 4), referências e ponteiros pendentes (Fases 1, 2, 4), vazamentos por gestão manual (Fase 2), corridas de dados (Fase 7). A boa notícia que fecha o ciclo é: existem ferramentas que detectam automaticamente esses erros, apontando o arquivo e a linha exatos. Os sanitizers instrumentam seu programa para flagrar erros de memória e concorrência em tempo de execução; o clang-tidy analisa seu código-fonte em busca de problemas e maus hábitos. Hoje você ganha essas redes de segurança — e elas transformam "parece funcionar" em "sei que está correto".
AddressSanitizer: caçando erros de memória
O AddressSanitizer (ASan) é talvez a ferramenta mais valiosa da aula. Ele instrumenta o programa para detectar, em tempo de execução, uma lista de erros de memória que de outra forma seriam comportamento indefinido silencioso: acesso fora dos limites de um array, uso de memória após liberação (use-after-free), vazamentos, e mais. Ativá-lo é uma flag de compilação. Veja um bug clássico que compila sem erro e roda "às vezes":
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {1, 2, 3};
// BUG: acesso fora dos limites. v[3] não existe (índices válidos: 0,1,2).
std::cout << v[3] << '\n'; // comportamento indefinido — pode imprimir lixo ou travar
return 0;
}
Compilado normalmente, isso pode imprimir lixo e seguir em frente, escondendo o bug. Compilado com o AddressSanitizer, ele para no exato ponto do erro, com um relatório detalhado:
# Ative o ASan com -fsanitize=address (e -g para nomes/linhas no relatório):
g++ -std=c++20 -fsanitize=address -g programa.cpp -o programa
./programa
# ASan imprime: "heap-buffer-overflow ... READ of size 4 ... programa.cpp:7"
# apontando o arquivo e a LINHA exata do acesso inválido.
Em vez de um bug fantasma que se manifesta aleatoriamente, você recebe uma mensagem precisa: "leitura inválida na linha 7". O ASan torna visíveis exatamente os erros que o C++ deixa como comportamento indefinido — os mais difíceis de depurar sem ele. O custo: o programa roda mais devagar (tipicamente 2x) e usa mais memória, então o ASan é para desenvolvimento e testes, não para produção. Mas rodar seus testes sob ASan pega uma classe enorme de bugs antes que cheguem ao usuário.
ThreadSanitizer: caçando corridas de dados
Lembra da corrida de dados da Fase 7 — o contador que dava resultados errados e imprevisíveis? Corridas de dados são especialmente traiçoeiras porque são não-determinísticas: podem não aparecer em mil execuções de teste. O ThreadSanitizer (TSan) foi feito para caçá-las, detectando acessos concorrentes não sincronizados mesmo que a corrida não chegue a causar erro visível naquela execução:
#include <thread>
#include <iostream>
int main() {
int contador = 0; // compartilhado, sem sincronização
std::thread a([&]{ for (int i = 0; i < 10000; ++i) ++contador; });
std::thread b([&]{ for (int i = 0; i < 10000; ++i) ++contador; });
a.join();
b.join();
std::cout << contador << '\n';
return 0;
}
Compilado com -fsanitize=thread, o TSan detecta que contador é acessado por duas threads sem proteção e reporta a corrida, apontando as duas linhas conflitantes — mesmo que naquela execução o número final parecesse plausível:
g++ -std=c++20 -fsanitize=thread -g programa.cpp -o programa
./programa
# TSan imprime: "WARNING: data race ... write ... previous write ..."
# identificando as threads e as linhas do acesso não sincronizado.
Isso é ouro para código concorrente. Um bug que só apareceria uma vez a cada milhão de execuções em produção é flagrado no desenvolvimento. (Nota: ASan e TSan não podem ser usados juntos na mesma compilação — você roda seus testes uma vez sob cada um.)
clang-tidy: análise estática do código-fonte
Enquanto os sanitizers analisam o programa rodando, o clang-tidy analisa o código-fonte sem executá-lo — é um analisador estático. Ele detecta padrões suspeitos, possíveis bugs e violações de boas práticas, muitas delas exatamente as que discutimos no curso: usar new/delete cru onde um ponteiro inteligente caberia, funções que deveriam ser const, conversões perigosas, e centenas de outras. Ele até sugere correções automáticas para muitos casos:
# Analisa um arquivo, verificando várias categorias de checagem:
clang-tidy programa.cpp -- -std=c++20
# Exemplo de saída:
# warning: use 'auto' when initializing with a cast [modernize-use-auto]
# warning: variable 'x' is not initialized [cppcoreguidelines-init-variables]
# warning: prefer 'make_unique' to 'new' [modernize-make-unique]
Repare que os nomes das checagens (cppcoreguidelines-*, modernize-*) remetem diretamente às práticas que ensinamos — o clang-tidy é, em boa medida, as C++ Core Guidelines e o C++ moderno codificados numa ferramenta automática. Rodá-lo sobre seu código é como ter um revisor experiente apontando onde você poderia aplicar melhor o que aprendeu. Integrá-lo ao CMake (via a opção CMAKE_CXX_CLANG_TIDY) faz a análise rodar a cada compilação.
A honestidade: ferramentas ajudam, não substituem o julgamento
Prometo sempre a medida certa. Essas ferramentas são transformadoras, mas não são mágicas, e vender isso seria desonesto. Os sanitizers só pegam bugs nos caminhos que seus testes efetivamente exercitam — se um teste nunca aciona a corrida de dados, o TSan não a vê; por isso eles se combinam com boa cobertura de testes (próximo artigo). O clang-tidy gera, às vezes, falsos positivos ou avisos irrelevantes ao seu contexto, e configurá-lo (escolher quais checagens ligar) faz parte do uso maduro. E nenhuma ferramenta substitui entender por que algo é um bug — o conhecimento que você construiu ao longo do curso é o que dá sentido aos relatórios delas. A postura correta: rode ASan e TSan nos seus testes rotineiramente, use clang-tidy como revisor automático, mas trate os resultados com julgamento, não como oráculos. Ferramentas amplificam um bom programador; não criam um.
Sanitizer e análise estática não competem, olham momentos diferentes: o ASan observa o programa rodando e aponta o acesso inválido no instante em que acontece, enquanto o clang-tidy lê o código-fonte e aponta padrões suspeitos sem executar nada. Os sanitizers custam caro — memória e tempo de execução várias vezes maiores —, o que os torna ferramenta de desenvolvimento e de testes, não de produção. Ligá-los na integração contínua é onde rendem mais.
Fontes e leituras recomendadas
- clang.llvm.org/docs/AddressSanitizer.html e ThreadSanitizer.html: a documentação oficial dos sanitizers, com a lista de erros que cada um detecta.
- clang.llvm.org/extra/clang-tidy: a documentação do clang-tidy, com o catálogo completo de checagens (incluindo as
cppcoreguidelines-*). - "Sanitizers" (github.com/google/sanitizers/wiki): o wiki do projeto original, com detalhes técnicos e exemplos de uso.
- ISO C++ Core Guidelines, seção sobre ferramentas de suporte: como as guidelines se conectam a analisadores estáticos como o clang-tidy.
- Documentação do GCC e do Clang sobre
-fsanitize: as flags exatas e suas combinações, incluindo-fsanitize=undefined(UBSan) para comportamento indefinido.
Exercícios
Exercício 1
Escreva um pequeno programa com um acesso fora dos limites de um std::vector (como v[v.size()]), compile-o com -fsanitize=address -g, rode e descreva o relatório do ASan.
Ver resposta
✓ Resposta: Programa e relatório:
#include <vector>
#include <iostream>
int main() {
std::vector<int> v = {10, 20, 30};
std::cout << v[v.size()] << '\n'; // acesso ao índice 3, inválido
return 0;
}
g++ -std=c++20 -fsanitize=address -g bug.cpp -o bug
./bug
O ASan interrompe o programa e imprime um relatório do tipo heap-buffer-overflow, indicando que houve uma leitura (READ of size 4) além do fim do bloco alocado, apontando o arquivo e a linha do acesso (a linha do v[v.size()]), além de mostrar onde a memória foi alocada. Em vez de imprimir lixo e continuar, ele localiza o erro com precisão cirúrgica.
Exercício 2
Explique a diferença fundamental entre um sanitizer (como o ASan) e um analisador estático (como o clang-tidy) quanto a quando e como cada um detecta problemas.
Ver resposta
✓ Resposta: Um sanitizer como o ASan detecta problemas em tempo de execução: ele instrumenta o programa compilado e observa o comportamento real enquanto ele roda, pegando erros no momento em que ocorrem — mas só nos caminhos que a execução efetivamente percorre. Um analisador estático como o clang-tidy detecta problemas em tempo de análise do código-fonte, sem executar o programa: ele examina o texto do código procurando padrões suspeitos e violações de regras, cobrindo todo o código escrito (não só o executado), mas sem "ver" o comportamento real em tempo de execução. o sanitizer pega bugs reais que acontecem ao rodar (a um custo de execução e limitado aos caminhos testados); o analisador estático aponta problemas potenciais lendo o código (sem rodar, cobrindo tudo, mas sujeito a falsos positivos). São complementares.
Exercício 3
Compile o exemplo do contador com corrida de dados (dois threads, sem mutex) com -fsanitize=thread -g e descreva o que o TSan reporta. Depois adicione um mutex e confirme que o aviso desaparece.
Ver resposta
✓ Resposta: Com o TSan:
g++ -std=c++20 -fsanitize=thread -g contador.cpp -o contador
./contador
O TSan reporta um WARNING: data race, identificando que duas threads acessam contador sem sincronização — mostrando a escrita de uma thread e a escrita/leitura anterior conflitante da outra, com as linhas e pilhas de chamada de cada uma. Ao adicionar um std::mutex com std::lock_guard protegendo o ++contador (como no artigo O Cadeado que Protege — std::mutex e lock_guard), o acesso passa a ser sincronizado, e o TSan não encontra mais corrida — o aviso desaparece, confirmando que a proteção foi efetiva.
Exercício 4
Explique por que os sanitizers são adequados para desenvolvimento/testes mas não para produção. Cite o custo que eles impõem e por que isso é aceitável durante o desenvolvimento.
Ver resposta
✓ Resposta: Os sanitizers são adequados para desenvolvimento/testes mas não para produção porque impõem um custo significativo: o AddressSanitizer tipicamente torna o programa cerca de 2x mais lento e aumenta bastante o uso de memória (mantém metadados sobre cada região alocada); o ThreadSanitizer pode desacelerar ainda mais. Em produção, onde desempenho e uso de recursos importam para o usuário final, essa sobrecarga é inaceitável. Durante o desenvolvimento, porém, o custo é perfeitamente aceitável: a lentidão extra de rodar a suíte de testes sob sanitizer é um preço pequeno frente ao valor de flagrar, cedo, bugs de memória e concorrência que seriam catastróficos e quase impossíveis de depurar em produção. A prática é: compile builds instrumentados para testar, e builds normais (otimizados, sem sanitizer) para distribuir.
Exercício 5
Discuta a afirmação "ferramentas ajudam, não substituem o julgamento". Dê um exemplo de bug que o ASan pegaria facilmente, e outro de problema de design que nenhuma dessas ferramentas detectaria, exigindo o conhecimento do programador.
Ver resposta
✓ Resposta: A afirmação reconhece que as ferramentas detectam categorias específicas de erros mecânicos, mas não entendem a intenção nem o design do seu programa. Exemplo que o ASan pegaria facilmente: um use-after-free — usar um objeto através de um ponteiro depois que ele foi deletado; o ASan flagra o acesso à memória liberada com arquivo e linha exatos. Exemplo de problema que nenhuma dessas ferramentas detectaria: um erro de lógica ou de design — por exemplo, uma função calcula_juros que aplica a fórmula errada (juros simples onde deveria ser composto), ou uma hierarquia de classes que modela mal o domínio (herança onde deveria ser composição, como no artigo Uma Classe que Nasce de Outra — Herança e a Cadeia de Construção). O código compila, não tem erro de memória nem corrida, roda sem travar — e está conceitualmente errado. Nenhum sanitizer ou analisador estático sabe qual era a fórmula certa ou qual o design adequado; isso exige o entendimento do programador sobre o problema e sobre boas práticas. As ferramentas garantem que o código faça corretamente o que está escrito; só o julgamento humano garante que esteja escrito a coisa certa.