Reparando falhas de segmentação em C ++

95

Estou escrevendo um programa C ++ multiplataforma para Windows e Unix. No lado da janela, o código será compilado e executado sem problemas. No lado do Unix, ele irá compilar, mas quando tento executá-lo, recebo uma falha de segmentação. Meu palpite inicial é que há um problema com os ponteiros.

Quais são as boas metodologias para encontrar e corrigir erros de falha de segmentação?

Elpezmuerto
fonte

Respostas:

137
  1. Compile seu aplicativo com -g, então você terá símbolos de depuração no arquivo binário.

  2. Use gdbpara abrir o console gdb.

  3. Use filee transmita o arquivo binário do seu aplicativo no console.

  4. Use rune transmita quaisquer argumentos que seu aplicativo precise para iniciar.

  5. Faça algo para causar uma falha de segmentação .

  6. Digite btno gdbconsole para obter um rastreamento de pilha da falha de segmentação .

Svisstack
fonte
O que significa ter isso compilado gno contexto de CMake?
Schütze
3
Ative o tipo de compilação de depuração. Uma maneira é cmake -DCMAKE_BUILD_TYPE=Debug.
Antonin Décimo
36

Às vezes, a falha em si não é a verdadeira causa do problema - talvez a memória tenha sido destruída em um ponto anterior, mas demorou um pouco para que a corrupção se manifestasse. Verifique o valgrind , que tem muitas verificações para problemas de ponteiro (incluindo verificação de limites de array). Ele vai te dizer onde está o problema começa , não apenas a linha onde a falha ocorre.

paleozogt
fonte
19

Antes que o problema surja, tente evitá-lo tanto quanto possível:

  • Compile e execute seu código sempre que puder. Será mais fácil localizar a peça defeituosa.
  • Tente encapsular rotinas de baixo nível / propensas a erros para que você raramente tenha que trabalhar diretamente com a memória (preste atenção à modelização de seu programa)
  • Manter um conjunto de testes. Ter uma visão geral do que está funcionando no momento, o que não está mais funcionando etc, ajudará você a descobrir onde está o problema (o teste de reforço é uma solução possível, eu não o uso, mas a documentação pode ajudar a entender de que tipo de informações devem ser exibidas).

Use ferramentas apropriadas para depuração. No Unix:

  • O GDB pode dizer onde você travou o programa e permitirá que você veja em que contexto.
  • Valgrind o ajudará a detectar muitos erros relacionados à memória.
  • Com o GCC, você também pode usar mudflap Com GCC, Clang e, desde outubro, experimentalmente MSVC, você pode usar o Address / Memory Sanitizer . Ele pode detectar alguns erros que o Valgrind não detecta e a perda de desempenho é menor. É usado compilando com o -fsanitize=addresssinalizador.

Finalmente, recomendaria as coisas habituais. Quanto mais seu programa for legível, fácil de manter, claro e organizado, mais fácil será depurar.

log0
fonte
6

No Unix, você pode usar valgrindpara encontrar problemas. É grátis e poderoso. Se você preferir fazer você mesmo, pode sobrecarregar os operadores newe deletepara definir uma configuração em que tenha 1 byte 0xDEADBEEFantes e depois de cada novo objeto. Em seguida, controle o que acontece em cada iteração. Isso pode falhar em capturar tudo (não há garantia de que você toque nesses bytes), mas funcionou para mim no passado em uma plataforma Windows.

trigo
fonte
1
bem, isso seria 4 bytes em vez de 1 ... mas o princípio é bom.
Jonas Wagner de
1
Posso vincular ao meu depurador de heap não intrusivo ? :-)
fredoverflow
Vá em frente. Nosso objetivo é ajudar os outros aqui, então qualquer coisa que possa ajudar deve ser adicionada.
trigo
Embora sobrecarregue newe deletepossa ser super útil, o uso -fsanitize=addressé uma opção melhor, já que o compilador irá compilar na detecção de tempo de execução para problemas e irá despejar a memória automaticamente na tela, o que torna a depuração mais fácil.
Tarick Welling
Além de newe delete, você pode embrulhar mallocse estiver usando gcc. Veja --wrap=symbol. Vou fazer isso no código de lançamento para que possa obter alguns diagnósticos de tempo de execução.
Daisuke Aramaki
3

Sim, há um problema com os ponteiros. Muito provavelmente você está usando um que não foi inicializado corretamente, mas também é possível que esteja bagunçando o gerenciamento de memória com liberações duplas ou algo parecido.

Para evitar ponteiros não inicializados como variáveis ​​locais, tente declará-los o mais tarde possível, de preferência (e isso nem sempre é possível) quando eles podem ser inicializados com um valor significativo. Convença-se de que eles terão um valor antes de serem usados, examinando o código. Se você tiver dificuldade com isso, inicialize-as a uma constante ponteiro nulo (geralmente escrita como NULLou0 ) e verifique-os.

Para evitar ponteiros não inicializados como valores de membro, certifique-se de que eles sejam inicializados corretamente no construtor e manipulados corretamente nos construtores de cópia e operadores de atribuição. Não confie em uma initfunção para gerenciamento de memória, embora você possa fazer outra inicialização.

Se sua classe não precisa de construtores de cópia ou operadores de atribuição, você pode declará-los como funções-membro privadas e nunca defini-los. Isso causará um erro do compilador se eles forem explícita ou implicitamente usados.

Use ponteiros inteligentes quando aplicável. A grande vantagem aqui é que, se você segui-los e usá-los consistentemente, pode evitar completamente a escrita deletee nada será excluído duas vezes.

Use strings C ++ e classes de contêiner sempre que possível, em vez de strings e arrays no estilo C. Considere usar em .at(i)vez de [i], porque isso forçará a verificação dos limites. Veja se seu compilador ou biblioteca pode ser configurado para verificar os limites [i], pelo menos no modo de depuração. Falhas de segmentação podem ser causadas por saturações de buffer que gravam lixo sobre ponteiros perfeitamente bons.

Fazer essas coisas reduzirá consideravelmente a probabilidade de falhas de segmentação e outros problemas de memória. Eles sem dúvida falharão em consertar tudo, e é por isso que você deve usar valgrind de vez em quando quando não tiver problemas, e valgrind e gdb quando tiver.

David Thornley
fonte
1

Não conheço nenhuma metodologia a ser usada para consertar coisas como essa. Eu não acho que seria possível chegar a um, pois o problema em questão é que o comportamento do seu programa é indefinido (não sei de nenhum caso em que SEGFAULT não tenha sido causado por algum tipo de UB) .

Existem todos os tipos de "metodologias" para evitar o problema antes que ele surja. Um importante é RAII.

Além disso, você só precisa lançar suas melhores energias psíquicas nisso.

Edward Strange
fonte