Une erreur de segmentation se produit lorsqu'un programme tente d'accéder à la mémoire en dehors de la zone qui lui a été allouée.
Dans ce cas, un programmeur C expérimenté peut voir que le problème se produit dans la ligne où sprintf
est appelé. Mais si vous ne pouvez pas dire où votre erreur de segmentation se produit, ou si vous ne voulez pas vous embêter à lire le code pour essayer de le comprendre, vous pouvez créer votre programme avec des symboles de débogage (avec gcc
, le -g
drapeau fait cela ), puis exécutez-le via un débogueur.
J'ai copié votre code source et l'ai collé dans un fichier que j'ai nommé slope.c
. Ensuite, je l'ai construit comme ceci:
gcc -Wall -g -o slope slope.c
(Le -Wall
est facultatif. Il sert simplement à lui faire générer des avertissements pour plus de situations. Cela peut également aider à déterminer ce qui pourrait ne pas fonctionner.)
Ensuite, j'ai exécuté le programme dans le débogueur gdb
en exécutant d'abord gdb ./slope
pour démarrer gdb
avec le programme, puis, une fois dans le débogueur, en donnant la run
commande au débogueur:
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
(Ne vous inquiétez pas pour mon you have broken Linux kernel i386 NX
... support
message; cela n'empêche pas gdb
d'être utilisé efficacement pour déboguer ce programme.)
Ces informations sont hautement cryptées ... et si vous n'avez pas de symboles de débogage installés pour libc, alors vous obtiendrez un message encore plus cryptique qui a une adresse hexadécimale au lieu du nom de la fonction symbolique _IO_default_xsputn
. Heureusement, cela n'a pas d'importance, car ce que nous voulons vraiment savoir, c'est où se produit le problème dans votre programme .
Donc, la solution est de regarder en arrière, pour voir quels appels de fonction ont eu lieu menant à cet appel de fonction particulier dans une bibliothèque système où le SIGSEGV
signal a finalement été déclenché.
gdb
(et tout débogueur) a cette fonctionnalité intégrée: elle s'appelle une trace de pile ou une trace arrière . J'utilise la bt
commande debugger pour générer une trace dans 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)
Vous pouvez voir que votre main
fonction appelle la calc_slope
fonction (que vous vouliez), puis calc_slope
appelle sprintf
, qui est (sur ce système) implémentée avec des appels à quelques autres fonctions de bibliothèque associées.
Ce qui vous intéresse généralement, c'est l'appel de fonction dans votre programme qui appelle une fonction en dehors de votre programme . Sauf s'il y a un bogue dans la bibliothèque / les bibliothèques elles-mêmes que vous utilisez (dans ce cas, la bibliothèque C standard libc
fournie par le fichier de bibliothèque libc.so.6
), le bogue qui provoque le crash est dans votre programme et sera souvent à ou près de la dernier appel dans votre programme.
Dans ce cas, c'est:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
C'est là que votre programme appelle sprintf
. Nous le savons parce que sprintf
c'est la prochaine étape. Mais même sans qu'il l'indique, vous le savez parce que c'est ce qui se passe à la ligne 26 , et il dit:
... at slope.c:26
Dans votre programme, la ligne 26 contient:
sprintf(s,"%d",curr);
(Vous devez toujours utiliser un éditeur de texte qui affiche automatiquement les numéros de ligne, au moins pour la ligne sur laquelle vous êtes actuellement. Cela est très utile pour interpréter à la fois les erreurs de compilation et les problèmes d'exécution révélés lors de l'utilisation d'un débogueur.)
Comme discuté dans la réponse de Dennis Kaarsemaker , s
est un tableau à un octet. (Pas zéro, car la valeur que vous lui avez attribuée ""
, est longue d'un octet, c'est-à-dire qu'elle est égale à { '\0' }
, de la même manière qu'elle "Hello, world!\n"
est égale à { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
.)
Alors, pourquoi cela pourrait- il toujours fonctionner sur une plate-forme (et apparemment, cela est compilé avec VC9 pour Windows)?
Les gens disent souvent que lorsque vous allouez de la mémoire, puis essayez d'accéder à la mémoire en dehors, cela produit une erreur. Mais ce n'est pas vraiment vrai. Selon les normes techniques C et C ++, cela produit vraiment un comportement indéfini.
En d'autres termes, tout peut arriver!
Pourtant, certaines choses sont plus probables que d'autres. Pourquoi un petit tableau sur la pile semble-t-il, sur certaines implémentations, fonctionner comme un plus grand tableau sur la pile?
Cela revient à la façon dont l'allocation de pile est mise en œuvre, qui peut varier d'une plateforme à l'autre. Votre exécutable peut allouer plus de mémoire à sa pile que ce qui est réellement destiné à être utilisé à tout moment. Parfois, cela peut vous permettre d'écrire dans des emplacements de mémoire que vous n'avez pas explicitement revendiqués dans votre code. Il est très probable que c'est ce qui se passe lorsque vous créez votre programme dans VC9.
Cependant, vous ne devez pas compter sur ce comportement même dans VC9. Cela peut potentiellement dépendre de différentes versions de bibliothèques pouvant exister sur différents systèmes Windows. Mais le problème est encore plus probable que l'espace de pile supplémentaire est alloué avec l'intention qu'il sera réellement utilisé, et donc qu'il peut réellement être utilisé.Ensuite, vous vivez le cauchemar complet d'un «comportement indéfini», où, dans ce cas, plus d'une variable pourrait finir par être stockée au même endroit, où l'écriture de l'une écrase l'autre ... mais pas toujours, car parfois elle écrit dans des variables sont mis en cache dans les registres et ne sont pas réellement exécutés immédiatement (ou les lectures des variables peuvent être mises en cache, ou une variable peut être supposée être la même qu'avant car la mémoire qui lui est allouée est connue du compilateur comme n'ayant pas été écrite via la variable elle-même).
Et cela m'amène à l'autre possibilité probable pour laquelle le programme a fonctionné lorsqu'il est construit avec VC9. Il est possible, et quelque peu probable, qu'un tableau ou une autre variable ait été réellement alloué par votre programme (ce qui peut inclure l'allocation par une bibliothèque que votre programme utilise) pour utiliser l'espace après le tableau d'un octet s
. Ainsi, le fait de traiter s
comme un tableau de plus d'un octet aurait pour effet d'accéder au contenu de ces / ces variables / tableaux, ce qui pourrait également être mauvais.
En conclusion, lorsque vous avez une erreur comme celle-ci, vous avez de la chance d'obtenir une erreur comme "Erreur de segmentation" ou "Erreur de protection générale". Lorsque vous ne l' avez pas , vous ne découvrirez peut-être pas avant qu'il ne soit trop tard que votre programme ait un comportement indéfini.