Помилка сегментації виникає, коли програма намагається отримати доступ до пам'яті за межами області, яка була призначена для неї.
У цьому випадку досвідчений програміст 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
(і будь-який налагоджувач) має вбудовану функцію: вона називається слід стека або зворотний шлях . Я використовую команду 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
як масив довший одного байта матиме ефект доступу до вмісту цієї / тих змінних / масивів, що також може бути погано.
На закінчення, коли у вас є така помилка, пощастить отримати помилку, на кшталт "Несправність сегментації" або "Помилка загального захисту". Якщо у вас цього немає , ви можете не дізнатися, поки не пізно, що у вашій програмі не визначена поведінка.