Nie można skompilować z GCC na Ubuntu 12.04

9

Próbuję skompilować i uruchomić poniższy program C na moich komputerach Ubuntu i Windows z GCC i VC9. Mam jednak następujące problemy:

Na komputerze Ubuntu:

GCC kompiluje się dobrze, ale po uruchomieniu wyświetla się następujący monit:

Segmentation Fault (Core Dump).

Na komputerze z systemem Windows:

VC9 Kompiluje się i działa poprawnie. GCC kompiluje się dobrze, ale proces kończy się po uruchomieniu programu.

Potrzebujesz tutaj pomocy eksperta. Oto mój kod:

#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;
}

Aktualizacja:

Podziękowania należą się Eliaszowi za nie tylko pomoc w śledzeniu błędu, ale także zapoznanie się z gdbnarzędziem do śledzenia wstecznego ( bt), które jest tak pomocne w debugowaniu skompilowanego programu gcc. Oto zmodyfikowana wersja, pracowałem po kilku próbach i błędach:

#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;
}
Prahlad Yeri
źródło
3
Myślę, że to nie jest problem z kompilacją, ale raczej kwestia czasu wykonywania. Więcej pomocy uzyskasz od StackOverflow .
oaskamay
Czy jesteś pewien, że to naprawdę działa dobrze po kompilacji z VC9?
Eliah Kagan
Tak, 100%. ale nie z gcc.
Prahlad Yeri
@PrahladYeri Cool! Wyjaśniłem powody tego w mojej odpowiedzi . (Oznacza to również, że prawdopodobnie powinniśmy rozważyć to pytanie na temat, ponieważ dotyczy zachowania specyficznego dla Ubuntu *. GCC w systemie Windows wykazuje podobne zachowanie, ale nie ma komunikatu o błędzie i trudno jest dokładnie wiedzieć, co się tam dzieje - ponadto GCC na Ubuntu i Microsoft Visual C ++ działają inaczej, myślę, że Zapytaj Ubuntu to rozsądne miejsce, aby zapytać, dlaczego GCC na Ubuntu działa tak, jak działa. W związku z tym dalsze pytania dotyczące tego, jak to zrobić poprawnie, należą do przepełnienia stosu.)
Eliah Kagan
Modyfikacja literału łańcuchowego w C jest niezdefiniowanym zachowaniem. Proszę, pamiętajcie o tym.
jn1kk

Odpowiedzi:

15

Winy segmentacji występuje, gdy program próbuje zewnętrznej pamięci dostęp do przestrzeni, która została przydzielona do niego.

W tym przypadku doświadczony programista C może zobaczyć, że problem występuje na linii, w której sprintfjest wywoływany. Ale jeśli nie możesz powiedzieć, gdzie występuje błąd segmentacji lub jeśli nie chcesz zawracać sobie głowy czytaniem kodu, aby spróbować go rozgryźć, możesz zbudować swój program za pomocą symboli debugowania (za gccpomocą -gflagi to robi ), a następnie uruchom go przez debugger.

Skopiowałem twój kod źródłowy i wkleiłem go do pliku o nazwie slope.c. Potem zbudowałem to w ten sposób:

gcc -Wall -g -o slope slope.c

(Opcja -Walljest opcjonalna. Chodzi o to, aby wyświetlała ostrzeżenia w większej liczbie sytuacji. Może to pomóc w ustaleniu, co może być nie tak.)

Następnie uruchomiłem program w debuggerze gdb, najpierw uruchamiając go, gdb ./slopeaby uruchomić gdbprogram, a następnie raz w debuggerze, wydając runpolecenie debuggerowi:

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

(Nie martw się o moją you have broken Linux kernel i386 NX... supportwiadomość; nie przeszkadza gdbto w efektywnym użyciu do debugowania tego programu).

Ta informacja jest bardzo tajemnicza ... a jeśli nie masz zainstalowanych symboli debugowania dla libc, otrzymasz jeszcze bardziej tajemniczą wiadomość, która ma adres szesnastkowy zamiast nazwy funkcji symbolicznej _IO_default_xsputn. Na szczęście to nie ma znaczenia, ponieważ tak naprawdę chcemy wiedzieć, gdzie w twoim programie występuje problem.

Tak więc rozwiązaniem jest spojrzenie wstecz, aby zobaczyć, jakie wywołania funkcji miały miejsce przed tym konkretnym wywołaniem funkcji w bibliotece systemowej, w której SIGSEGVsygnał został ostatecznie wyzwolony.

gdb(i każdy debugger) ma tę wbudowaną funkcję: nazywa się to śledzeniem stosu lub śladem wstecznym . Korzystam z btpolecenia debugger, aby wygenerować ślad w 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)

Możesz zobaczyć, że twoja mainfunkcja wywołuje calc_slopefunkcję (którą zamierzałeś), a następnie calc_slopewywołania sprintf, które (w tym systemie) są implementowane wraz z wywołaniami kilku innych powiązanych funkcji bibliotecznych.

To, co ogólnie Cię interesuje, to wywołanie funkcji w twoim programie, która wywołuje funkcję poza twoim programem . O ile w używanej bibliotece / bibliotekach nie ma błędu (w tym przypadku standardowej biblioteki C libcdostarczonej przez plik biblioteki libc.so.6), błąd, który powoduje awarię, występuje w twoim programie i często będzie znajdować się w pobliżu ostatnie połączenie w twoim programie.

W tym przypadku jest to:

#4  0x080484cc in calc_slope (input1=1, input2=150) at slope.c:26

Tam właśnie wzywa twój program sprintf. Wiemy o tym, ponieważ sprintfjest to kolejny krok do przodu. Ale nawet bez stwierdzenia tego, wiesz o tym, ponieważ tak dzieje się w linii 26 i mówi:

