Uma falha de segmentação ocorre quando um programa tenta acessar a memória fora da área que foi alocado para isso.
Nesse caso, um programador C experiente pode ver que o problema está acontecendo na linha em que sprintf
é chamado. Mas se você não souber onde a falha de segmentação está ocorrendo, ou se você não quer se incomodar em ler o código para tentar descobrir, então você pode construir seu programa com símbolos de depuração. (com gcc
, o -g
flag faz isso) e depois executá-lo através de um depurador.
Copiei seu código-fonte e o colei em um arquivo chamado slope.c
. Então eu construí assim:
gcc -Wall -g -o slope slope.c
(O -Wall
é opcional. É só para produzir avisos para mais situações. Isso pode ajudar a descobrir o que pode estar errado também.)
Em seguida, executei o programa no depurador gdb
executando primeiro gdb ./slope
para iniciar gdb
com o programa e, em seguida, uma vez no depurador, dando o comando run
ao depurador:
ek@Kip:~/source$ gdb ./slope
GNU gdb (GDB) 7.5-ubuntu
Copyright (C) 2012 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/ek/source/slope...done.
(gdb) run
Starting program: /home/ek/source/slope
warning: Cannot call inferior functions, you have broken Linux kernel i386 NX (non-executable pages) support!
Program received signal SIGSEGV, Segmentation fault.
0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
(Não se preocupe com minha mensagem you have broken Linux kernel i386 NX
... support
; ela não impede que gdb
seja usado efetivamente para depurar este programa.)
Essa informação é altamente enigmática ... e se você não tiver os símbolos de depuração instalados para a libc, você receberá uma mensagem ainda mais criptografada que tem um endereço hexadecimal em vez do nome simbólico da função _IO_default_xsputn
. Felizmente, isso não importa, porque o que realmente queremos saber é onde no seu programa o problema está acontecendo.
Portanto, a solução é olhar para trás, para ver quais chamadas de função ocorreram, levando a essa chamada de função específica em uma biblioteca do sistema, onde o sinal SIGSEGV
foi finalmente acionado.
gdb
(e qualquer depurador) tem esse recurso embutido: ele é chamado de rastreamento de pilha ou backtrace . Eu uso o comando bt
debugger para gerar um backtrace em gdb
:
(gdb) bt
#0 0x001a64cc in _IO_default_xsputn () from /lib/i386-linux-gnu/libc.so.6
#1 0x00178e04 in vfprintf () from /lib/i386-linux-gnu/libc.so.6
#2 0x0019b234 in vsprintf () from /lib/i386-linux-gnu/libc.so.6
#3 0x0017ff7b in sprintf () from /lib/i386-linux-gnu/libc.so.6
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
#5 0x08048578 in main () at slope.c:52
(gdb)
Você pode ver que sua função main
chama a função calc_slope
(que você pretendia) e, em seguida, calc_slope
chamadas sprintf
, que (neste sistema) foram implementadas com chamadas para alguns outras funções de biblioteca relacionadas.
Em geral, você está interessado em chamar a função no seu programa que chama uma função fora do seu programa . A menos que haja um bug na própria biblioteca / biblioteca que você está usando (neste caso, a biblioteca C padrão libc
fornecida pelo arquivo de biblioteca libc.so.6
), o bug que causa a falha está no seu programa e < em> frequentemente estará na ou perto da última chamada no seu programa.
Nesse caso, isso é:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
É aí que seu programa chama sprintf
. Sabemos disso porque sprintf
é o próximo passo. Mas mesmo sem afirmar isso, você sabe disso porque é o que acontece na linha 26 , e diz:
... at slope.c:26
No seu programa, a linha 26 contém:
sprintf(s,"%d",curr);
(Você deve sempre usar um editor de texto que mostre automaticamente números de linha, pelo menos para a linha em que você está atualmente. Isso é muito útil na interpretação de erros de tempo de compilação e problemas de tempo de execução revelados durante o uso de um depurador). / p>
Como discutido na resposta de Dennis Kaarsemaker , s
é uma matriz de um byte. (Não zero, porque o valor atribuído a você, ""
, é de um byte, ou seja, é igual a { '
, da mesma forma que "Hello, world!\n"
' }{ 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '
é igual a s
' }s
.)
Então, por que isso ainda funciona em alguma plataforma (e aparentemente quando compilado com o VC9 para Windows)?
As pessoas costumam dizer que quando você aloca memória e depois tenta acessar a memória fora dela, isso produz um erro. Mas isso não é verdade. De acordo com os padrões técnicos C e C ++, o que isso realmente produz é um comportamento indefinido.
Em outras palavras, tudo pode acontecer!
Ainda assim, algumas coisas são mais prováveis do que outras. Por que uma pequena matriz na pilha, em algumas implementações, parece funcionar como uma matriz maior na pilha?
Isso se resume a como a alocação de pilha é implementada, que pode variar de plataforma para plataforma. Seu executável pode alocar mais memória para sua pilha do que é realmente destinado a ser usado a qualquer momento. Às vezes, isso pode permitir que você grave em locais de memória que você não tenha explicitamente reivindicado em seu código. É muito provável que isso seja o que acontece quando você constrói seu programa no VC9.
No entanto, você não deve confiar nesse comportamento, mesmo no VC9. Ele pode depender de diferentes versões de bibliotecas que podem existir em diferentes sistemas Windows. Mas ainda mais provável é o problema que o espaço de pilha extra é alocado com a intenção de que ele seja realmente usado, e então ele pode realmente ser usado. Então você experimenta o pesadelo completo do "comportamento indefinido", onde, neste caso, mais de uma variável pode ser armazenada no mesmo lugar, onde escrever para um substitui o outro ... mas nem sempre, porque às vezes, as gravações em variáveis são armazenadas em cache em registradores e não executadas imediatamente (ou leituras para variáveis podem ser armazenadas em cache, ou uma variável pode ser considerada a mesma que era antes porque a memória alocada para ela é conhecida pelo compilador para não ter foi escrito através da própria variável).
E isso me leva à outra provável possibilidade de por que o programa funcionou quando construído com o VC9. É possível, e um pouco provável, que alguma matriz ou outra variável tenha sido alocada pelo seu programa (que pode incluir ser alocada por uma biblioteca que seu programa está usando) para usar o espaço após a matriz de um byte %código%. Então, tratar %code% como uma matriz com mais de um byte teria o efeito de acessar o conteúdo dessas / essas variáveis / matrizes, o que também poderia ser ruim.
Em conclusão, quando você tem um erro como este, é sorte receber um erro como "Falha de segmentação" ou "Falha de proteção geral". Quando você não tem isso, você pode não descobrir até que seja tarde demais para que seu programa tenha um comportamento indefinido.