Ошибка сегментации возникает, когда программа пытается получить доступ к памяти за пределами области, выделенной для нее.
В этом случае опытный программист C может увидеть, что проблема происходит в строке, где sprintf
вызывается. Но если вы не можете сказать, где происходит ошибка сегментации, или если вы не хотите читать код, чтобы попытаться выяснить это, тогда вы можете построить свою программу с помощью символов отладки (с gcc
помощью -g
флага это делает ), а затем запустите его через отладчик.
Я скопировал ваш исходный код и вставил его в файл, который назвал slope.c
. Тогда я построил это так:
gcc -Wall -g -o slope slope.c
(Опция -Wall
необязательна. Она просто заставляет выдавать предупреждения для большего числа ситуаций. Это также может помочь выяснить, что может быть не так.)
Затем я запустил программу в отладчике gdb
, сначала запустив ее gdb ./slope
для запуска gdb
, а затем, однажды в отладчике, дал run
команду отладчику:
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
(Не беспокойтесь о моем you have broken Linux kernel i386 NX
... support
сообщении; оно не мешает gdb
эффективно использоваться для отладки этой программы.)
Эта информация очень загадочная ... и если у вас не установлены отладочные символы для libc, вы получите еще более загадочное сообщение с шестнадцатеричным адресом вместо символьного имени функции _IO_default_xsputn
. К счастью, это не имеет значения, потому что мы действительно хотим знать, где в вашей программе происходит проблема.
Таким образом, решение состоит в том, чтобы оглянуться назад, чтобы увидеть, какие вызовы функций имели место, приводя к тому конкретному вызову функции в системной библиотеке, где SIGSEGV
сигнал был, наконец, запущен.
gdb
(и любой отладчик) имеет эту встроенную функцию: она называется трассировкой стека или backtrace . Я использую команду bt
отладчика для генерации обратной трассировки в 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)
Вы можете видеть, что ваша main
функция вызывает calc_slope
функцию (которую вы намеревались), а затем calc_slope
вызывает sprintf
, которая (в этой системе) реализована с помощью вызова нескольких других связанных библиотечных функций.
Что вас обычно интересует, так это вызов функции в вашей программе, который вызывает функцию вне вашей программы . Если только в самих библиотеках / библиотеках, которые вы используете (есть стандартная библиотека C, libc
предоставляемая файлом библиотеки libc.so.6
), отсутствует ошибка, ошибка, вызывающая сбой, присутствует в вашей программе и часто будет на уровне или вблизи последний звонок в вашей программе.
В этом случае это:
#4 0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26
Вот где ваша программа вызывает sprintf
. Мы знаем это, потому что sprintf
это следующий шаг. Но даже не говоря об этом, вы знаете это, потому что это то, что происходит в строке 26 , и это говорит:
... at slope.c:26
В вашей программе строка 26 содержит:
sprintf(s,"%d",curr);
(Вы всегда должны использовать текстовый редактор, который автоматически показывает номера строк, по крайней мере, для той строки, в которой вы находитесь. Это очень полезно для интерпретации как ошибок во время компиляции, так и проблем во время выполнения, выявленных при использовании отладчика.)
Как обсуждалось в ответе Денниса Каарсемейкера , s
это однобайтовый массив. (Не ноль, потому что значение, которое вы ему присвоили ""
, имеет длину в один байт, то есть равно так { '\0' }
же, "Hello, world!\n"
как и { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }
.)
Итак, почему может это еще работать на какой - то платформе (и , видимо , при компиляции с VC9 для Windows)?
Люди часто говорят, что когда вы выделяете память и затем пытаетесь получить доступ к памяти вне ее, это приводит к ошибке. Но это не совсем так. В соответствии с техническими стандартами C и C ++, это действительно приводит к неопределенному поведению.
Другими словами, все может случиться!
Тем не менее, некоторые вещи более вероятны, чем другие. Почему в некоторых реализациях небольшой массив в стеке работает как большой массив в стеке?
Это сводится к тому, как реализовано распределение стека, которое может варьироваться от платформы к платформе. Ваш исполняемый файл может выделить больше памяти для своего стека, чем фактически предполагается использовать в любое время. Иногда это может позволить вам записывать в области памяти, на которые вы явно не претендовали в своем коде. Скорее всего, это то, что происходит, когда вы собираете свою программу в VC9.
Однако вы не должны полагаться на это поведение даже в VC9. Это может потенциально зависеть от разных версий библиотек, которые могут существовать в разных системах Windows. Но еще более вероятно , что проблема заключается в том, что дополнительное пространство стека выделяется с намерением, что оно будет фактически использовано, и поэтому оно может фактически использоваться.Затем вы испытываете полный кошмар "неопределенного поведения", когда в этом случае более одной переменной может в конечном итоге храниться в одном и том же месте, где запись в одну перезаписывает другую ... но не всегда, потому что иногда пишет в переменные кэшируются в регистрах и фактически не выполняются немедленно (или чтение в переменные может кэшироваться, или можно предположить, что переменная такая же, как и раньше, потому что выделенная ей память известна компилятору, что она не была записана через сама переменная).
И это подводит меня к другой вероятной возможности того, почему программа работала при сборке с VC9. Возможно, и довольно вероятно, что какой-то массив или другая переменная была фактически выделена вашей программой (которая может включать в себя выделение библиотекой, которую использует ваша программа), чтобы использовать пространство после однобайтового массива s
. Таким образом, обработка s
массива длиннее одного байта будет иметь эффект доступа к содержимому этих / этих переменных / массивов, что также может быть плохо.
В заключение, если у вас есть такая ошибка, вам повезет получить ошибку типа «Ошибка сегментации» или «Ошибка общей защиты». Если у вас этого нет , вы можете не узнать, пока не станет слишком поздно, что ваша программа имеет неопределенное поведение.