E:\Moje dokumenty\HELION\Linux Unleashed\Indeks\26.DOC 445
Rozdzia³ 26. ¨ Programowanie w języku C 445
Rick McMullin
W tym rozdziale:
u Język C
u Kompilator GNU C
u Wyszukiwanie błędów – debuger gdb
u Inne narzędzia dla programistów
Linux rozprowadzany jest wraz z wieloma programami narzędziowymi wspierającymi tworzenie oprogramowania. Większość z nich przeznaczona jest do współpracy z kompilatorami języka C i C++. Niektóre z tych programów – takie jak debugery (programy służące do wyszukiwania błędów), programy formatujące kod źródłowy czy programy do automatycznego generowania plików nagłówkowych – zostaną omówione w tym rozdziale. Jego celem nie jest nauka programowania w języku C, a tylko przedstawienie zasad używania kompilatora i innych narzędzi przeznaczonych dla programistów.
C to język programowania przeznaczony do zastosowań ogólnych, stworzony we wczesnej fazie rozwoju systemu UNIX. Pierwsze wersje tego systemu pisane były w asemblerze oraz języku nazywanym B. Język C zaprojektowany został w celu ominięcia niektórych ograniczeń języka B. Od tamtej pory stał się najpopularniejszym językiem używanym w świecie komputerów.
Skąd wynika jego popularność? Oto kilka najważniejszych powodów.
u Jest to język przenośny. Praktycznie dla każdego komputera stworzony został przynajmniej jeden kompilator tego języka, a standaryzacja składni oraz bibliotek znacznie ułatwia przenoszenie programów pomiędzy różnymi platformami sprzętowymi, co jest bardzo ważne dla programistów.
u Programy pisane w języku C są szybkie.
u C jest językiem systemowym we wszystkich wersjach UNIX-a.
Język C ewoluował dość znacząco w ciągu ostatnich 20 lat. W drugiej połowie lat osiemdziesiątych Amerykański Instytut Standardów (American National Standard Institute) opracował normy obowiązujące w tym języku; standard ten jest dziś znany jako ANSI C. To jeszcze bardziej poprawiło przenośność C. Również w tym okresie do języka C wprowadzono obsługę obiektów, co zaowocowało powstaniem języka C++ (który zostanie omówiony w następnym rozdziale).
Kompilator języka C dla systemu Linux (ang. GNU C compiler, w skrócie GCC) został stworzony w ramach Free Software Foundation i rozprowadzany jest zgodnie z zasadami licencji GNU. Znajdziesz go na dysku CD-ROM dołączonym do tej książki.
Kompilator GNU C (GCC) jest w pełni funkcjonalnym kompilatorem, spełniającym normy standardu ANSI C. Jeśli potrafisz obsługiwać jakiś inny kompilator języka C, będziesz w stanie nauczyć się obsługi kompilatora gcc w bardzo krótkim czasie. Ten podrozdział opisuje pokrótce używanie tego kompilatora oraz przedstawia najczęściej wykorzystywane opcje.
Kompilator GCC wywołuje się podając w wierszu poleceń cały szereg opcji oraz nazw plików. Najprościej składnię jego można zdefiniować następująco:
gcc [opcje] [nawy_plików]
Każdy z plików poddawany jest przetwarzaniu zgodnie z informacjami podanymi za pomocą opcji. Najważniejsze z nich opisane są w następnym podrozdziale.
Opcji, które mogą zostać przesłane do GCC, jest ponad setka. Większości z nich prawdopodobnie nigdy nie użyjesz, ale niektóre są wręcz niezbędne nawet przy podstawowych zastosowaniach. Wiele opcji może składać się z więcej niż jednego znaku. Z tego powodu każda z nich musi zaczynać się od własnego myślnika, nie można grupować opcji po kilka za jednym myślnikiem, jak ma to miejsce w większości poleceń systemu Linux. Przykładowo, dwa poniższe polecenia nie są równoważne:
gcc –p –g test.cgcc –pg test.c
Pierwsze mówi kompilatorowi, by skompilował plik test.c, zapisując informacje służące do późniejszego profilowania za pomocą programu prof oraz informacje dla debugera. Drugie polecenie nakazuje skompilowanie tego pliku i zapisanie informacji do profilowania za pomocą programu gprof, nie powoduje natomiast wygenerowania informacji dla debugera.
Kiedy kompilujesz program, nie używając żadnych opcji, w bieżącym katalogu tworzony jest plik wykonywalny (o ile kompilacja zakończy się bez błędów) o nazwie a.out. Jeśli chcesz, by nazwa tworzonego pliku wykonywalnego była inna, powinieneś użyć opcji –o. Przykładowo, aby skompilować program zapisany w pliku licznik.c do pliku wykonywalnego o nazwie licznik, należy wydać polecenie:
gcc –olicznik licznik.c
Nazwa pliku wyjściowego musi pojawić się bezpośrednio po opcji –o.
Inne opcje kompilatora decydują o tym, na jakim etapie należy zakończyć proces kompilacji. Opcja –c powoduje zakończenie procesu po utworzeniu pliku pośredniego (domyślnie z rozszerzeniem .o). Jest ona używana dość często, ponieważ umożliwia przyśpieszenie kompilacji złożonych, wieloplikowych programów.
Opcja –S powoduje zakończenie kompilacji po wygenerowaniu plików asemblera (domyślnie z rozszerzeniem .s). Opcja –E powoduje, że kompilator tylko wstępnie przetworzy pliki wejściowe, wykonując zawarte w nich dyrektywy preprocesora. W takim przypadku dane wyjściowe wysyłane są na ekran, a nie do pliku.
Kompilator GCC stara się utworzyć kod wynikowy w możliwie najkrótszym czasie w taki sposób, by łatwo było znaleźć w nim ewentualne błędy. Oznacza to, że kolejność operacji pozostaje taka sama jak w pliku źródłowym i nie jest dokonywana żadna optymalizacja. Istnieje wiele opcji, dzięki którym możesz nakazać tworzenie mniejszych czy szybszych wersji programów, kosztem czasu ich kompilacji oraz łatwości wyszukiwania błędów. Najczęściej używa się opcji –O oraz –O2.
Opcja –O powoduje zastosowanie podstawowych technik optymalizacyjnych. Owocuje to zwykle powstaniem szybciej działających wersji programów. Opcja –O2 powoduje, że wygenerowany zostanie możliwie krótki oraz szybki kod. Czas kompilacji w tym przypadku jest dłuższy, ale za to program wynikowy działa szybciej.
Poza tymi opcjami istnieje jeszcze wiele opcji niskiego poziomu, których można użyć do dalszego przyspieszania działania programu. Są one jednak bardzo specyficzne i powinieneś używać ich tylko wtedy, gdy dokładnie zdajesz sobie sprawę z tego, co powodują, i jakie mogą być ich konsekwencje. Bardziej szczegółowe informacje o tych opcjach znajdziesz na stronach man (wydaj polecenie man gcc).
Spośród kilku opcji dotyczących wstawiania dodatkowego kodu służącego do określania szybkości wykonywania programu i ułatwiającego uruchamianie i testowanie, najczęściej używane są dwie: -g oraz –pg.
Opcja –g powoduje dołączenie do pliku wykonywalnego informacji dla debugera gdb, który często okazuje się niezbędny w procesie wyszukiwania błędów. GCC oferuje Ci coś, czego nie daje większość innych kompilatorów: możliwość łącznego użycia opcji –g oraz –O (która powoduje wygenerowanie zoptymalizowanej wersji programu). Jest to bardzo przydatne, szczególnie jeśli chcesz testować produkt jak najbardziej zbliżony do wersji końcowej. Powinieneś jednak zdawać sobie sprawę, że część kodu zostanie przez kompilator nieco zmodyfikowana. Więcej informacji na ten temat znajdziesz w podrozdziale „Wyszukiwanie błędów – debuger gdb”.
Opcja –pg pozwala na dołączenie do programu wykonywalnego dodatkowego kodu, który, po uruchomieniu programu, wygeneruje informacje o czasie wykonania poszczególnych sekcji programu. Informacje te mogą być przeglądane za pomocą programu gprof. Więcej informacji na jego temat znajdziesz w podrozdziale „gprof”.
Wraz z Linuxem rozprowadzany jest program gdb, który jest bardzo potężnym debugerem, służącym do wyszukiwania błędów w programach napisanych w językach C i C++. Umożliwia dostęp do struktur danych i pamięci wykorzystywanej przez program podczas jego działania. Oto jego podstawowe zalety:
u pozwala śledzić wartości zmiennych podczas wykonywania programu,
u pozwala ustawiać pułapki, które zatrzymują program po osiągnięciu danego wiersza kodu,
u pozwala wykonywać program krokowo, wiersz po wierszu.
Program gdb można uruchomić, wydając polecenie gdb. Jeśli Twój system jest prawidłowo skonfigurowany, przywita Cię on informacją podobną do tej:
GDB is free software and you are welcome to distribute copies of it under certain conditions; type "show copying" to see the conditions.There is absolutely no warranty for GDB; type "show warranty" for details.GDB 4.12 (i486-unknown-linux), Copyright 1994 Free Software Foundation, Inc.(gdb)
Przy uruchamianiu tego programu można również podać różne parametry w wierszu poleceń. Zwykle program ten uruchamia się, podając nazwę pliku wykonywalnego, w którym chcemy szukać błędów:
gdb <nazwa_pliku>
W takim przypadku plik wykonywalny zostanie automatycznie załadowany. Można również uruchomić gdb w ten sposób, by możliwe było oglądanie zawartości pliku ze zrzutem pamięci (ang. core dump) wygenerowanego przez program; można też dołączyć gdb do działającego już procesu. Aby obejrzeć listę dostępnych opcji z ich krótkim opisem, zajrzyj na stronę man lub uruchom gdb z opcją –h.
Aby gdb mógł działać poprawnie, do pliku wykonywalnego muszą zostać dołączone przez kompilator informacje o typach i nazwach zmiennych, o mapowaniu kodu na linie programu źródłowego itd., które pozwolą na powiązanie kodu źródłowego i skompilowanego kodu programu.
Aby do tworzonego programu wykonywalnego dołączyć informacje pozwalające na współpracę z debugerem, należy uruchomić kompilator z opcją –g.
gdb obsługuje wiele różnych poleceń, od prostych, służących do ładowania pliku itp., aż do bardzo zaawansowanych, pozwalających np. obejrzeć zawartość stosu. Tabela 26.1 zawiera polecenia niezbędne do rozpoczęcia pracy z gdb. Opis pozostałych poleceń znajdziesz na stronach man.
Tabela 26.1. Podstawowe polecenia gdb
Polecenie
Opis
file
Ładuje plik wykonywalny, w którym można będzie szukać błędów.
kill
Kończy działanie aktualnie wykonywanego programu.
list
Wyświetla listę sekcji kodu źródłowego użytego do utworzenia pliku wykonywalnego.
next
Przechodzi do kolejnego wiersza kodu w bieżącej funkcji, bez wchodzenia do funkcji podrzędnych.
step
Przechodzi do kolejnego wiersza kodu w bieżącej funkcji, wchodząc również do funkcji podrzędnych.
run
Powoduje wykonanie bieżącego programu.
quit
Kończy pracę debugera gdb.
watch
Pozwala na sprawdzenie wartości zmiennej po każdej jej zmianie.
break
Ustawia pułapkę w kodzie źródłowym. Pułapka powoduje wstrzymanie wykonywania programu po dojściu do zadanego punktu.
make
Pozwala na przekompilowanie programu bez konieczności wychodzenia z gdb.
shell
Umożliwia wykonanie polecenia powłoki.
Środowisko gdb obsługuje większość udogodnień znanych z interpreterów poleceń. Można na przykład używać dokańczania poleceń za pomocą klawisza Tab, a jeśli polecenie nie jest jednoznaczne, po ponownym naciśnięciu tego klawisza wyświetlone zostaną wszystkie możliwości. Można również poruszać się po liście wydanych wcześniej poleceń za pomocą klawiszy kursora.
Ten podrozdział poprowadzi Cię krok po kroku przez przykładową sesję gdb. Program, którym się zajmiemy, będzie nazywał się pozdr, a jego jedynym celem będzie wyświetlenie tekstu prostego pozdrowienia w przód i wstecz.
#include <stdio.h>main(){ char moj_tekst[]="Witam Cie"; druk_1(moj_tekst); druk_2(moj_tekst);}void druk_1 (char *tekst){ printf ("Tekst normalnie: %s\n",tekst);}void druk_2 (char *tekst){ char *tekst2; int rozm, i; rozm=strlen(tekst);
tekst2=(char *)malloc(rozm+1); for (i=0; i<rozm; i++) tekst2[rozm-i]=tekst[i]; tekst2[rozm+1]='\0'; printf ("Tekst wstecz: %s\n",tekst2);}
Aby skompilować ten program, użyj programu gcc, podając jako parametr nazwę pliku źródłowego. Aby plik wyjściowy nie nazywał się a.out, użyj opcji –o, na przykład tak:
gcc –g –opozdr pozdr.c
Po uruchomieniu program wyświetla następujące informację:
Tekst normalnie: Witam CieTekst wstecz:
Pierwszy wiersz jest w porządku, ale z drugim zdecydowanie jest coś nie tak. Powinien on oczywiście wyglądać następująco:
Tekst wstecz: eiC matiW
Z jakiegoś powodu jednak funkcja druk_2 nie działa prawidłowo. Spróbujmy rozwiązać ten problem za pomocą gdb. Najpierw trzeba go uruchomić:
gdb pozdr
Wolf-1