Неможливо компілювати з GCC на Ubuntu 12.04


9

Я намагаюся компілювати та запускати програму C нижче на моїх машинах Ubuntu & Windows з обома GCC та VC9. Однак я стикаюся з наступними проблемами:

На машині Ubuntu:

GCC складається добре, але коли я запускаю, мені показується це підказка:

Segmentation Fault (Core Dump).

На машині Windows:

VC9 Компілює & працює чудово. GCC складається добре, але процес закінчується при запуску програми.

Тут потрібна ваша експертна допомога. Ось мій код:

#include <string.h>
#include <stdio.h>

int calc_slope(int input1,int input2)
{
    int sum=0;
    int start=input1;
    int end=input2;
    int curr=start;

    //some validation:
    if (input1>input2)
        return -1;


    while(curr<=end)
    {
        if (curr>100)
        {
            char *s="";
            int length;
            int left;
            int right;
            int cent;

            sprintf(s,"%d",curr);
            length=strlen(s);
            s++;
            do
            {
                //printf("curr=%d char=%c pointer=%d length=%d \n",curr,*s,s,length);
                left = *(s-1) - '0';
                cent = *s - '0';
                right = *(s+1) - '0';
                //printf("curr=%d l=%d c=%d r=%d\n",curr,left,cent,right);
                if ( (cent>left && cent>right) || (cent<left && cent<right) )
                {
                    sum+=1; //we have either a maxima or a minima.
                }

                s++;
            } while (*(s+1)!='\0');
        }
        curr++;
    }

    return sum;
}

int main()
{
    printf("%d",calc_slope(1,150));
    return 0;
}

Оновлення:

Кредит належить Eliah за те, що він не лише допомагає мені відслідковувати помилку, але і знайомить мене з gdbйого інструментом зворотного відстеження ( bt), який так корисний для налагодження програми, складеної gcc. Ось модифікована версія, я працював після деяких проб і помилок:

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

int calc_slope(int input1,int input2)
{
    int sum=0;
    int start=input1;
    int end=input2;
    int curr=start;

    //some validation:
    if (input1>input2)
        return -1;


    while(curr<=end)
    {
        if (curr>100)
        {
            int size=10;
            char *s=(char*)malloc((size+1) * sizeof(char));
            int left;
            int right;
            int cent;

            sprintf(s,"%d",curr);
            s++;
            do
            {
                left = *(s-1) - '0';
                cent = *s - '0';
                right = *(s+1) - '0';
                if ( (cent>left && cent>right) || (cent<left && cent<right) )
                {
                    sum+=1; //we have either a maxima or a minima.
                }

                s++;
            } while (*(s+1)!='\0');
        }
        curr++;
    }

    return sum;
}

int main()
{
    printf("%d",calc_slope(1,150));
    return 0;
}

3
Я думаю, що це не питання компіляції, а більше питання виконання. Ви отримаєте більше допомоги від StackOverflow .
oaskamay

Ви впевнені , що це дійсно працює відмінно після компілюється з VC9?
Елія Каган

Так, 100%. але не з gcc.
Prahlad Yeri

@PrahladYeri Класно! Я пояснив причини цього у своїй відповіді . (Це також означає, що ми, мабуть, повинні розглядати це питання на тему, оскільки мова йде про поведінку Ubuntu * -specific *. GCC в Windows демонструє порівнянну поведінку, але немає повідомлення про помилку, і важко точно знати, що там відбувається - крім того, коли GCC на Ubuntu та Microsoft Visual C ++ працюють по-різному, я думаю, що запитати Ubuntu є розумним місцем, щоб запитати про те, чому GCC в Ubuntu працює так, як це робиться. З цим сказано, додаткові питання про те, як це зробити правильно, належать на переповнення стека.)
Eliah Каган

Модифікація рядкового літералу в C - це невизначена поведінка. Будь ласка, пам’ятайте про це.
jn1kk

Відповіді:


15

Помилка сегментації виникає, коли програма намагається отримати доступ до пам'яті за межами області, яка була призначена для неї.

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

На закінчення, коли у вас є така помилка, пощастить отримати помилку, на кшталт "Несправність сегментації" або "Помилка загального захисту". Якщо у вас цього немає , ви можете не дізнатися, поки не пізно, що у вашій програмі не визначена поведінка.


1
Дякую за таке чітке пояснення. Це саме те, що мені було потрібно .. !!
Prahlad Yeri

9

Привіт переповнення буфера!

char *s="";
sprintf(s,"%d",curr);
length=strlen(s);

Ви виділяєте один байт для рядка в стеку, а потім продовжуєте писати до нього більше одного байта. І на завершення, ви читаєте поза межами цього масиву. Прочитайте посібник з C, особливо розділ про рядки та виділення для них пам'яті.


Так, я дізнався про це пізніше. Але коли я написав це, компілятор VC9 не лише дозволив, але і правильно показав мені результати. Я роздрукував strlen (s), і він показав мені 4, а не 1 !!
Prahlad Yeri

Чи не могли б ви порадити мені, як я повинен піти на коригування цього? Як ви, мабуть, відмовились від коду, я не можу заздалегідь виділити фіксований розмір * s. Його довжина - це кількість цифр змінної curr, яку не можна знати, поки я не перетворять її в рядок !! ?
Prahlad Yeri

Я можу, але ви дійсно повинні перейти до Stack Overflow для програмування поради, оскільки тут досить офтопік.
Денніс Каарсемейкер

1
@DennisKaarsemaker Оригінальне запитання тут може бути поза темою, оскільки воно, мабуть, передбачає поведінку, яка відрізняється між Ubuntu та іншою платформою (і я пояснив найбільш вірну причину цього у своїй відповіді ). Я погоджуюся, що питання про те, як правильно розподілити рядки в C, належать у Stack Overflow, а не тут.
Елія Каган
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.