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 gdb
narzę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;
}
Odpowiedzi:
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
sprintf
jest 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 (zagcc
pomocą-g
flagi 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:(Opcja
-Wall
jest 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 ./slope
aby uruchomićgdb
program, a następnie raz w debuggerze, wydającrun
polecenie debuggerowi:(Nie martw się o moją
you have broken Linux kernel i386 NX
...support
wiadomość; nie przeszkadzagdb
to 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
SIGSEGV
sygnał został ostatecznie wyzwolony.gdb
(i każdy debugger) ma tę wbudowaną funkcję: nazywa się to śledzeniem stosu lub śladem wstecznym . Korzystam zbt
polecenia debugger, aby wygenerować ślad wgdb
:Możesz zobaczyć, że twoja
main
funkcja wywołujecalc_slope
funkcję (którą zamierzałeś), a następniecalc_slope
wywołaniasprintf
, 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
libc
dostarczonej przez plik bibliotekilibc.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:
Tam właśnie wzywa twój program
sprintf
. Wiemy o tym, ponieważsprintf
jest to kolejny krok do przodu. Ale nawet bez stwierdzenia tego, wiesz o tym, ponieważ tak dzieje się w linii 26 i mówi:W twoim programie wiersz 26 zawiera:
(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 ,
s
jest 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 traktowanies
jako 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.
źródło
Witam przepełnienie bufora!
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.
źródło