... at slope.c:26

W twoim programie wiersz 26 zawiera:

            sprintf(s,"%d",curr);

(Zawsze powinieneś używać edytora tekstu, który automatycznie pokazuje numery linii, przynajmniej dla linii, na której aktualnie jesteś. Jest to bardzo pomocne w interpretacji zarówno błędów kompilacji, jak i problemów w czasie wykonywania ujawnionych podczas korzystania z debuggera.)

Jak omówiono w odpowiedzi Dennisa Kaarsemakera , sjest to tablica jednobajtowa. (Nie zero, ponieważ przypisana mu wartość ""jest długa na jeden bajt, to znaczy jest równa { '\0' }, w ten sam sposób, co "Hello, world!\n"jest równa { 'h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\0' }.)

Dlaczego więc to może nadal działać na niektórych platformach (i najwyraźniej działa po skompilowaniu z VC9 dla Windows)?

Ludzie często mówią, że przydzielenie pamięci, a następnie próba uzyskania dostępu do pamięci poza nią, powoduje błąd. Ale to nie do końca prawda. Zgodnie ze standardami technicznymi C i C ++ tak naprawdę powoduje to nieokreślone zachowanie.

Innymi słowy, wszystko może się zdarzyć!

Niektóre rzeczy są jednak bardziej prawdopodobne niż inne. Dlaczego mała tablica na stosie w niektórych implementacjach wydaje się działać jak większa tablica na stosie?

Sprowadza się to do sposobu alokacji stosu, który może różnić się w zależności od platformy. Plik wykonywalny może przydzielić więcej pamięci do swojego stosu, niż jest faktycznie przeznaczony do użycia w danym momencie. Czasami może to pozwolić ci na zapisanie w pamięci miejsc, do których wprost nie zgłosiłeś roszczenia w kodzie. Jest bardzo prawdopodobne, że tak się dzieje, gdy budujesz swój program w VC9.

Jednak nie powinieneś polegać na tym zachowaniu nawet w VC9. Może to potencjalnie zależeć od różnych wersji bibliotek, które mogą istnieć w różnych systemach Windows. Ale jeszcze bardziej prawdopodobny jest problem, że dodatkowe miejsce na stosie jest przydzielane z zamiarem, że zostanie ono faktycznie wykorzystane, a więc może być faktycznie wykorzystane.Następnie doświadczasz pełnego koszmaru „niezdefiniowanego zachowania”, w którym w tym przypadku więcej niż jedna zmienna mogłaby zostać zapisana w tym samym miejscu, w którym zapis do jednej nadpisuje drugą ... ale nie zawsze, ponieważ czasami zapisuje do zmiennych są buforowane w rejestrach i faktycznie nie wykonywane natychmiast (lub odczyty zmiennych mogą być buforowane, lub można założyć, że zmienna jest taka sama jak wcześniej, ponieważ przydzielona do niej pamięć jest znana kompilatorowi, że nie została zapisana za pośrednictwem sama zmienna).

I to prowadzi mnie do innej prawdopodobnej możliwości, dlaczego program działał po zbudowaniu z VC9. Jest możliwe i dość prawdopodobne, że pewna tablica lub inna zmienna została faktycznie przydzielona przez twój program (co może obejmować przydzielenie przez bibliotekę, z której korzysta Twój program), aby wykorzystać przestrzeń po tablicy jednobajtowej s. Zatem traktowanie sjako tablicy dłuższej niż jeden bajt skutkowałoby uzyskaniem dostępu do zawartości tej / tych zmiennych / tablic, co również mogłoby być złe.

Podsumowując, jeśli masz błąd jak ten, to szczęście, aby dostać się błąd jak „winy segmentacji” lub „Ogólny błąd ochrony”. Jeśli tego nie masz, możesz nie dowiedzieć się, dopóki nie będzie za późno, aby Twój program zachował się w sposób nieokreślony.

Eliah Kagan
źródło
1
Dzięki za takie jasne wyjaśnienie. Właśnie tego potrzebowałem .. !!
Prahlad Yeri
9

Witam przepełnienie bufora!

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

Przydzielasz jeden bajt dla ciągu znaków na stosie, a następnie kontynuujesz zapisywanie do niego więcej niż jednego bajtu. I na dodatek, czytasz poza końcem tej tablicy. Przeczytaj instrukcję C, a zwłaszcza rozdział dotyczący ciągów i przydzielania im pamięci.

Dennis Kaarsemaker
źródło
Tak, dowiedziałem się o tym później. Ale kiedy to napisałem, kompilator VC9 nie tylko pozwolił, ale również poprawnie pokazał mi wyniki. Wydrukowałem strlen (s) i pokazało mi 4, a nie 1 !!
Prahlad Yeri
Czy możesz mi również doradzić, jak powinienem to naprawić? Jak zapewne przypuszczałeś z kodu, nie mam możliwości wcześniejszego przydzielenia stałego rozmiaru do * s. Jego długość to liczba cyfr w zmiennej curr, która nie może być znana, dopóki nie przekonwertuję jej na ciąg !! ?
Prahlad Yeri
Mogę, ale naprawdę powinieneś udać się do Stack Overflow, aby uzyskać porady programistyczne, ponieważ jest to dość nie na temat.
Dennis Kaarsemaker
1
@DennisKaarsemaker Pierwotne pytanie tutaj może nie być nie na temat, ponieważ najwyraźniej dotyczy zachowania różniącego się między Ubuntu a inną platformą (i wyjaśniłem najbardziej prawdopodobny powód tego w mojej odpowiedzi ). Zgadzam się, że pytania dotyczące prawidłowego przydzielania ciągów w C należą do przepełnienia stosu, a nie tutaj.
Eliah Kagan