Strona główna / main page

Dla (bardzo) początkujących programistów C++

 

Kompilacja

 

Kompilator, którego będziemy używać to GNU C Compiler gcc. W szczególności komenda służaca do kompilacji to g++. A najprostsze polecenie kompilacji:
g++ mojkod.cxx.

Przydatnymi (dla początkujących) opcjami kompilatora są -Wall włącza ostrzeżenia kompilatora i -g, która przygotowuje program do śledzenia (debugowania). Opcje te należy zawsze włączać na laboratorium.

Innym kompilatorem, którego można używać jest llvm. Nazwa komendy clang. Kompilator ten charakteryzuje się bardziej przejrzystymi komunikatami o błędach kompilacji.

Większość programów składa się z wielu plików nagłówkowych i plików z kodem. Aby móc skompilować taki program należy w pierwszej kolejności skompilować kod zawarty w plikach źródłowych do plików obiektowych. Wtedy to możemy zaobserwować błędy kompilacji. Opcja -c służy do poinstruowania kompilatora że ma poprzestać na kompilacji, czyli wytworzeniu pliku .o.

Po udanym (bez błędów) etapie kompilacji otrzymujemy zestaw plików obiektowych, które muszą zostać połączone (linked). Służy do tego program ld, jednakowoż można w tym celu użyć także polecenia c++. Argumentem powinny być pliki obiektowe (.o) a po opcji -o nzwa programu wykonywalnego.

Błędów na etapie kompilacji należy szukać w plikach źródłowych. Zwykle są to błędy składniowe. Błędy na etapie łączenia są zwykle dwojakiego rodzaju. Powielenie, wtedy gdy jakiś symbol (np funkcja) występuje wieloktronie w plikach obiektowych (najczęściej implementacja w pliku nagłówkowym). Brak symbolu (np. faktyczny brak definicji funkcji lub brakujące pliki w poleceniu linkującym). W tym drugim przypadku może również brakować symbolu z biblioteki. Biblioteki dołączamu za pomocą opcji -l np. bibliotekę z finkcjami matematycznymi -lm (libm.so).

Przydatnym (bardzo) programem ułatwiającym proces kompilacji jest program make. Jego plik wsadowy, makefile definuje polecenia i zależności między kompilowanymi plikami. Inaczej mówiąc pozwala na opis zależności tego rodzaju: "jeśli zmienił się plik defs.h to należy przekompilować algs.cpp".

Z użyciem programu make polecenie kompilacji to (!?) make. Wczytywany jest wtedy plik makefile i wykonywane są zawarte w nim instrukcje. Troszkę więcej o make.
Przykładowy makefile
Trywialnie prosty makefile

 

Pliki źródłowe

 

Pliki z kodem powinny mieć końcówki: .C .cxx .cpp (c code) a plik nagłówkowe .h .H .hpp (header). Należy sobie wybrać jakiś zestaw wg. upodobania.

Deklaracje np. klas, czy funkcji należy umieszczać w plikach nagłówkowych. Na laboratorium należy tak robić zawsze. Mimo że czasem wygodniej bylo by umieścić mały kod w pliku .cpp.

Wszystkie (!) pliki nagłówkowe należy zabezpieczyć przed podwójnym włączeniem za pomocą instrukcji preprocesora.
#ifndef filename_h
#define filename_h
// deklaracje ...
#endif

Uwaga 1: To co nastepuje po #define musi być poprawnym symbolem. Żadnych kropek etc.
Uwaga 2: Czasem kopiuje się pliki nagłowkowe. Należy wtedy zwrócić szczególną uwagę aby zmienić te dyrektywy. W przeciwnym razie nie będzie można jednocześnie załączyć obu tych plików w jednym kodzie żródłowym. Najłatwiejszym rozwiązaniem jest napisanie sobie prostego generatora plików nagłówkowych (i źródłowych) generujący je z odpowiednią zawartością (w szczególności z tymi dyrektywami ale też może to być zaczątek dokumentacji itp.).

