Uma falha de segmentação ocorre quando um programa tenta acessar a memória fora da área que foi alocada para ele.
Nesse caso, um programador C experiente pode ver que o problema está acontecendo na linha em que sprintf
é chamado. Mas se você não pode dizer onde está ocorrendo sua falha de segmentação ou se não deseja ler o código para tentar descobrir, pode criar seu programa com símbolos de depuração (com gcc
, o -g
sinalizador faz isso ) e execute-o através de um depurador.
Copiei seu código-fonte e colei-o em um arquivo que eu nomeei slope.c
. Então eu construí assim:
gcc -Wall -g -o slope slope.c
(O -Wall
é opcional. É apenas para fazer com que ele produza avisos para mais situações. Isso pode ajudar a descobrir o que também pode estar errado.)
Em seguida, executei o programa no depurador gdb
executando primeiro gdb ./slope
para iniciar gdb
o programa e, uma vez no depurador, fornecendo o run
comando 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 a minha you have broken Linux kernel i386 NX
... support
mensagem; ela não impede que gdb
seja usada com eficácia para depurar este programa.)
Essa informação é altamente enigmática ... e se você não possui símbolos de depuração instalados no libc, receberá uma mensagem ainda mais enigmática que possui um endereço hexadecimal em vez do nome da função simbólica _IO_default_xsputn
. Felizmente, isso não importa, porque o que realmente queremos saber é onde o problema está acontecendo no seu programa .
Portanto, a solução é olhar para trás e ver quais chamadas de função ocorreram antes dessa chamada de função específica em uma biblioteca do sistema onde o SIGSEGV
sinal foi finalmente acionado.
gdb
(e qualquer depurador) tem esse recurso embutido: é chamado de rastreamento de pilha ou backtrace . Eu uso o bt
comando 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 main
função chama a calc_slope
função (que você pretendeu) e, em seguida , calc_slope
chama sprintf
, que é (neste sistema) implementada com chamadas para algumas outras funções relacionadas da biblioteca.
Em geral, o que você está interessado é a chamada de função no seu programa que chama uma função fora do seu programa . A menos que exista um erro nas próprias bibliotecas / bibliotecas que você esteja usando (nesse caso, a biblioteca C padrão libc
fornecida pelo arquivo da biblioteca libc.so.6
), o erro que causa a falha está no seu programa e geralmente estará no ou próximo ao última chamada no seu programa.
Nesse caso, é isso:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
É para onde o seu programa chama sprintf
. Sabemos disso porque sprintf
é o próximo passo. Mas mesmo sem declarar 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ê sempre deve usar um editor de texto que mostre automaticamente os números de linha, pelo menos para a linha em que está atualmente. Isso é muito útil para interpretar erros em tempo de compilação e problemas de tempo de execução revelados ao usar um depurador.)
Como discutido na resposta de Dennis Kaarsemaker , s
é uma matriz de um byte. (Diferente de zero, porque o valor que você atribuiu a ele ""
tem um byte de comprimento, ou seja, é igual a { '\0' }
, da mesma maneira que "Hello, world!\n"
é igual a { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
.)
Então, por que isso ainda funciona em alguma plataforma (e aparentemente funciona quando compilado com o VC9 para Windows)?
As pessoas costumam dizer que quando você aloca memória e tenta acessar a memória fora dela, isso gera 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 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, o que pode variar de plataforma para plataforma. Seu executável pode alocar mais memória para sua pilha do que realmente se destina a ser usado a qualquer momento. Às vezes, isso pode permitir que você escreva na memória locais que você não reivindicou explicitamente em seu código. É muito provável que seja isso que acontece quando você cria seu programa no VC9.
No entanto, você não deve confiar nesse comportamento mesmo no VC9. Pode depender potencialmente de diferentes versões de bibliotecas que possam existir em diferentes sistemas Windows. Mas o mais provável é o problema de o espaço extra da pilha ser alocado com a intenção de que ele realmente seja usado e, portanto , possa realmente ser usado.Então você experimenta o pesadelo completo do "comportamento indefinido", onde, nesse caso, mais de uma variável pode acabar armazenada no mesmo local, onde a escrita em uma substitui a outra ... mas nem sempre, porque às vezes escreve em variáveis são armazenados em cache nos registradores e, na verdade, não são executados imediatamente (ou as leituras das variáveis podem ser armazenadas em cache, ou pode-se presumir que uma variável seja a mesma de antes), porque a memória alocada a ela é conhecida pelo compilador por não ter sido gravada por meio de a própria variável).
E isso me leva à outra possibilidade provável de por que o programa funcionou quando construído com o VC9. É possível, e de certa forma, que alguma matriz ou outra variável tenha sido realmente alocada pelo seu programa (que pode incluir ser alocada por uma biblioteca que o seu programa está usando) para usar o espaço após a matriz de um byte s
. Portanto, tratar s
como uma matriz com mais de um byte teria o efeito de acessar o conteúdo dessa / dessas variáveis / matrizes, o que também poderia ser ruim.
Concluindo, quando você tem um erro como esse, é uma sorte receber um erro como "Falha na segmentação" ou "Falha na proteção geral". Quando você não tem isso, pode não descobrir até que seja tarde demais que seu programa tenha um comportamento indefinido.