26.DOC

(134 KB) Pobierz









 

Rozdział 26.
Programowanie w języku C









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.

Język C

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

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.

Uruchamianie GCC

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.

Opcje kompilatora GCC

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.c
gcc –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.

Opcje dotyczące optymalizacji

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).

Opcje współpracy z debugerem
i programem profilującym

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”.

Wyszukiwanie błędów – debuger gdb

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.

Kompilowanie kodu przeznaczonego do debugowania

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.

Podstawowe polecenia gdb

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.

Przykładowa sesja gdb

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 Cie
Tekst 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

 

Zgłoś jeśli naruszono regulamin