W plikach nagłówkowych należy dokumentować to czego nie da się opisać za pomocą kodu. Nie należy słownie opisywać deklaracji. Oto przykład złej dokumentacji.
/**
* @brief Funkcja generująca
* @arg size int z rozmiarem
* @return wskażnik do wygenerowanej tablicy
*/
int* generate(int size);
Dokumentacja ta jest porównywalna z instrukcją obsługi radia. "To jest czerwony guzik. Guzik można przysisnąć i jest wtedy przyciśnięty."
Oto przykład dobrej dokumentacji.
/**
* @brief Funkcja generująca ciąg liczb pierwszych
* @arg size ilosc generowanych liczb, jednocześnie jest to rozmiar zaalokowanej pamięci gdzie będą złożone
* @return wskaźnik do wygenerowanej tablicy, @warnin należy zwolnić tę pamięć
*/
int* generate(int size);

W dokumentacji powyżej została użyta jedna z konwencji obsługiwanych przez program doxygen. Oczywiście to tylko konwencja. Najważniejsza w dokumentacji jest informacja dodana do kodu.

Kod powinien być estetyczny. Jest to oczywiście pojęcie względne ale są pewne wytyczne, które są szeroko stosowane przez programistów c++.

Kod źródłowy powinien być tak jasny i czytelny aby nie potrzeba było w nim umieszczać żadnych komentarzy.

Podczas pracy z kodem oczywiście prydatne są podstawowe polcenia unixowe (skopiowanie pliku, wyświetlenie zaw. katalogu itp.) Pełno informacji tego typu znajduje się na sieci. np: 1 2 ... To (lub podobne) można mieć na zajęciach.

 

Uruchamianie i debugowanie (specyficzne dla linuxa)

 

Uruchomienie programu porzez podanie jego nazwy wygląda tak: ./nazwa. Znaki porzedzające nazwę pliku wykonywalnego ./ są zwykle konieczne bo bierząca ścieżka nie jest wyszczególniona w zmiennej $PATH. Wygodnie jest, uruchomienie programu, jako jeden z celi kompilacji z pomocą programu make (zobacz wyżej).

Zwykle program, nawet działający, nie daje poprawnych wyników albo też wykonuje się błednie (np. segfault). Należy wtedy prześledzić działanie programu. Najprostszym sposobem jest dodanie komunikatów diagnostyczynmych. To znaczy wypisywanie jakiś informacji opisujących stan programu (np. wartości pewnych zmiennych itp.). Taka diagnostyka często przydaje się nawet po uruchomieniu programu. W przypadku większych programów zwykle dość łatwo zgubić się w tych komunikatach. Zwykle jestem tu. Rozwiązaniem może być użycie predefiniowanych zmiennych preprocesora: __FILE__ i __LINE__ (odpowiedni nazwa pliku źródłowego i linia w tym pliku), które to niezawodnie pomogą wskazać na fragment kodu odpowiedzialnego za konkretny komunikat.

Jednak najszybszym sposobem na znalezienie błędu w kodzie jest uruchomienie z programem śledzącym (tzw. debuggerem). Na przykład gdb. Pozwala on śledzić (tj. podglądać warości zmiennych) i wykonywać program krokowo (tj. decydować w jakiej linii program ma się zatrzymać i umożliwić podgląd) W celu umożliwienia śledzenia należy użyć opcji -g podczas kompilacji. A samą sesję śledzenia uruchamiamy tak gdb ./nasz_program.

Po uruchomieniu w gdb, program zatrzymuje się na początku funkcji main oczekując na polecenia. Najczęstsze z nich to:

GDB wyposarzony jest w pseudokienowe środowisko dostęne po użyciu opcji -tui. Przystępny tutorial. Pełna dokumentacja. Skrót poleceń (można go mieć na zajęciach)

Innym przydatnym narzędziem przy programowaniu w c++ jest valgrind. Służy do wyszukiwania problemów z zarządzaniem pamięcią (nadpisywanie, użycie niezalokowanej pamięci, wycieki). Uruchamia się go podobnie jak GDB poprzez valgrind ./moj_program. Wynik działania to raport o problemach z pamięcią. W przypadku gdy program zakończył swoje działanie poprzez "segfault" najszybciej można zrozumieć przyczynę właśnie z użyciem programu valgrind.