Se produce un error de segmentación cuando un programa intenta acceder a la memoria fuera del área que se le ha asignado.
En este caso, un programador de C experimentado puede ver que el problema está sucediendo en la línea donde sprintf
se llama. Pero si no puede saber dónde está ocurriendo su falla de segmentación, o si no quiere molestarse en leer el código para tratar de resolverlo, entonces puede construir su programa con símbolos de depuración (con gcc
, la -g
bandera hace esto ) y luego ejecutarlo a través de un depurador.
Copié su código fuente y lo pegué en un archivo que nombré slope.c
. Luego lo construí así:
gcc -Wall -g -o slope slope.c
(El -Wall
es opcional. Es solo para que produzca advertencias para más situaciones. Esto también puede ayudar a descubrir qué podría estar mal).
Luego ejecuté el programa en el depurador gdb
ejecutando primero gdb ./slope
para comenzar gdb
con el programa, y luego, una vez en el depurador, dándole el run
comando al 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
(No se preocupe por mi you have broken Linux kernel i386 NX
... support
mensaje; no evita que gdb
se use de manera efectiva para depurar este programa).
Esa información es muy críptica ... y si no tiene símbolos de depuración instalados para libc, obtendrá un mensaje aún más críptico que tiene una dirección hexadecimal en lugar del nombre de la función simbólica _IO_default_xsputn
. Afortunadamente, no importa, porque lo que realmente queremos saber es en qué parte del programa está ocurriendo el problema.
Entonces, la solución es mirar hacia atrás, para ver qué llamadas de función se llevaron a cabo antes de esa llamada de función en particular en una biblioteca del sistema donde SIGSEGV
finalmente se activó la señal.
gdb
(y cualquier depurador) tiene esta característica incorporada: se llama traza de pila o traza inversa . Utilizo el bt
comando depurador para generar una traza inversa en 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)
Puede ver que su main
función llama a la calc_slope
función (que ha previsto) y luego calc_slope
llama sprintf
, que (en este sistema) se implementa con llamadas a un par de otras funciones de biblioteca relacionadas.
Lo que generalmente le interesa es la llamada de función en su programa que llama a una función fuera de su programa . A menos que haya un error en la biblioteca / bibliotecas que esté utilizando (en este caso, la biblioteca C estándar libc
provista por el archivo de la biblioteca libc.so.6
), el error que causa el bloqueo está en su programa y con frecuencia estará cerca del última llamada en su programa.
En este caso, eso es:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
Ahí es donde llama su programa sprintf
. Sabemos esto porque sprintf
es el siguiente paso. Pero incluso sin que eso lo diga , usted sabe esto porque eso es lo que sucede en la línea 26 , y dice:
... at slope.c:26
En su programa, la línea 26 contiene:
sprintf(s,"%d",curr);
(Siempre debe usar un editor de texto que muestre automáticamente los números de línea, al menos para la línea en la que se encuentra actualmente. Esto es muy útil para interpretar tanto los errores en tiempo de compilación como los problemas de tiempo de ejecución revelados al usar un depurador).
Como se discutió en la respuesta de Dennis Kaarsemaker , s
es una matriz de un byte. (No es cero, porque el valor que le ha asignado ""
es de un byte, es decir, es igual a { '\0' }
, de la misma manera que "Hello, world!\n"
es igual a { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
).
Entonces, ¿por qué esto podría funcionar en alguna plataforma (y aparentemente funciona cuando se compila con VC9 para Windows)?
La gente suele decir que cuando asigna memoria y luego intenta acceder a la memoria externa, se produce un error. Pero eso no es realmente cierto. De acuerdo con los estándares técnicos C y C ++, lo que esto realmente produce es un comportamiento indefinido.
En otras palabras, ¡cualquier cosa puede pasar!
Aún así, algunas cosas son más probables que otras. ¿Por qué es que una pequeña matriz en la pila, en algunas implementaciones, parece funcionar como una matriz más grande en la pila?
Esto se reduce a cómo se implementa la asignación de la pila, que puede variar de una plataforma a otra. Su ejecutable puede asignar más memoria a su pila de la que está destinada a ser utilizada en cualquier momento. A veces esto puede permitirle escribir en ubicaciones de memoria que no ha reclamado explícitamente en su código. Es muy probable que esto sea lo que sucede cuando compila su programa en VC9.
Sin embargo, no debe confiar en este comportamiento incluso en VC9. Potencialmente podría depender de diferentes versiones de bibliotecas que podrían existir en diferentes sistemas de Windows. Pero aún más probable es el problema de que el espacio de pila adicional se asigne con la intención de que realmente se use, por lo que en realidad se puede usar.Luego experimenta la pesadilla completa del "comportamiento indefinido", donde, en este caso, más de una variable podría terminar almacenada en el mismo lugar, donde escribir en una sobrescribe a la otra ... pero no siempre, porque a veces escribe en variables se almacenan en caché en los registros y en realidad no se realizan de inmediato (o las lecturas de las variables se pueden almacenar en caché, o se puede suponer que una variable es la misma que antes porque el compilador sabe que la memoria asignada a ella no se ha escrito a través de la variable misma).
Y eso me lleva a la otra posibilidad probable de por qué el programa funcionó cuando se creó con VC9. Es posible, y algo probable, que alguna matriz u otra variable haya sido asignada realmente por su programa (lo que puede incluir ser asignado por una biblioteca que esté utilizando su programa) para usar el espacio después de la matriz de un byte s
. Entonces, tratar s
como una matriz de más de un byte tendría el efecto de acceder al contenido de esas / esas variables / matrices, lo que también podría ser malo.
En conclusión, cuando tiene un error como este, es afortunado recibir un error como "Error de segmentación" o "Error de protección general". Cuando no tenga eso, es posible que no se entere hasta que sea demasiado tarde que su programa tenga un comportamiento indefinido.