Aprenda seu depurador
É realmente útil entender o depurador, seja ele com base em texto, IDE completo ou alguma mistura dos mesmos. Como você não fornece muitos detalhes, descreverei o caso geral:
1) Pontos de interrupção
Além de apenas parar em uma linha de código, muitos depuradores permitem especificar a quebra quando uma condição surgir (por exemplo, "x> 5"), após várias passagens pelo código ou quando alguma memória mudar de valor. Isso é muito útil para entender como seu código entra em um estado ruim, por exemplo, observar quando um ponteiro se torna nulo em vez de detectar a falha quando é desreferenciado.
2) Percorrendo o código
Você pode entrar em funções, linha por linha ao longo do código, pular linhas ('definir próxima instrução') e depois 'subir' as funções. É uma maneira realmente poderosa de seguir a execução do código para verificar se ele faz o que você pensa que faz :-)
3) Avaliando expressões
Assim, você pode colocar variáveis em uma lista de observação / janela e ver o valor dos mesmos mudar quando você atinge um ponto de interrupção ou percorre o código, mas geralmente também é possível fazer avaliações de expressões complexas, por exemplo, "x + y / 5" será avaliado. Alguns depuradores também permitem que você faça chamadas de função nas listas de observação. Você pode fazer coisas como "time ()", "MyFunction (...)", "time ()" e obter o tempo de duração de sua função.
4) Exceções e manipulação de sinal
Portanto, se o seu idioma suportar exceções e / ou sinais, você poderá configurar o depurador para saber como reagir a isso. Alguns depuradores permitem que você decifre o código no ponto em que a exceção está prestes a ocorrer, em vez de depois que ela não foi capturada. Isso é útil para rastrear problemas estranhos, como os erros "Arquivo não encontrado", porque o programa está sendo executado como uma conta de usuário diferente.
5) Anexando a um processo / núcleo
Às vezes, você precisa usar o depurador para saltar para um processo existente que está dando errado. Se você tiver um código-fonte por perto e os símbolos de depuração estiverem intactos, você poderá mergulhar como se tivesse iniciado no depurador em primeiro lugar. Isso também é semelhante para os core dumps, exceto que você geralmente não pode continuar a depuração nesses (o processo já terminou).
Configuração de compilação
Há várias variações de compilação que você pode fazer ativando ou desativando recursos como símbolos de depuração, otimizações e outros sinalizadores do compilador:
1) Depuração
Tradicionalmente, essa é uma construção simples, sem características especiais, o que facilita a depuração e a previsibilidade. Isso varia um pouco de plataforma, mas pode haver algum espaço extra, por exemplo, alocações e tamanhos de buffer, a fim de garantir a confiabilidade. Geralmente, um símbolo do compilador como DEBUG ou Condicional ("Debug") estará presente para que o código específico da depuração seja recebido. Geralmente, essa compilação é enviada com os símbolos de nível de função intactos, especialmente se a confiabilidade e / ou a repetibilidade são uma preocupação.
2) Versão / versão otimizada
A ativação de otimizações do compilador permite que alguns recursos de geração de código de baixo nível no compilador criem código mais rápido ou menor com base em suposições sobre seu código. Os aumentos de velocidade possíveis são irrelevantes se a escolha do algoritmo for ruim, mas para cálculos intensivos isso pode fazer a diferença suficiente através da eliminação de subexpressão comum e desenrolamento de loop, etc. Às vezes, as suposições feitas pelo otimizador são incompatíveis com seu código e, portanto, o otimizador tem que ser dobrado um entalhe. Os erros do compilador no código otimizado também foram um problema no passado.
3) Construção instrumentada / com perfil
Seu código é construído com código de instrumentação específico para medir o número de vezes que uma função é chamada e quanto tempo é gasto nessa função. Esse código gerado pelo compilador é gravado no final do processo de análise. Às vezes, é mais fácil usar uma ferramenta de software especializada para isso - veja abaixo. Esse tipo de construção nunca é enviado.
4) Construção segura / verificada
Todas as 'válvulas de segurança' são ativadas através de símbolos do pré-processador ou configurações do compilador. Por exemplo, macros ASSERT verificam parâmetros de função, iteradores verificam coleções não modificadas, canários são colocados na pilha para detectar corrupção, alocações de heap são preenchidas com valores sentinela (0xdeadbeef é memorável) para detectar corrupção de heap. Para clientes que têm problemas persistentes que só podem ser reproduzidos em seus sites, isso é uma coisa útil.
5) Compilação de recursos
Se você tem clientes diferentes com requisitos diferentes para o seu produto de software, é comum criar uma compilação para cada cliente que exercita as diferentes partes durante o teste. Por exemplo, um cliente deseja a funcionalidade offline e outro deseja apenas online. É importante testar nos dois sentidos se o código for criado de maneira diferente.
Log e rastreamento
Portanto, escrevemos algumas declarações úteis para printf () e escrevemos informações abrangentes e estruturadas sobre os traços nos arquivos de dados. Essas informações podem ser extraídas para entender o comportamento / características do tempo de execução do seu software. Se o seu código não falha, ou leva algum tempo para ser reproduzido, é útil ter uma imagem de, por exemplo, lista de threads, suas transições de estado, alocações de memória, tamanhos de pool, memória livre, número de identificadores de arquivo, etc. depende realmente do tamanho, da complexidade e dos requisitos de desempenho do seu aplicativo, mas como exemplo, os desenvolvedores de jogos desejam garantir que não haja 'picos' no uso da CPU ou da memória enquanto um jogo estiver em andamento, pois isso provavelmente afetará a taxa de quadros. Algumas dessas informações são mantidas pelo sistema, algumas pelas bibliotecas e o restante pelo código.
Outras ferramentas
Nem sempre é necessário criar uma compilação diferente para cobrir esses cenários: alguns aspectos podem ser escolhidos em tempo de execução através da configuração do processo (truques do Registro do Windows), disponibilizando bibliotecas alternativas com maior prioridade para as bibliotecas padrão, por exemplo, efence no seu caminho do carregador ou usando um ICE de software ou depurador especializado para analisar o seu software quanto às características de tempo de execução (por exemplo, Intel v-Tune). Alguns deles custam muito dinheiro, outros são ferramentas gratuitas para o Xcode do dtrace.