You are on page 1of 33

IDZ DO

PRZYKADOWY ROZDZIA
SPIS TRECI

KATALOG KSIEK
KATALOG ONLINE
ZAMW DRUKOWANY KATALOG

TWJ KOSZYK
DODAJ DO KOSZYKA

CENNIK I INFORMACJE
ZAMW INFORMACJE
O NOWOCIACH
ZAMW CENNIK

CZYTELNIA
FRAGMENTY KSIEK ONLINE

Czytanie kodu. Punkt widzenia


twrcw oprogramowania
open source
Autor: Diomidis Spinellis
Tumaczenie: Bartomiej Garbacz
ISBN: 83-7361-555-5
Tytu oryginau: Code Reading The Open Source Perspective
Format: B5, stron: 448

Ksika Czytanie kodu. Punkt widzenia twrcw oprogramowania open source to


pierwszy na rynku podrcznik powicony czytaniu kodu rdowego jako osobnej
dziedzinie wiedzy, ktrej znajomo jest przydatna kademu programicie.
Ponad 600 przykadw, w ktrych wykorzystywane s kody oprogramowania open
source, przedstawia sposoby identyfikowania dobrego i zego kodu, czytania go,
przeszukiwania pod ktem konkretnych funkcji oraz wykorzystywania umiejtnoci
czytania kodu do poprawy jakoci kodw rdowych pisanych samodzielnie.
Podstawowe konstrukcje sterujce dziaaniem programu
Proste i zoone typy danych
Struktury i unie
Dynamiczne zarzdzanie pamici
Metody analizy projektw informatycznych
Konwencje pisania i formatowania kodu rdowego
Tworzenie i czytanie dokumentacji
Architektura systemw

Poznaj umiejtno czytania kodu rdowego i popraw samodzielnie pisany kod

Wydawnictwo Helion
ul. Chopina 6
44-100 Gliwice
tel. (32)230-98-63
e-mail: helion@helion.pl

Spis treci
Przedmowa.....................................................................................7
Wstp..........................................................................................11
Rozdzia 1. Wprowadzenie ..............................................................................15
1.1. Motywy i metody czytania kodu......................................................................... 16
1.1.1. Kod jako literatura ................................................................................... 16
1.1.2. Kod jako model ....................................................................................... 19
1.1.3. Utrzymanie kodu ..................................................................................... 20
1.1.4. Rozwj.................................................................................................... 21
1.1.5. Ponowne wykorzystanie........................................................................... 22
1.1.6. Inspekcje ................................................................................................. 23
1.2. Jak czyta t ksik? ........................................................................................ 24
1.2.1. Konwencje typograficzne......................................................................... 24
1.2.2. Diagramy ................................................................................................ 25
1.2.3. wiczenia................................................................................................ 27
1.2.4. Materia dodatkowy ................................................................................. 27
1.2.5. Narzdzia ................................................................................................ 28
1.2.6. Zarys treci.............................................................................................. 28
1.2.7. Debata na temat najlepszego jzyka.......................................................... 29
Dalsza lektura .......................................................................................................... 30

Rozdzia 2. Podstawowe konstrukcje programistyczne ......................................33


2.1. Peny program ................................................................................................... 33
2.2. Funkcje i zmienne globalne................................................................................ 39
2.3. Ptle while, instrukcje warunkowe i bloki........................................................... 42
2.4. Instrukcja switch................................................................................................ 45
2.5. Ptle for ............................................................................................................ 47
2.6. Instrukcje break i continue ................................................................................. 50
2.7. Wyraenia znakowe i logiczne ........................................................................... 52
2.8. Instrukcja goto................................................................................................... 55
2.9. Refaktoryzacja w skrcie ................................................................................... 57
2.10. Ptle do i wyraenia cakowite ........................................................................... 63
2.11. Podsumowanie wiadomoci o strukturach sterujcych......................................... 65
Dalsza lektura .......................................................................................................... 71

Rozdzia 3. Zaawansowane typy danych jzyka C .............................................73


3.1. Wskaniki ......................................................................................................... 73
3.1.1. Powizane struktury danych ..................................................................... 74
3.1.2. Dynamiczne przydzielanie struktur danych ............................................... 74

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source


3.1.3. Wywoania przez referencj ..................................................................... 75
3.1.4. Uzyskiwanie dostpu do elementw danych ............................................. 76
3.1.5. Tablice jako argumenty i wyniki .............................................................. 77
3.1.6. Wskaniki na funkcje............................................................................... 78
3.1.7. Wskaniki jako aliasy .............................................................................. 81
3.1.8. Wskaniki a cigi znakw........................................................................ 82
3.1.9. Bezporedni dostp do pamici ................................................................ 84
3.2. Struktury ........................................................................................................... 85
3.2.1. Grupowanie elementw danych................................................................ 85
3.2.2. Zwracanie wielu elementw danych z funkcji ........................................... 85
3.2.3. Odwzorowanie organizacji danych ........................................................... 86
3.2.4. Programowanie obiektowe ....................................................................... 87
3.3. Unie .................................................................................................................. 89
3.3.1. Wydajne wykorzystanie pamici .............................................................. 89
3.3.2. Implementacja polimorfizmu.................................................................... 90
3.3.3. Uzyskiwanie dostpu do rnych reprezentacji wewntrznych .................. 91
3.4. Dynamiczne przydzielanie pamici .................................................................... 92
3.4.1. Zarzdzanie woln pamici .................................................................... 95
3.4.2. Struktury z dynamicznie przydzielanymi tablicami ................................... 97
3.5. Deklaracje typedef............................................................................................. 98
Dalsza lektura ........................................................................................................ 100

Rozdzia 4. Struktury danych jzyka C ...........................................................101


4.1. Wektory .......................................................................................................... 102
4.2. Macierze i tabele ............................................................................................. 106
4.3. Stosy ............................................................................................................... 110
4.4. Kolejki ............................................................................................................ 112
4.5. Mapy............................................................................................................... 114
4.5.1. Tablice mieszajce................................................................................. 117
4.6. Zbiory ............................................................................................................. 119
4.7. Listy................................................................................................................ 120
4.8. Drzewa............................................................................................................ 127
4.9. Grafy............................................................................................................... 131
4.9.1. Przechowywanie wierzchokw.............................................................. 132
4.9.2. Reprezentacje krawdzi ......................................................................... 133
4.9.3. Przechowywanie krawdzi ..................................................................... 136
4.9.4. Waciwoci grafu ................................................................................. 137
4.9.5. Struktury ukryte..................................................................................... 138
4.9.6. Inne reprezentacje.................................................................................. 138
Dalsza lektura ........................................................................................................ 139

Rozdzia 5. Zaawansowane techniki sterowania przebiegiem programw ..........141


5.1. Rekurencja ...................................................................................................... 141
5.2. Wyjtki ........................................................................................................... 147
5.3. Rwnolego.................................................................................................. 151
5.3.1. Rwnolego sprztowa i programowa .................................................. 151
5.3.2. Modele sterowania................................................................................. 153
5.3.3. Implementacja wtkw .......................................................................... 158
5.4. Sygnay ........................................................................................................... 161
5.5. Skoki nielokalne.............................................................................................. 165
5.6. Podstawienie makr........................................................................................... 167
Dalsza lektura ........................................................................................................ 171

Spis treci

Rozdzia 6. Metody analizy duych projektw .................................................173


6.1. Techniki projektowe i implementacyjne ........................................................... 173
6.2. Organizacja projektu........................................................................................ 175
6.3. Proces budowy i pliki Makefile ........................................................................ 183
6.4. Konfiguracja.................................................................................................... 190
6.5. Kontrola wersji................................................................................................ 194
6.6. Narzdzia zwizane z projektem ...................................................................... 201
6.7. Testowanie ...................................................................................................... 205
Dalsza lektura ........................................................................................................ 212

Rozdzia 7. Standardy i konwencje pisania kodu .............................................213


7.1. Nazwy plikw i ich organizacja wewntrzna..................................................... 214
7.2. Wcicia ........................................................................................................... 216
7.3. Formatowanie.................................................................................................. 218
7.4. Konwencje nazewnictwa.................................................................................. 221
7.5. Praktyki programistyczne................................................................................. 224
7.6. Standardy zwizane z procesem rozwojowym................................................... 226
Dalsza lektura ........................................................................................................ 227

Rozdzia 8. Dokumentacja ............................................................................229


8.1. Rodzaje dokumentacji...................................................................................... 229
8.2. Czytanie dokumentacji..................................................................................... 230
8.3. Problemy dotyczce dokumentacji ................................................................... 241
8.4. Dodatkowe rda dokumentacji ...................................................................... 242
8.5. Popularne formaty dokumentacji w rodowisku open-source............................. 246
Dalsza lektura ........................................................................................................ 251

Rozdzia 9. Architektura ...............................................................................253


9.1. Struktury systemw ......................................................................................... 253
9.1.1. Podejcie scentralizowanego repozytorium i rozproszone........................ 254
9.1.2. Architektura przepywu danych .............................................................. 259
9.1.3. Struktury obiektowe............................................................................... 261
9.1.4. Architektury warstwowe ........................................................................ 264
9.1.5. Hierarchie.............................................................................................. 266
9.1.6. Przecinanie............................................................................................ 267
9.2. Modele sterowania........................................................................................... 269
9.2.1. Systemy sterowane zdarzeniami ............................................................. 269
9.2.2. Meneder systemowy............................................................................. 273
9.2.3. Przejcia stanw .................................................................................... 274
9.3. Pakietowanie elementw.................................................................................. 277
9.3.1. Moduy.................................................................................................. 277
9.3.2. Przestrzenie nazw .................................................................................. 279
9.3.3. Obiekty ................................................................................................. 283
9.3.4. Implementacje oglne............................................................................ 295
9.3.5. Abstrakcyjne typy danych ...................................................................... 298
9.3.6. Biblioteki .............................................................................................. 299
9.3.7. Procesy i filtry ....................................................................................... 302
9.3.8. Komponenty.......................................................................................... 304
9.3.9. Repozytoria danych ............................................................................... 306
9.4. Wielokrotne uycie architektury....................................................................... 308
9.4.1. Schematy strukturalne............................................................................ 308
9.4.2. Generatory kodu .................................................................................... 309
9.4.3. Wzorce projektowe................................................................................ 310
9.4.4. Architektury dziedzinowe ...................................................................... 312
Dalsza lektura ........................................................................................................ 316

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

Rozdzia 10. Narzdzia pomocne w czytaniu kodu .............................................319


10.1. Wyraenia regularne ...................................................................................... 320
10.2. Edytor jako przegldarka kodu ....................................................................... 323
10.3. Przeszukiwanie kodu za pomoc narzdzia grep ............................................. 326
10.4. Znajdowanie rnic midzy plikami ............................................................... 333
10.5. Wasne narzdzia........................................................................................... 335
10.6. Kompilator jako narzdzie pomocne w czytaniu kodu ..................................... 338
10.7. Przegldarki i upikszacze kodu ..................................................................... 342
10.8. Narzdzia uywane w czasie uruchomienia..................................................... 347
10.9. Narzdzia nieprogramowe.............................................................................. 351
Dostpno narzdzi oraz dalsza lektura.................................................................. 353

Rozdzia 11. Peny przykad ............................................................................355


11.1. Przegld ........................................................................................................ 355
11.2. Plan dziaania ................................................................................................ 356
11.3. Wielokrotne uycie kodu................................................................................ 358
11.4. Testowanie i uruchamianie............................................................................. 363
11.5. Dokumentacja................................................................................................ 368
11.6. Uwagi ........................................................................................................... 369

Dodatek A Struktura doczonego kodu.........................................................371


Dodatek B Podzikowania dla autorw kodu rdowego.................................375
Dodatek C Pliki rdowe .............................................................................377
Dodatek D Licencje kodu rdowego ............................................................385
D.1. ACE ............................................................................................................... 385
D.2. Apache ........................................................................................................... 386
D.3. ArgoUML ...................................................................................................... 387
D.4. DemoGL ........................................................................................................ 388
D.5. hsqldb ............................................................................................................ 388
D.6. NetBSD.......................................................................................................... 389
D.7. OpenCL.......................................................................................................... 390
D.8. Perl ................................................................................................................ 390
D.9. qtchat ............................................................................................................. 393
D.10. socket............................................................................................................. 393
D.11. vcf.................................................................................................................. 393
D.12. X Window System.......................................................................................... 394

Dodatek E Porady dotyczce czytania kodu...................................................395


Bibliografia.................................................................................413
Skorowidz...................................................................................427

Rozdzia 3.

Zaawansowane
typy danych jzyka C
Wskaniki, struktury, unie, pami przydzielana dynamicznie oraz deklaracje nazw
typw to podstawowe elementy budowy bardziej wyrafinowanych typw danych i algorytmw jzyka C. Chocia niejednokrotnie ju si z nimi spotkalimy, ich uycie jest
wystarczajco specyficzne, wyspecjalizowane i idiomatyczne, aby powici im wicej
uwagi. W niniejszym rozdziale zostan omwione najczciej stosowane sposoby uycia
wskanikw, struktur i unii w programach oraz przedstawione przykady dla kadego
wzorca uycia. Rozpoznajc funkcje penione przez okrelone konstrukcje jzykowe
mona lepiej zrozumie kod, ktry z nich korzysta.
Umiejtno tworzenia nowych obszarw przechowywania danych w trakcie dziaania programu poprzez dynamiczne przydzielanie i zwalnianie pamici stanowi jedn
z istotnych koncepcji wykorzystywanych obok wskanikw i struktur w celu tworzenia i manipulowania nowymi typami danych. Wystarczy pozna kilka najwaniejszych
idiomw dotyczcych zarzdzania pamici przydzielan dynamicznie, by znika wikszo niejasnoci.

3.1. Wskaniki
Wikszo osb uczcych si jzyka C pocztkowo traktuje wskaniki z pewn doz
szacunku i jednoczenie obawy. Jest prawd, e kod wykorzystujcy wskaniki jest
niekiedy bardzo tajemniczy i czsto stanowi rdo subtelnych bdw oraz nieprzewidzialnych awarii. Wynika to gwnie z faktu, e wskanik jest niskopoziomowym,
oferujcym ogromne moliwoci, ale prymitywnym narzdziem. Naley nauczy si
rozpoznawa sposoby jego uycia, co pozwoli pozna czsto stosowane wzorce zapisu kodu. Nieliczne pozostae fragmenty kodu, ktrych nie bdzie mona odpowiednio
zaklasyfikowa, naley traktowa podejrzliwie.

74

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

Wskaniki s uywane w jzyku C z nastpujcych powodw:


 w celu tworzenia powizanych struktur danych;
 w celu odwoywania si do dynamicznie przydzielanych struktur danych;
 w celu implementacji wywoa przez referencje;
 w celu uzyskiwania dostpu do elementw danych i iterowania po nich;
 w momencie przekazywania tablic jako argumentw;
 w celu odwoywania si do funkcji;
 jako aliasy innych wartoci;
 w celu reprezentowania cigw znakw;
 w celu zapewnienia bezporedniego dostpu do pamici systemowej.

Poniej rozpatrzymy reprezentatywne przykady uycia wskanikw w kadym z tych


przypadkw. Tam, gdzie to wskazane, zostan zamieszczone odsyacze do podrozdziaw, w ktrych zawarto szczegowe omwienie okrelonego przypadku uycia. Celem
poniszych paragrafw jest przedstawienie podsumowania, taksonomii oraz oglnych
wytycznych dotyczcych czytania kodu wykorzystujcego wskaniki.

3.1.1. Powizane struktury danych


Na najniszym poziomie abstrakcji wskanik stanowi po prostu adres pamici. Nadaje
si on zatem idealnie do reprezentowania czy midzy elementami struktur danych.
Na poziomie koncepcyjnym struktury danych, takie jak listy, drzewa i graf, skadaj
si z elementw (wierzchokw) poczonych ukami lub krawdziami. Wskanik moe
reprezentowa krawd skierowan midzy dwoma wierzchokami, przechowujc
w jednym z nich adres drugiego. Ponadto wierzchoki s uywane podczas przetwarzania takich struktur danych w celu uzyskiwania dostpu do ich elementw oraz wykonywania operacji porzdkowych. Powizane struktury danych s omawiane w podrozdziale 4.7 i kolejnych.

3.1.2. Dynamiczne przydzielanie struktur danych


Struktury danych typu wektorowego s czsto przydzielane dynamicznie, tak aby ich
rozmiar odpowiada informacjom dotyczcym liczby wymaganych elementw w czasie
wykonywania. Kwesti t omwiono szczegowo w podrozdziale 3.4. Wskaniki s
uywane do przechowywania adresw pocztkowych takich struktur danych. Ponadto
programy czsto dynamicznie konstruuj struktury jzyka C w celu przekazywania ich
midzy funkcjami lub uywania jako elementw budulcowych dla powizanych struktur danych. W takim przypadku wskaniki s uywane do przechowywania informacji
o lokalizacji w pamici przydzielonej konstrukcji struct, jak w poniszym przykadzie1:
1

netbsdsrc/sbin/fsck/preen.c: 250 280.

Rozdzia 3. Zaawansowane typy danych jzyka C

75

static struct diskentry *


finddisk(const char *name)
{
struct diskentry *d;
[...]
d = emalloc(sizeof(*d));
d->d_name = estrdup(name);
d->d_name[len] = '\0';
[...]
return d;
}

Kod sucy do przydzielenia pamici rwnej co do pojemnoci rozmiarowi konstrukcji


struct oraz rzutowanie wyniku na odpowiedni wskanik bywaj czsto przenoszone
do makra o nazwie takiej jak new (jak adekwatny operator jzyka C++)2, 3.
#define new(type)
(type *) calloc(sizeof(type), 1)
[...]
node = new(struct codeword_entry);

3.1.3. Wywoania przez referencj


Wskaniki s rwnie uywane w funkcjach, ktre pobieraj argumenty przekazywane
przez referencj (ang. by reference). Argumenty funkcji przekazywane przez referencj
s wykorzystywane w celu zwracania wynikw funkcji lub w celu uniknicia narzutu
czasowego zwizanego z kopiowaniem argumentu funkcji. Kiedy argumenty typu
wskanikowego s uywane w celu zwrcenia wynikw funkcji, w jej treci znajduje
si instrukcja przypisania wartoci do takich argumentw, jak w przypadku poniszej
funkcji, ktra ustawia warto gid na identyfikator grupy o podanej nazwie4.
int
gid_name(char *name, gid_t *gid)
{
[...]
*gid = ptr->gid = gr->gr_gid;
return(0);
}

Warto zwracana przez funkcj jest wykorzystywana jedynie w celu okrelenia bdu.
Wiele funkcji interfejsu API systemu Windows wykorzystuje tak konwencj. Po stronie obiektu wywoujcego zwykle widzi si odpowiedni argument przekazywany do
funkcji poprzez zastosowanie operatora adresu (&) wzgldem zmiennej.
Argumenty wskanikowe s rwnie uywane w celu uniknicia narzutu zwizanego
z kopiowaniem duych elementw przy kadym wywoaniu funkcji. Takie argumenty to
zwykle struktury, rzadziej liczby zmiennoprzecinkowe o podwjnej precyzji. Tablice s
zawsze przekazywane przez referencj i w przypadku wikszoci architektur inne proste
typy danych wydajniej jest kopiowa w momencie wywoania funkcji ni przekazywa
je przez referencj. Ponisza funkcja wykorzystuje dwa argumenty typu strukturalnego
2

XFree86-3.3/xc/doc/specs/PEX5/PEX5.1/SI/xref.h: 113.

XFree86-3.3/xc/doc/specs/PEX5/PEX5.1/SI/xref.c: 268.

netbsdsrc/bin/pax/cache.c: 430 490.

76

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

(now oraz then) przekazywane przez referencj tylko w celu obliczenia wyniku bez modyfikowania treci struktur. Zapewne zapisano j w ten sposb w celu uniknicia narzutu
zwizanego z kopiowaniem struktury timeval przy kadym wywoaniu5.
static double
diffsec(struct timeval *now),
struct timeval *then)
{
return ((now->tv_sec then->tv_sec)*1.0
+ (now->tv_usec then->tv_usec)/1000000.0);
}

We wspczesnych programach pisanych w C i C++ czsto mona zidentyfikowa


argumenty przekazywane przez referencj ze wzgldw wydajnociowych, gdy s
opatrzone deklaratorem const6.
static char *ccval (const struct cchar *, int);

Rola kadego z argumentw jest czasem rwnie identyfikowana poprzez uycie komentarza IN lub OUT, jak w poniszej (zapisanej w stylu sprzed ANSI C) definicji funkcji7:
int
atmresolve(rt, m, dst, desten)
register struct rtentry *rt;
struct muf *m;
register struct sockaddr *dst;
register struct atm_pseudohdr *desten;
{

/* OUT */

3.1.4. Uzyskiwanie dostpu do elementw danych


W podrozdziale 4.2 zostanie przedstawiony sposb uycia wskanika jako kursora bazy
danych sucego do uzyskiwania dostpu do elementw tabeli. Mona wymieni pewne kluczowe pojcia pomagajce w czytaniu kodu, zawierajce wskaniki suce do
uzyskiwania dostpu do tablic. Po pierwsze, wskanik na adres elementu tablicy moe
by wykorzystany do uzyskania dostpu do elementu znajdujcego si pod okrelonym
indeksem. Po drugie, arytmetyk wskanikw na elementy tablicy charakteryzuj te
same cechy semantyczne, co arytmetyk odpowiednich indeksw tablicy. W tabeli 3.1
przedstawiono przykady wykonywania najczciej wystpujcych operacji w czasie
uzyskiwania dostpu do tablicy a o elementach typu T. Na listingu 3.18 przedstawiono
przykad kodu ze wskanikami sucego do uzyskiwania dostpu do stosu.
Listing 3.1. Dostp poprzez wskanik do stosu opartego na tablicy
stackp = de_stack;
[...]
*stackp++ = finchar;
[...]
do {

Inicjalizacja stosu
Odoenie finchar na stos

netbsdsrc/sbin/ping/ping.c: 1028 1034.

netbsdsrc/bin/stty/print.c: 56.

netbsdsrc/sys/netinet/if_atm.c: 223 231.

netbsdsrc/usr.bin/compress/zopen.c: 523 555.

Rozdzia 3. Zaawansowane typy danych jzyka C

77

Tabela 3.1. Kod wykorzystujcy indeksy oraz wskaniki, sucy do uzyskiwania dostpu
do tablicy a o elementach typu T
Kod z uyciem indeksw

Kod z uyciem wskanikw

int i;

T *p

i = 0

p = a lub p = &a[0]

a[i]

*p

a[i].f

p->f

i++

p++

i += K

p += K

i == N

p == &a[N] lub p == a + N
if (count-- == 0)
return (num);
*bp++ = *--stackp;
} while (stackp > de_stack);

Zdjcie elementu ze stosu do *bp


Sprawdzenie czy stos jest pusty

3.1.5. Tablice jako argumenty i wyniki


W programach pisanych w jzykach C i C++ wskaniki pojawiaj si w przypadku
przekazywania tablic do funkcji oraz ich zwracania jako wynikw. W kodzie C, kiedy
nazwa tablicy zostanie uyta jako argument funkcji, przekazywany jest do niej tylko
adres pierwszego elementu tablicy. W rezultacie wszelkie modyfikacje dokonane na
danych tablicy w czasie wykonywania funkcji maj wpyw na elementy tablicy przekazanej przez obiekt wywoujcy funkcj. Takie niejawne wywoanie przez referencj
w przypadku tablic odrnia je od sposobu przekazywania do funkcji wszystkich innych
typw jzyka C, a przez to moe by rdem niejasnoci.
Podobnie funkcje jzyka C mog zwraca jedynie wskanik na element tablicy, a nie
ca tablic. A zatem, kiedy funkcja tworzy w tablicy wynik, a nastpnie zwraca odpowiedni wskanik, istotn rzecz jest zapewnienie, aby tablica nie bya zmienn lokaln,
ktrej pami jest przydzielana na stosie funkcji. W takim przypadku przestrze tablicy
moe zosta nadpisana po zakoczeniu dziaania funkcji i w konsekwencji wynik jej
dziaania bdzie niepoprawny. Jednym ze sposobw na uniknicie tego problemu jest
zadeklarowanie takiej tablicy jako statycznej (static), jak w przypadku poniszej funkcji, ktra zamienia adres internetowy na jego reprezentacj z wartociami dziesitnymi
rozdzielonymi kropkami9.
char *inet_ntoa(struct in_addr ad)
{
unsigned long int s_ad;
int a, b, c, d;
static char addr[20];
s_ad = ad.s_addr;
d = s_ad % 256;
s_ad /= 256;
c = s_ad % 256;
9

netbsdsrc/libexec/identd/identd.c: 120 137.

78

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

s_ad /= 256;
b = s_ad % 256;
a = s_ad / 256;
sprintf(addr, "%d.%d.%d.%d", a, b, c, d);
return addr;

Funkcja tworzy wynik w buforze addr. Gdyby nie zosta on zadeklarowany jako static,
jego zawarto (czytelna reprezentacja adresu internetowego) staaby si nieprawidowa
po zakoczeniu dziaania funkcji. Nawet powysza konstrukcja nie jest w peni bezpieczna. Funkcje wykorzystujce globalne lub statyczne zmienne lokalne nie s w wikszoci przypadkw wielobiene. Oznacza to, e funkcja nie moe zosta wywoana z poziomu wtku innego programu, kiedy jedna instancja tej funkcji ju dziaa. Co gorsza,
w naszym przypadku wynik funkcji musi zosta zapisany w innym miejscu (na przykad
przy uyciu wywoania funkcji strdup) zanim funkcja zostanie wywoana ponownie.
W przeciwnym razie zostaby on nadpisany nowym rezultatem. Przykadowo, przedstawiona implementacja funkcji inet_ntoa nie mogaby zosta uyta zamiast funkcji
10
naddr_ntoa w nastpujcym kontekcie :
(void)fprintf(ftrace, "%s Router Ad from %s to %s via %s life=%d\n",
act, naddr_ntoa(from), naddr_ntoa(to),
ifp ? ifp->int_name : "?",
ntohs(p->ad.icmp_ad_life));

W tym przypadku w celu obejcia opisanego problemu funkcja naddr_ntoa jest uywana
jako kod opakowujcy dla funkcji inet_ntoa, zapewniajc przechowanie jej wyniku
w licie cyklicznej o czterech rnych buforach tymczasowych11.
char *
naddr_ntoa(naddr a)
{
#define NUM_BUFS 4
static int bufno;
static struct {
char
str[16];
} bufs[NUM_BUFS];
char *s;
struct in_addr addr;

/* xxx.xxx.xxx.xxx\0 */

addr.s_addr = a;
s = strcpy(bufs[bufno].str, inet_ntoa(addr));
bufno = (bufno+1) % NUM_BUFS;
return s;

3.1.6. Wskaniki na funkcje


Czsto przydatnym rozwizaniem jest sparametryzowanie funkcji poprzez przekazywanie jej do innej funkcji jako argumentu. Jednak jzyk C nie dopuszcza przekazywania
funkcji jako argumentw. Moliwe jest jednak przekazanie jako argumentu wskanika
na funkcj. Na listingu 3.212 funkcja getfile, uywana do przetwarzania plikw w czasie procesu odtwarzania po awarii, pobiera parametry fill oraz skip. Su one do
10

netbsdsrc/sbin/routed/rdisc.c: 121 125.

11

netbsdsrc/sbin/routed/trace.c: 123 139.

12

netbsdsrc/sbin/restore/tape.c: 177 837.

Rozdzia 3. Zaawansowane typy danych jzyka C

79

okrelenia, w jaki sposb naley odczytywa lub pomija dane. Funkcja jest wywoywana (listing 3.2:1) z rnymi argumentami (listing 3.2:3) w celu wykonania pocztkowego przegldu danych lub odtworzenia albo pominicia plikw danych.
Listing 3.2. Parametryzacja przy uyciu argumentw funkcji
/*
* Verify that the tape drive can be accessed and
* that it actually is a dump tape.
*/
void
setup()
{
[...]
getfile(xtrmap, xtrmapskip);
[...]
}
/* Prompt user to load a new dump volume. */
void
getvol(int nextvol)
{
[...]
getfile(xtrlnkfile, xtrlnkskip);
[...]
getfile(xtrfile, xtrskip);
[...]
}
/*
* skip over a file on the tape
*/
void
skipfile()
{
curfile.action = SKIP;
getfile(xtrnull, xtrnull);
}
/* Extract a file from the tape. */
void
getfile(void (*fill)(char *, long), (*skip)(char *, long))
{
[...]
(*fill)((char *)buf, (long)(size > TP_BSIZE ?
fssize : (curblk - 1) * TP_BSIZE + size));
[...]
(*skip)(clearedbuf, (long)(size > TP_BSIZE ?
TP_BSIZE : size));
[...]
(*fill)((char *)buf, (long)((curblk * TP_BSIZE) + size));
[...]
}
/* Write out the next block of a file. */
static void
xtrfile(char *buf, long size)
{ [...] }
/* Skip over a hole in a file. */
static void
xtrskip(char *buf, long size)
{ [...] }
/* Collect the next block of a symbolic link. */
static void

[1] Wywoanie z argumentami


bdcymi wskanikami na funkcje

[1]
[1]

[1]

[2] Wywoanie funkcji


przekazanej w argumencie
[2]

[2]

[3] Funkcje przekazywane


jako parametry argumentw

80

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source


xtrlnkfile(char *buf, long size)
{ [...] }
/* Skip over a hole in a symbolic link (should never happen). */
static void
xtrlnkskip(char *buf, long size)
{ [...] }
/* Collect the next block of a bit map. */
static void
xtrmap(char *buf, long size)
{ [...] }
/* Skip over a hole in a bit map (should never happen). */
static void
xtrmapskip(char *buf, long size)
{ [...] }
/* Noop, when an extraction function is not needed. */
void
xtrnull(char *buf, long size)
{ return; }

Wiele plikw z bibliotek jzyka C, takich jak qsort i bsearch, pobiera argumenty bdce
wskanikami na funkcje, ktre okrelaj sposb ich dziaania13.
getnfile()
{
[...]
qsort(nl, nname, sizeof(nltype), valcmp);
[...]
}
valcmp(nltype *p1, nltype *p2)
{
if ( p1 -> value < p2 -> value ) {
return LESSTHAN;
}
if ( p1 -> value > p2 -> value ) {
return GREATERTHAN;
}
return EQUALTO;
}

W powyszym przykadzie sposb, w jaki funkcja qsort porwnuje elementy sortowanej


tablicy, okrela funkcja valcmp.
Wreszcie, wskaniki na funkcje mog by uywane do parametryzowania sterowania
w treci kodu. W poniszym przykadzie wskanik closefunc jest uywany do przechowywania funkcji, ktra zostanie wywoana w celu zamknicia strumienia fin w zalenoci od sposobu jego otwarcia14.
void
retrieve(char *cmd, char *name)
{
int (*closefunc)(FILE *) = NULL;
[...]
if (cmd == 0) {
fin = fopen(name, "r"), closefunc = fclose;
[...]
if (cmd) {
13

netbsdsrc/usr.bin/gprof/gprof.c: 216 536.

14

netbsdsrc/libexec/ftpd/ftpd.c: 792 860.

Rozdzia 3. Zaawansowane typy danych jzyka C

81

[...]
fin = ftpd_popen(line, "r", 1), closefunc = ftpd_pclose;
[...]
(*closefunc)(fin);

3.1.7. Wskaniki jako aliasy


Wskaniki s czsto uywane do tworzenia aliasw pewnych wartoci15.
struct output output = {NULL, 0, NULL, OUTBUFSIZ, 1, 0};
[...]
struct output *out1 = &output;

W powyszym przykadzie kodu poddany dereferencji wskanik out1 moe zosta uyty
zamiast oryginalnej wartoci zmiennej output. Aliasw uywa si z wielu rnych
powodw.

Kwestie wydajnociowe
Przypisanie wskanika jest bardziej wydajne od przypisania wikszego obiektu. W poniszym przykadzie wskanik curt mgby by zmienn strukturaln, a nie wskanikiem
na tak struktur. Jednak odpowiednia instrukcja przypisania byaby mniej wydajna,
gdy wizaaby si ze skopiowaniem zawartoci caej struktury16.
static struct termios cbreakt, rawt, *curt;
[...]
curt = useraw ? &rawt : &cbreakt;

Odwoania do statycznie inicjalizowanych danych


Zmienna jest uywana do wskazywania na rne wartoci danych statycznych. Najczciej
spotykany przykad w tym przypadku to zbir wskanikw znakowych wskazujcych
na rne cigi znakw17.
char *s;
[...]
s = *(opt->bval) ? "True" : "False";

Implementacja semantyki odwoa do zmiennych


w kontekcie globalnym
Mao przyjazny tytu niniejszego punktu odnosi si do odpowiednika uycia wskanikw wywoa przez referencj, tyle e z wykorzystaniem zmiennej globalnej zamiast
argumentu funkcji. Zatem globalna zmienna wskanikowa jest uywana do odwoywania si do danych, do ktrych naley uzyska dostp i zmodyfikowa je w innym miejscu. W przedstawionym poniej generatorze liczb pseudolosowych wskanik fptr jest
15

netbsdsrc/bin/sh/output.c: 81 84.

16

netbsdsrc/lib/libcurses/tty.c: 66 171.

17

netbsdsrc/games/rogue/room.c: 629 635.

82

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

inicjalizowany tak, aby wskazywa na wpis w tabeli ziaren generatora liczb pseudolosowych. W podobny sposb jest on ustawiany w funkcji srrandom(). W kocu, w funkcji
18
rrandom() zmienna fptr jest uywana do zmodyfikowania wartoci, na ktr wskazuje .
static long rntb[32] = {
3, 0x9a319039, 0x32d9c024, 0x9b663182, 0x5da1f342,
[...]
};
static long *fptr = &rntb[4];
[...]
void
srrandom(int x)
{
[...]

fptr = &state[rand_sep];
[...]

long
rrandom()
{
[...]
*fptr += *rptr;
i = (*fptr >> 1) & 0x7fffffff;

Rwnowany rezultat otrzymalibymy, przekazujc fptr jako argument do funkcji


srrandom() oraz rrandom().
Podobne podejcie jest czsto wykorzystywane w celu uzyskiwania dostpu do modyfikowalnej kopii danych globalnych19.
WINDOW scr_buf;
WINDOW *curscr = &scr_buf;
[...]
move(short row, short col);
{
curscr->_cury = row;
curscr->_curx = col;
screen_dirty = 1;
}

3.1.8. Wskaniki a cigi znakw


W jzyku C literay znakowe s reprezentowane przez tablic znakw zakoczon znakiem zerowym '\0'. W rezultacie cigi znakw s reprezentowane przez wskanik na
pierwszy znak sekwencji zakoczonej znakiem zerowym. W poniszym fragmencie
kodu mona zobaczy, w jaki sposb kod funkcji bibliotecznej jzyka C strlen przesuwa wskanik wzdu cigu znakw przekazanego jako parametr funkcji do momentu
osignicia jego koca. Nastpnie odejmuje wskanik na pocztek cigu od wskanika na kocowy znak zerowy w celu obliczenia i zwrcenia dugoci cigu20.
18

netbsdsrc/games/rogue/random.c: 62 109.

19

netbsdsrc/games/rogue/curses.c: 121 157.

20

netbsdsrc/lib/libc/string/strlen.c: 51 59.

Rozdzia 3. Zaawansowane typy danych jzyka C

83

size_t
strlen(const char *str)
{
register const char *s;

for (s = str; *s; ++s)


;
return(s - str);

Czytajc kod operujcy na cigach znakw, naley pamita o istniejcym rozrnieniu na wskaniki znakowe i tablice znakw. Cho obie konstrukcje s czsto uywane
do reprezentowania cigw znakw (ze wzgldu na fakt, e tablica znakw jest automatycznie konwertowana na wskanik na pierwszy znak w tablicy w momencie
przekazania do funkcji), odpowiednie typy i operacje, ktre mona na nich wykonywa,
s rne. Przykadowo, poniszy kod definiuje zmienn pw_file jako wskanik na znak
wskazujcy na sta znakow zawierajc sekwencj "/etc/passwd"21.
static char *pw_file = "/etc/passwd";

Rozmiar zmiennej pw_file na maszynie Autora wynosi 4 i moe ona zosta zmodyfikowana tak, aby wskazywaa inne miejsce, jednak prba zmiany zawartoci pamici,
na ktr wskazuje, spowoduje niezdefiniowane zachowanie.
Z drugiej strony, poniszy wiersz definiuje zmienn line jako tablic znakw zainicjalizowan treci "/dev/XtyXX", po ktrej wystpuje warto zerowa22.
static char line[] = "/dev/XtyXX";

Zastosowanie operatora sizeof wzgldem line daje w wyniku 11. Zmienna line zawsze
bdzie odwoywa si do tego samego obszaru pamici, a elementy ktre zawiera mog
by dowolnie modyfikowane23.
line[5] = 'p';

Pamitanie o rnicach istniejcych midzy tablicami znakw a wskanikami podczas


czytania kodu jest istotne, gdy obie konstrukcje s uywane, czsto zamiennie, w podobnych celach. W szczeglnoci aden kompilator jzyka C nie ostrzega uytkownika
o sytuacji, w ktrej obie zostan przypadkowo wymieszane w ramach rnych plikw.
Wemy pod uwag ponisze dwie (niezwizane ze sob) definicje oraz odpowiedni
deklaracj24, 25, 26.
char version[] = "332";
char *version = "2.1.2";
extern char *version;

21

netbsdsrc/distrib/utils/libhack/getpwent.c: 45.

22

netbsdsrc/lib/libutil/pty.c: 71.

23

netbsdsrc/lib/libutil/pty.c: 81.

24

netbsdsrc/usr.bin/less/less/version.c: 575.

25

netbsdsrc/libexec/identd/version.c: 3.

26

netbsdsrc/libexec/identd/identd.c: 77.

84

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

Obie definicje s uywane do zdefiniowania cigu informujcego o wersji, ktry jest


zapewne przekazywany do funkcji printf w celu wydrukowania na ekranie. Jednake
deklaracja extern char *version moe by uyta jedynie w celu uzyskania dostpu
do zmiennej zdefiniowanej jako char *version. Cho konsolidacja pliku rdowego
z plikiem zawierajcym definicj char version[] zwykle nie spowoduje wygenerowania bdu, wynikowy program zakoczy awaryjnie dziaanie po uruchomieniu. Zmienna
version zamiast wskazywa na obszar pamici, zawierajcy cig z informacj o wersji,
bdzie prawdopodobnie wskazywaa na obszar pamici, ktrej adres jest reprezentowany przez wzorzec bitowy cigu znakw "332" (0x33333200 w przypadku architektur
niektrych procesorw).

3.1.9. Bezporedni dostp do pamici


Ze wzgldu na fakt, e wskaniki s wewntrznie reprezentowane jako adresy pamici, mona spotka kod niskopoziomowy wykorzystujcy wskaniki do uzyskiwania
dostpu do obszarw pamici zwizanych z zasobami sprztowymi. Wiele urzdze
peryferyjnych, takich jak karty graficzne i sieciowe, wykorzystuje oglnodostpn
pami w celu wymiany danych z procesorem systemowym. Zmienna zainicjalizowana
tak, aby wskazywaa na ten obszar, moe by z atwoci wykorzystywana do komunikowania si z danym urzdzeniem. W poniszym przykadzie zmienna video jest
ustawiana tak, aby wskazywaa na obszar pamici zajmowany przez sterownik ekranu
(0xe08b8000). Zapis do tego obszaru przy przesuniciu okrelonym przez dany wiersz
i kolumn (vid_ypos oraz vid_xpos) powoduje pojawienie si na ekranie odpowiedniego
znaku (c)27.
static void
vid_wrchar(char c)
{
volatile unsigned short *video;

video = (unsigned short *)(0xe08b8000) + vid_ypos * 80 + vid_xpos;


*video = (*video & 0xff00) | 0x0f00 | (unsigned short)c;

Naley pamita, e wspczesne systemy operacyjne zapobiegaj uzyskiwaniu dostpu


do zasobw sprztowych przez programy uytkownika bez podjcia wczeniej odpowiednich krokw. Tego rodzaju kod spotyka si w zasadzie tylko w przypadku badania
systemw osadzonych lub kodu jdra systemu i sterownikw urzdze.
wiczenie 3.1. Zaproponuj wasn implementacj stosu opartego na wskanikach z biblioteki compress28 uywajc indeksu tablicy. Zmierz rnic w szybkoci i skomentuj
czytelno obu implementacji.
wiczenie 3.2. Zlokalizuj na pycie CD-ROM doczonej do ksiki trzy wystpienia
kodu uywajcego wskanikw z kadego z wymienionych powodw.

27
28

netbsdsrc/sys/arch/pica/pica/machdep.c: 951 958.


netbsdsrc/usr.bin/compress/zopen.c

Rozdzia 3. Zaawansowane typy danych jzyka C

85

wiczenie 3.3. Jeeli Czytelnik zna jzyk C++ lub Java, moe sprbowa wyjani,
w jaki sposb mona zminimalizowa (w jzyku C++) lub unikn (w jzyku Java)
uywania wskanikw.
wiczenie 3.4. Czym rni si wskanik jzyka C od adresu pamici? Jaki ma to wpyw
na zrozumienie kodu? Jakie narzdzia wykorzystuj istniejc rnic?
wiczenie 3.5. Czy cigi znakw zawierajce informacje o wersji programu powinny
by reprezentowane jako tablice znakw czy jako wskaniki na cigi znakw? Uzasadnij odpowied.

3.2. Struktury
Konstrukcja jzyka C struct to zgrupowanie elementw danych, ktre umoliwia
uywanie ich wsplnie. Struktury s uywane w programach pisanych w C w celu:
 zgrupowania elementw danych uywanych zwykle wsplnie;
 zwrcenia wielu elementw danych z funkcji;
 konstruowania powizanych struktur danych (podrozdzia 4.7);
 odwzorowania organizacji danych w urzdzeniach sprztowych,

czach sieciowych oraz nonikach danych;


 implementacji abstrakcyjnych typw danych (podrozdzia 9.3.5);
 tworzenia programw zgodnie z paradygmatem obiektowym.

Ponisze punkty stanowi rozwinicie dyskusji na temat uycia struktur i poruszaj


zagadnienia nie omawiane w innych podrozdziaach.

3.2.1. Grupowanie elementw danych


Struktury s czsto uywane w celu tworzenia grup powizanych elementw, ktre
zazwyczaj s uywane wsplnie jako cao. Sztandarowym przykadem jest tu reprezentacja wsprzdnych pozycji na ekranie29.
struct point {
int col, line;
};

W innych przypadkach moe chodzi o liczby zespolone lub pola tworzce wiersze tabeli.

3.2.2. Zwracanie wielu elementw danych z funkcji


Kiedy wynik dziaania funkcji naley wyrazi uywajc wicej ni jednego podstawowego typu danych, wiele elementw wyniku mona zwrci albo poprzez argumenty
wywoania funkcji przekazane przez referencj (patrz podrozdzia 3.1.3), albo grupujc
29

netbsdsrc/games/snake/snake/snake.c: 75 77.

86

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

je w ramach zwracanej struktury. W poniszym przykadzie funkcja difftv zwraca rnic midzy dwiema wartociami czasowymi wyraonymi w sekundach (tv_sec) oraz
w mikrosekundach (tv_usec) w postaci struktury timeval30.
static struct timeval
difftv(struct timeval a, struct timeval b)
{
static struct timeval diff;

diff.tv_sec = b.tv_sec - a.tv_sec;


if ((diff.tv_usec = b.tv_usec - a.tv_usec) < 0) {
diff.tv_sec--;
diff.tv_usec += 1000000;
}
return(diff);

3.2.3. Odwzorowanie organizacji danych


Kiedy dane s przesyane przez sie, przenoszone z i na urzdzenie pamici masowej
lub kiedy programy bezporednio komunikuj si ze sprztem, struktury s czsto uywane w celu reprezentowania sposobu organizacji danych w przypadku takiego medium.
Ponisza struktura reprezentuje blok polece karty sieciowej Intel EtherExpress31.
struct fxp_cb_nop {
void *fill[2];
volatile u_int16_t cb_status;
volatile u_int16_t cb_command;
volatile u_int16_t link_addr;
};

Kwalifikator volatile jest uywany w celu oznaczenia, e odpowiednie pola pamici


s uywane przez obiekty pozostajce poza kontrol programu (w tym przypadku chodzi
o kart sieciow). Kompilator nie moe wic przeprowadza optymalizacji na takich
polach, na przykad usuwa nadmiarowych referencji.
W okrelonych przypadkach mona zadeklarowa pole bitowe (ang. bit field), okrelajce cisy zakres bitw uywanych do przechowywania okrelonej wartoci w danym
urzdzeniu32.
struct fxp_cb_config {
[...]
volatile u_int8_t
byte_count:6,
:2;
volatile u_int8_t
rx_fifo_limit:4,
tx_fifo_limit:3,
:1;

W powyszym przykadzie liczba przesyanych bajtw okrelona jako byte_count ma


zajmowa 6 bitw, natomiast ograniczenia kolejek FIFO odbiornika i nadajnika maj
zajmowa, odpowiednio, 4 i 3 bity w urzdzeniu sprztowym.
30

netbsdsrc/usr.bin/named/dig/dig.c: 1221 1233.

31

netbsdsrc/sys/dev/pci/if_fxpreg.h: 102 107.

32

netbsdsrc/sys/dev/pci/if_fxpreg.h: 116 125.

Rozdzia 3. Zaawansowane typy danych jzyka C

87

Pakiety danych sieciowych rwnie s czsto kodowane przy uyciu struktur jzyka
C w celu okrelenia struktury ich elementw, co ukazuje ponisza klasyczna definicja
nagwka pakietw TCP33.
struct tcphdr {
u_int16_t th_sport;
u_int16_t th_dport;
tcp_seq
th_seq;
tcp_seq
th_ack;

/*
/*
/*
/*

source port */
destination port */
sequence number */
acknowledgement number */

Wreszcie, struktury s rwnie uywane w celu odwzorowania sposobu przechowywania danych na nonikach danych, na przykad dyskach lub tamach. Przykadowo,
waciwoci dysku partycji systemu MS-DOS okrela si za pomoc tak zwanego bloku
parametrw BIOS. Jego pola odwzorowuje ponisza struktura34.
struct bpb33 {
u_int16_t
u_int8_t
u_int16_t
u_int8_t
u_int16_t
u_int16_t
u_int8_t
u_int16_t
u_int16_t
u_int16_t
u_int16_t
};

bpbBytesPerSec;
bpbSecPerClust;
bpbResSectors;
bpbFATs;
bpbRootDirEnts;
bpbSectors;
bpbMedia;
bpbFATsecs;
bpbSecPerTrack;
bpbHeads;
bpbHiddenSecs;

/*
/*
/*
/*
/*
/*
/*
/*
/*
/*
/*

bytes per sector */


sectors per cluster */
number of reserved sectors */
number of FATs */
number of root directory entries */
total number of sectors */
media descriptor */
number of sectors per FAT */
sectors per track */
number of heads */
number of hidden sectors */

Sposb uporzdkowania pl w ramach struktury jest zaleny od architektury oraz kompilatora. Ponadto reprezentacja rnych elementw w strukturze jest zalena od architektury oraz systemu operacyjnego (system operacyjny moe wymusza na procesorze
dostosowanie si do okrelonej kolejnoci przechowywania bajtw). Nawet proste typy
danych, takie jak liczby cakowite, mog posiada swoj warto przechowywan na
rne sposoby. Std uycie struktur w celu odwzorowania zewntrznych danych stanowi z gruntu nieprzenone rozwizanie.

3.2.4. Programowanie obiektowe


W jzyku C struktury s czasem uywane do tworzenia konstrukcji przypominajcych
obiekty poprzez zgrupowanie razem elementw danych i wskanikw na funkcje w celu
zasymulowania pl i metod klasy. W poniszym przykadzie struktura domain, reprezentujca rne domeny protokou sieciowego (na przykad internet, SNA, IPX), grupuje
dane dotyczce okrelonej domeny, takie jak jej rodzina dom_family oraz metody suce do operowania na nich, takie jak metoda inicjalizacji tabeli routingu dom_rtattach35.
struct
domain {
int
dom_family;
char *dom_name;
33

netbsdsrc/sys/netinet/tcp.h: 43 47.

34

netbsdsrc/sys/msdosfs/bpb.h: 22 34.

35

netbsdsrc/sys/sys/domain.h: 50 65.

/* AF_xxx */

88

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

};

void (*dom_init)(void); /* initialize domain data structures */


[...]
int (*dom_rtattach)(void **, int);
/* initialize routing table */
int dom_rtoffset;
/* an arg to rtattach, in bits */
int dom_maxrtkey;
/* for routing layer */

Po wprowadzeniu takiej deklaracji, zmienne okrelonego typu strukturalnego, czy te


cilej rzecz ujmujc wskaniki na typ strukturalny po odpowiednim zainicjalizowaniu mog by traktowane w sposb przypominajcy uycie obiektw w jzykach
C++ i Java36.
for (dom = domains; dom; dom = dom->dom_next)
if (dom->dom_family == i && dom->dom_rtattach) {
dom->dom_rtattach((void **)&nep->ne_rtable[i],
dom->dom_rtoffset);
break;

Ze wzgldu na fakt, e obiekty mog by inicjalizowane za pomoc rnych metod


(wskanikw na funkcje), a jednak s uywane poprzez ten sam interfejs (wywoania
nastpuj poprzez te same nazwy skadowych struktur), opisana technika stanowi implementacj metod wirtualnych (ang. virtual methods), dostpnych w jzykach obiektowych, oraz programowania polimorficznego (ang. polymorphic programming). W rzeczywistoci, ze wzgldu na fakt, e obiekty nalece do tej samej klasy wspuytkuj
swoje metody (ale nie pola), wskaniki na metody s czsto dzielone przez rne obiekty
dziki przechowywaniu w strukturze obiektw jedynie wskanika na inn struktur
zawierajc wskaniki na faktyczne metody37.
struct file
[...]
short
short
short
struct
struct
int
int
int
int

};

{
f_type;
/*
f_count;
/*
f_msgcount;
/*
ucred *f_cred; /*
fileops {
(*fo_read)(struct

descriptor type */
reference count */
references from message queue */
credentials associated with descriptor */

file *fp, struct uio *uio,


struct ucred *cred);
(*fo_write)(struct file *fp, struct uio *uio,
struct ucred *cred);
(*fo_ioctl)(struct file *fp, u_long com,
caddr_t data, struct proc *p);
(*fo_poll)(struct file *fp, int events,
struct proc *p);
(*fo_close)(struct file *fp, struct proc *p);

int
} *f_ops;
off_t
f_offset;
caddr_t
f_data;

/* vnode or socket */

W powyszym przykadzie kady obiekt reprezentujcy otwarty plik lub gniazdo dzieli
swoje metody read, write, ioctl, poll oraz close poprzez skadow struktury f_ops,
wskazujc na dzielon struktur fileops.
36

netbsdsrc/sys/kern/vfs_subr.c: 1436 1440.

37

netbsdsrc/sys/sys/file.h: 51 73.

Rozdzia 3. Zaawansowane typy danych jzyka C

89

wiczenie 3.6. Alternatywnym sposobem przekazywania do funkcji danych typu tablicowego przez warto i zwracania ich jako rzeczywistych wartoci jest zawarcie tablicy
w strukturze. Zlokalizuj takie przykady na pycie doczonej do ksiki. Wyjanij,
dlaczego takie podejcie nie jest wykorzystywane zbyt czsto.
wiczenie 3.7. Zlokalizuj 20 oddzielnych wystpie struktur na pycie doczonej do
ksiki i okrel powd ich wykorzystania. Zapoznaj si z bibliotek struktur danych
oglnego przeznaczenia, tak jak STL jzyka C++ lub java.util. Wska, ktre wystpienia mona by zapisa korzystajc z biblioteki. Zminimalizuj czas powicony kademu wystpieniu.
wiczenie 3.8. Wiele technik, bibliotek oraz narzdzi obsuguje przenone kodowanie
danych w celu ich przenoszenia midzy aplikacjami. Okrel techniki majce zastosowanie w Twoim rodowisku i porwnaj je z uyciem podejcia bazujcego na strukturach jzyka C.

3.3. Unie
Konstrukcja jzyka C union grupuje elementy, ktre dziel ten sam obszar pamici.
Moliwy jest dostp tylko do jednego elementu naraz spord wspuytkujcych taki
obszar. Unie s uywane w jzyku C w celu:
 zapewnienia wydajnego wykorzystania pamici;
 zaimplementowania polimorfizmu;
 umoliwienia dostpu do danych przy uyciu rnych reprezentacji wewntrznych.

3.3.1. Wydajne wykorzystanie pamici


Czsto spotykane uzasadnienie wykorzystania unii dotyczy wspuytkowania tego
samego obszaru pamici w dwch rnych celach. Ma to na celu zaoszczdzenie przynajmniej kilku bajtw pamici. Istniej przypadki, w ktrych unie s wykorzystywane
wycznie w tym celu. Jednak w przypadku, gdy urzdzenia wbudowane obsuguj
pami o wielomegabajtowej pojemnoci, uywanie unii staje si nieuzasadnione.
Oprcz starszego kodu mona rwnie spotka przypadki, gdy dua liczba obiektw
opartych na unii uzasadnia dodatkowe wysiki zwizane z napisaniem odpowiedniego
kodu. Poniszy przykad pochodzcy z funkcji malloc standardowej biblioteki jzyka
C to typowy przypadek38.
union overhead {
union
overhead *ov_next;
struct {
u_char ovu_magic;
u_char ovu_index;
#ifdef RCHECK
u_short ovu_rmagic;
38

/* when free */
/* magic number */
/* bucket # */
/* range magic number */

netbsdsrc/lib/libc/stdlib/malloc.h: 78 92.

90

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source


u_long ovu_size;
/* actual block size */
#endif
} ovu;
#define ov_magic
ovu.ovu_magic
#define ov_index
ovu.ovu_index
#define ov_rmagic ovu.ovu_rmagic
#define ov_size
ovu.ovu_size
};

Bloki pamici mog by zajte lub wolne. W kadym przypadku musz by przechowywane rne wartoci a ze wzgldu na fakt, e blok nie moe by jednoczenie wolny
i zajty, mog one dzieli t sam przestrze pamici. Dodatkowe koszty programistyczne zwizane z konserwacj takiego rozwizania s amortyzowane tysicami elementw,
ktre s przydzielane w wyniku wywoa funkcji bibliotecznych.
Warto zwrci uwag na definicj makro, ktra wystpuje po definicji struktury ovu.
Takie definicje s czsto uywane jako skrcona forma odwoywania si bezporednio
do skadowych struktury w ramach unii bez koniecznoci uywania na pocztku nazwy
skadowej unii. Std przedstawiony kod ma posta39
op->ov_index = bucket;

zamiast bardziej rozbudowanego odwoania op->ovu.ovu_index.

3.3.2. Implementacja polimorfizmu


Najczciej wystpujcym powodem wykorzystania unii jest ch zaimplementowania
polimorfizmu. W tym przypadku ten sam obiekt (zwykle reprezentowany przez struktur jzyka C) jest uywany w celu reprezentowania rnych typw. Dane dla takich
rnych typw s przechowywane w oddzielnych skadowych unii. Dane polimorficzne
przechowywane w ramach unii pozwalaj rwnie zaoszczdzi pami w przypadku
rnych konfiguracji. Jednak w tym przypadku unia jest uywana w celu wyraenia
charakterystyki obiektu, ktry przechowuje rne typy danych, a nie zaoszczdzenia
zasobw. Unie uywane w ten sposb s zwykle zawarte w strukturze zawierajcej pole
typu, ktre okrela, jakiego rodzaju dane s przechowywane w unii. To pole jest czsto
reprezentowane za pomoc typu wyliczeniowego, a jego nazw jest zwykle type. Poniszy przykad, cz biblioteki RPC (ang. Remote Procedure Call; zdalne wywoywanie
procedur), zawiera struktur uywan do reprezentowania komunikatw RPC. Obsugiwane s dwa rne rodzaje komunikatw: wywoanie oraz odpowied. Typ wyliczeniowy msg_type stanowi rozrnienie midzy tymi dwoma typami, natomiast unia zawiera struktur z elementami danych dla kadego typu40.
enum msg_type {
CALL=0,
REPLY=1
};
[...]
struct rpc_msg {
u_int32_t
enum msg_type

rm_xid;
rm_direction;

39

netbsdsrc/lib/libc/stdlib/malloc.c: 213.

40

netbsdsrc/include/rpc/rpc_msg.h: 54 158.

Rozdzia 3. Zaawansowane typy danych jzyka C

};

91

union {
struct call_body RM_cmb;
struct reply_body RM_rmb;
} ru;

3.3.3. Uzyskiwanie dostpu


do rnych reprezentacji wewntrznych
Ostatnie uycie unii jest zwizane z przechowywaniem danych w ramach jednego pola
unii oraz uzyskiwaniem dostpu do innego w celu przeniesienia danych midzy rnymi reprezentacjami wewntrznymi. Cho tego rodzaju rozwizania s z gruntu nieprzenone, mona bez przeszkd przeprowadza pewne konwersje. Inne s przydatne
w pewnych okrelonych przypadkach, zalenych od danej maszyny. Ponisza definicja
struktury jest uywana przez program archiwizujcy tar w celu reprezentowania informacji o kadym pliku z archiwum41.
union record {
char
charptr[RECORDSIZE];
struct header {
char name[NAMSIZ];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char chksum[8];
char linkflag;
char linkname[NAMSIZ];
char magic[8];
char uname[TUNMLEN];
char gname[TGNMLEN];
char devmajor[8];
char devminor[8];
} header;
};

Aby umoliwi wykrywanie uszkodzenia danych, w polu chksum jest umieszczana suma
bajtw wszystkich rekordw skadajcych si na plik (wliczajc w to rekord nagwkowy). Programy wykorzystuj skadow unii charptr do iteracyjnego przegldania
danych nagwka bajt po bajcie, obliczajc sum kontroln oraz skadow header
w celu uzyskania dostpu do okrelonych pl nagwka. Ze wzgldu na fakt, e typy
cakowite w jzyku C (wliczajc znaki) s poprawne w zakresie wszystkich moliwych
wzorcw bitowych, jakie mog reprezentowa, uzyskiwanie dostpu do wewntrznej
reprezentacji innych typw jzyka C (wskanikw, liczb zmiennoprzecinkowych i innych typw cakowitych) jako typu cakowitego jest z zaoenia operacj dozwolon.
Jednak operacja odwrotna generowanie innego typu poprzez jego reprezentacj cakowit nie zawsze daje poprawne wyniki. W przypadku wikszoci architektur operacj bezpieczn jest generowanie typw niecakowitych na podstawie danych, ktre
stanowi starsz wersj kopii ich wartoci.
41

netbsdsrc/usr.bin/file/tar.h: 36 54.

92

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

Poza tym wykorzystanie struktur w celu uzyskania dostpu do zalenych od architektury elementw danych w pewnym innym formacie jest dziaaniem z gruntu nieprzenonym. Moe to by przydatne w przypadku interpretowania typu danych w oparciu
o jego reprezentacj lub tworzenia typu danych na podstawie jego reprezentacji. W przykadzie z listingu 3.342 unia u jest uywana w celu uzyskania dostpu do wewntrznej
reprezentacji liczby zmiennoprzecinkowej v w formie mantysy, wykadnika oraz znaku. Taka konwersja jest wykorzystywana do rozbicia liczby zmiennoprzecinkowej na
znormalizowany uamek oraz cakowit potg liczby 2.
Listing 3.3. Uzyskiwanie dostpu do wewntrznej reprezentacji typu przez wykorzystanie unii
double
frexp(double value, int *eptr)
{
union {
double v;

struct {
u_int u_mant2 : 32;
u_int u_mant1 : 20;
u_int u_exp : 11;
u_int u_sign : 1;
} s;

} u;
if (value) {
u.v = value;
*eptr = u.s.u_exp - 1022;
u.s.u_exp = 1022;
return(u.v);
} else {
*eptr = 0;
return((double)0);
}
}

Zwracany wykadnik

Warto jest przechowywana w tym polu


Dostp do reprezentacji wewntrznej odbywa si przez to pole
Mantysa

Wykadnik

Zachowanie wartoci
Pobranie i ustawienie wykadnika ze znakiem
Wykadnik zerowy
Zwrcenie znormalizowanej mantysy

wiczenie 3.9. Zlokalizuj 20 rnych wystpie unii na pycie doczonej do ksiki


i sklasyfikuj powody ich wykorzystania. Zminimalizuj czas powicony kademu wystpieniu. Utwrz wykres ilustrujcy czstotliwo uywania tej konstrukcji w zalenoci
od powodu.
wiczenie 3.10. Zaproponuj przenon alternatyw wzgldem implementacji nieprzenonych konstrukcji uywanych w przypadku struktur i unii. Omw swoj propozycj
w kontekcie kosztw implementacyjnych, moliwoci konserwacji oraz wydajnoci.

3.4. Dynamiczne przydzielanie pamici


Struktura danych, ktrej rozmiar nie jest znany w momencie pisania programu lub wzrasta
w podczas pracy programu, jest przechowywana w pamici przydzielanej dynamicznie
w trakcie jego dziaania. Programy odwouj si do pamici przydzielanej dynamicznie dziki uyciu wskanikw. W niniejszym podrozdziale zostan przedstawione spo42

netbsdsrc/lib/libc/arch/i386/gen/frexp.c: 48 72.

Rozdzia 3. Zaawansowane typy danych jzyka C

93

soby dynamicznego przydziau pamici w przypadku struktur wektorowych. Jednak


prezentowane fragmenty kodu s bardzo podobne lub identyczne z tymi, ktre su
do przechowywania innych struktur danych.
Na listingu 3.443 przedstawiono typowy przykad sposobu dynamicznego przydzielania
i uywania przestrzeni danych. W tym przypadku pami zostaje przydzielona w celu
przechowywania cigu liczb cakowitych w formie tablicy, tak wic na pocztku zmienna RRlen zostaje zdefiniowana jako wskanik na te liczby (listing 3.4:1). Zmienne wskanikowe musz zosta zainicjalizowane poprzez zdefiniowanie ich wskazania na poprawny
obszar pamici. W opisywanym przypadku program wykorzystuje funkcj biblioteczn
malloc jzyka C w celu otrzymania z systemu adresu obszaru pamici wystarczajco
obszernego, aby przechowywa w nim liczby cakowite c. Argumentem funkcji malloc
jest liczba bajtw, jakie maj zosta przydzielone. Wykonywane tu obliczenia s typowe: jest to iloczyn liczby elementw, dla ktrych ma zosta przydzielona pami (c)
oraz rozmiaru kadego elementu (sizeof(int)). Jeeli pami systemowa zostanie
wyczerpana, funkcja malloc wskazuje to, zwracajc warto NULL. Poprawnie napisane
programy powinny zawsze sprawdza wystpienie takiej sytuacji (listing 3.4:2). Od
tego miejsca program moe rozwikywa wskanik RRlen (uywajc notacji [] lub operatora *), tak jakby bya to tablica zawierajca elementy c. Naley jednak pamita, e
nie s to metody rwnowane. Funkcja sizeof zastosowana wzgldem zmiennej tablicowej zwraca rozmiar tablicy (przykadowo 40 dla tablicy zawierajcej 10 czterobajtowych liczb cakowitych), natomiast zastosowana wzgldem wskanika zwraca jedynie
warto wymagan do jego przechowywania w pamici (na przykad 4 w przypadku
wielu wspczesnych architektur). Wreszcie, kiedy przydzielona pami nie jest ju
potrzebna, musi zosta zwolniona poprzez wywoanie funkcji free. Od tego momentu
prba rozwikania wskanika bdzie prowadzi do niezdefiniowanego wyniku. Niezwolnienie pamici jest bdem, ktry moe prowadzi do tego, e program bdzie powodowa wycieki pamici (ang. memory leaks), ktre powoduj stopniowe marnowanie
zasobw pamiciowych systemu.
Listing 3.4. Dynamiczne przydzielanie pamici
int
update_msg(uchar *msg, int *msglen, int Vlist[], int c)
{
[...]
int *RRlen;
[...]
RRlen = (int *)malloc((unsigned)c*sizeof(int));
if (!RRlen)
panic(errno, "malloc(RRlen)");
[...]
for (i = 0; i < c; i++) {
[...]
RRlen[i] = dn_skipname(cp, msg + *msglen);
[...]

43

}
[...]
free((char *)RRlen);
return (n);

netbsdsrc/usr.sbin/named/named/ns_validate.c: 871 1231.

[1] Wskanik na liczb cakowit


Liczba elementw
Rozmiar kadego elementu
[2] Obsuga przypadku wyczerpania pamici

Iteracyjne przejcie po wszystkich elementach


Uycie RRlen jako tablicy

Zwolnienie przydzielonej pamici

94

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

Kiedy pami zostanie wyczerpana, program nie moe zbyt wiele zrobi. W wikszoci
sytuacji jedyn skuteczn strategi postpowania jest wywietlenie komunikatu o bdzie i wyjcie. Z tego wzgldu, oraz w celu uniknicia koniecznoci sprawdzania po
kadym wywoaniu wartoci zwrconej przez funkcj malloc, moe zosta ona otoczona funkcj (zwykle noszc nazw xmalloc), ktra wykonuje owo sprawdzenie i jest
zawsze wywoywana zamiast funkcji malloc, jak w poniszym przykadzie44.
void *
xmalloc(u_int size)
{
void *p;

if ((p = malloc(size)) == NULL)


err(FATAL, "%s", strerror(errno));
return (p);
[...]
oe = xmalloc(s);
(void)regerror(errcode, preg, oe, s);

W pewnych przypadkach rozmiar tablicy jest okrelany w momencie, gdy s do niej


wstawiane elementy. Dzieje si tak zazwyczaj wtedy, kiedy przetwarzaniu podlegaj
dane wejciowe: dane s przechowywane w tablicy, jednak liczba elementw, ktre
musz by przechowane, nie jest znana do momentu a wszystkie one zostan odczytane. Listing 3.545 ilustruje taki przypadek. Zanim element zostanie zachowany, biecy
indeks tablicy w stosunku do rozmiaru elementw w przydzielonym bloku pamici
poddawany jest sprawdzeniu. Jeeli wymagana jest wiksza ilo pamici, zostaje wywoania funkcja biblioteczna jzyka C realloc w celu dostosowania przestrzeni, na
ktr wskazuje pierwszy argument wywoania do nowego rozmiaru okrelonego przez
drugi argument funkcji. Funkcja zwraca wskanik na dostosowany blok pamici, poniewa jego adres moe by inny od adresu oryginalnego bloku. W takim przypadku
zawarto oryginalnego bloku jest kopiowana do nowej lokacji. Naley pamita, e
wszelkie zmienne wskanikowe wskazujce na lokacje nalece do oryginalnego bloku
bd od tego momentu wskazywa na niezdefiniowane dane. W przykadzie z listingu
3.5 rozmiar bloku pamici jest liniowo zwikszany po 16 bajtw za kadym razem, gdy
przydzielona pami zostanie wyczerpana. Czsto spotyka si rwnie wykadniczy
przyrost przydzielanej przestrzeni46.
if (cur_pwtab_num + 1 > max_pwtab_num) {
/* need more space in table */
max_pwtab_num *= 2;
pwtab = (uid2home_t *) xrealloc(pwtab,
sizeof(uid2home_t) * max_pwtab_num);

Listing 3.5. Dostosowanie przydziau pamici


void
remember_rup_data(char *host, struct statstime *st)
{
if (rup_data_idx >= rup_data_max) {
rup_data_max += 16;
44

netbsdsrc/usr.bin/sed/misc.c: 63 107.

45

netbsdsrc/usr.bin/rup/rup.c: 146 164.

46

netbsdsrc/usr.sbin/amd/hlfsd/homedir.c: 521 525.

Czy indeks wikszy od przydzielonego rozmiaru?


Nowy rozmiar

Rozdzia 3. Zaawansowane typy danych jzyka C


rup_data = realloc (rup_data,
rup_data_max * sizeof(struct rup_data));
if (rup_data == NULL) {
err (1, "realloc");
}

}
rup_data[rup_data_idx].host = strdup(host);
rup_data[rup_data_idx].statstime = *st;
rup_data_idx++;
}

95
Dostosowanie przydziau

Przechowanie danych
Nowy indeks

3.4.1. Zarzdzanie woln pamici


Powyej wspomniano, e niezwalnianie pamici prowadzi do sytuacji, w ktrych programy powoduj jej wyciekanie. Jednak czsto mona spotka takie programy, w ktrych przydziela si, ale nie zwalnia pamici47. Ich twrcy mog sobie na to pozwoli,
jeeli dziaanie programw jest krtkie w momencie zakoczenia dziaania caa
przydzielona programowi pami jest automatycznie przejmowana przez system operacyjny. Podobnie jest w przypadku implementacji programu skeyinit48.
int
main(int argc, char *argv[])
{
[...]
skey.val = (char *)malloc(16 + 1);
[... brak wywoania free(skey.val) ]
exit(1)
}

Program skeyinit jest uywany do zmiany hasa lub dodania uytkownika w systemie
uwierzytelniania Bellcore S/Key. Wykonuje on swoje dziaania, a potem natychmiast
koczy dziaanie, zwalniajc zajmowan pami. Jednak taka nieostrona praktyka kodowania moe powodowa problemy, kiedy ten sam kod zostanie wykorzystany ponownie w programie o znacznie duszym czasie dziaania (na przykad jako cz systemu
oprogramowania routera). W takim przypadku program bdzie powodowa wycieki
pamici, co mona sprawdzi korzystajc z polecenia przegldania procesw, takiego
jak ps lub top w systemach uniksowych albo menedera zada w systemie Windows.
Warto zauway, e w powyszym przypadku mona by po prostu uy rozwizania,
gdzie program ustawiaby warto skey.val tak, aby zmienna wskazywaa na tablic
o staym rozmiarze, ktrej pami przydzielono jako zmiennej lokalnej na stosie.
main (int argc, char *argv[])
{
char valspace[16 + 1];
[...]
skey.val = valspace;

Czsto popenianym przez pocztkujcych programistw piszcych w jzykach C i C++


bdem jest przyjcie zaoenia, e wszystkie wskaniki musz zosta zainicjalizowane
tak, aby wskazyway na bloki pamici przydzielone za pomoc funkcji malloc. Cho
kod zapisany w takim stylu nie jest bdny, program wynikowy jest czsto trudniej czyta
i konserwowa.
47

netbsdsrc/bin/mv/mv.c: 260.

48

netbsdsrc/usr.bin/skeyinit/skeyinit.c: 34 233.

96

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

W kilku przypadkach mona spotka programy, ktre zawieraj mechanizm przywracania pamici (ang. garbage collector), automatycznie zwalniajcy nieuywan pami.
Taka technika jest czsto wykorzystywana w sytuacji, gdy przydzielone bloki pamici
jest trudno ledzi, poniewa na przykad s one wspuytkowane przez rne zmienne.
W takich sytuacjach z kadym blokiem zostaje zwizany licznik referencji (ang. reference count). Jego warto jest zwikszana za kadym razem, gdy zostaje utworzone
nowe odwoanie do bloku49:
req.ctx = ctx;
req.event.time = time;
ctx->ref_count++;

i zmniejszana za kadym razem, gdy referencja zostaje zniszczona50.


XtFree((char*)req);
ctx->req = NULL;
ctx->ref_count--;

Kiedy licznik referencji osiga warto 0, blok nie jest duej uywany i moe zosta
zwolniony51.
if (--ctx->ref_count == 0 && ctx->free_when_done)
XtFree((char*)ctx);

Inne, rzadziej stosowane podejcie polega na uyciu konserwatywnego mechanizmu


przywracania pamici (ang. conservative garbage collector), ktry bada ca pami
procesu, szukajc adresw odpowiadajcych istniejcym, przydzielonym blokom pamici. Wszystkie bloki, ktre nie zostan znalezione w toku procesu przegldania, s
pniej zwalniane.
W kocu, niektre wersje bibliotek jzyka C implementuj niestandardow funkcj
o nazwie alloca. Przydziela ona blok pamici uywajc takiego samego interfejsu jak
malloc, jednak zamiast przydziela blokowi pami na stercie (ang. heap) programu
(jest to pami oglnego przeznaczenia naleca do programu) przydziela j ona na
stosie (ang. stack) programu (jest to obszar uywany do przechowywania adresw
zwrotnych funkcji oraz zmiennych lokalnych)52.
int
ofisa_intr_get(int phandle, struct ofisa_intr_desc *descp,
int ndescs)
{
char *buf, *bp;
[...]
buf = alloca(i);

Blok zwrcony przez funkcj alloca jest automatycznie zwalniany, kiedy nastpuje
powrt z funkcji, w ktrej j przydzielono. Nie ma potrzeby wywoywania funkcji free
w celu zwolnienia przydzielonego bloku. Oczywicie, adres pamici przydzielonej przez
funkcj alloca nie powinien nigdy by przekazywany do podprogramw wywoujcych
funkcj, w ktrej j przydzielono, gdy w momencie wyjcia z funkcji adres ten staje si
49

XFree86-3.3/xc/lib/XT/Selection.c: 1540 1542.

50

XFree86-3.3/xc/lib/XT/Selection.c: 744 746.

51

XFree86-3.3/xc/lib/XT/Selection.c: 563 564.

52

netbsdsrc/sys/dev/ofisa/ofisa.c: 225 244.

Rozdzia 3. Zaawansowane typy danych jzyka C

97

niepoprawny. Funkcji alloca nie naley uywa w pewnych rodowiskach programistycznych, takich jak FreeBSD, gdy jest ona uwaana za nieprzenon i zalen od
danej maszyny. W innych przypadkach, takich jak rodowisko GNU, jej uywanie
jest zalecane, gdy redukuje liczb przypadkowych wyciekw pamici.

3.4.2. Struktury z dynamicznie przydzielanymi


tablicami
Niekiedy pojedyncza przydzielona dynamicznie struktura jest uywana w celu przechowywania pewnych pl oraz tablicy zawierajcej specyficzne dla struktury dane
o zmiennej dugoci. Taka konstrukcja jest wykorzystywana w celu uniknicia porednioci wskanikw oraz narzutu pamiciowego zwizanego z posiadaniem przez element struktury wskanika na dane o zmiennej dugoci. Zatem zamiast definicji podobnej
do poniszej53:
typedef struct {
XID
id_base;
[...]
unsigned char
*data;
unsigned long
data_len;
} XRecordInterceptData;

/* in 4-byte units */

uyto by podobnej do nastpujcej54:


typedef struct {
char *user;
char *group;
char *flags;
char data[1];
} NAMES;

Tablica data element struktury jest uywana jako miejsce na faktyczne dane.
W momencie przydziau pamici przechowujcej struktur jej rozmiar jest rozszerzany
w oparciu o liczb elementw w tablicy data. Od tego momentu element tablicowy
jest uywany tak, jakby zawiera przestrze dla tych elementw55.
if ((np = malloc(sizeof(NAMES) +
ulen + glen + flen + 3)) == NULL)
err(1, "%s", "");
np->user = &np->data[0];
(void)strcpy(np->user, user);

Warto zauway, w jaki sposb w powyszym przykadzie przydzielana jest pami


o jeden bajt wiksza od faktycznie potrzebnej. Rozmiar bloku pamici jest obliczany
jako suma rozmiaru struktury oraz powizanych elementw danych: rozmiaru trzech
cigw znakw (ulen, glen, flen) i odpowiednich trzech znakw zerowych koczcych
cigi. Jednak rozmiar struktury uwzgldnia ju jeden bajt dla powizanych danych,
ktry nie jest brany pod uwag w czasie obliczania rozmiaru bloku pamici. Zarzdzanie pamici na najniszym poziomie jest dziaaniem ryzykownym i podatnym na bdy.
53

XFree86-3.3/xc/include/extensions/record.h: 99 108.

54

netbsdsrc/bin/ls/ls.h: 69 74.

55

netbsdsrc/bin/ls/ls.c: 470 475.

98

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

wiczenie 3.11. Zlokalizuj na pycie doczonej do ksiki redniej wielkoci program


napisany w jzyku C, ktry wykorzystuje dynamiczne przydzielanie pamici. Oce
odsetek caoci kodu programu zwizany z zarzdzaniem dynamicznie przydzielanymi
strukturami. Oszacuj warto ponownie, zakadajc, e zwalnianie pamici jest wykonywane automatycznie przez mechanizm czyszczenia pamici, tak jak ma to miejsce
w przypadku programw pisanych w jzyku Java.
wiczenie 3.12. Wikszo wspczesnych rodowisk programistycznych oferuje specjalizowane biblioteki, opcje kompilacji lub inne narzdzia suce do wykrywania
wyciekw pamici. Zidentyfikuj mechanizmy dostpne w Twoim rodowisku i uyj
ich w przypadku trzech rnych programw. Przeanalizuj otrzymane wyniki.

3.5. Deklaracje typedef


Przykady z poprzedniego podrozdziau zawieray deklaracje typedef suce do tworzenia nazw nowych typw danych. Deklaracja typedef dodaje now nazw (synonim)
dla ju istniejcego typu. Zatem po umieszczeniu poniszej deklaracji56:
typedef unsigned char cc_t;

widzc zapis cc_t naley go odczytywa jako unsigned char. Programy pisane w jzyku
C uywaj deklaracji typedef w celu zapewnienia obsugi abstrakcji, zwikszenia czytelnoci kodu, zapobieenia problemom zwizanym z przenonoci oraz emulowania
mechanizmu deklaracji klas znanego z jzykw C++ oraz Java.
Wsplne uycie typw przedrostkowych i przyrostkowych w deklaracjach jzyka C
czasem sprawia, e odczytanie deklaracji typedef staje si utrudnione57.
typedef char ut_line_t[UT_LINESIZE];

Jednake rozszyfrowanie takich deklaracji nie nastrcza wikszych problemw. Wystarczy traktowa zapis typedef jako specyfikator przechowywania klasy, podobny do
extern lub static, i odczytywa deklaracj jako definicj zmiennej.
static char ut_line_t[UT_LINESIZE];

Nazwa definiowanej zmiennej (w powyszym przypadku ut_line_t) jest nazw typu.


Typem zmiennej jest typ odpowiadajcy tej nazwie.
Kiedy deklaracja typedef jest uywana jako mechanizm abstrakcyjny, nazwa abstraktu
jest definiowana jako synonim jego konkretnej implementacji. W rezultacie kod, ktry
wykorzystuje zadeklarowan nazw, jest lepiej udokumentowany, gdy jest on zapisany
w kontekcie odpowiednio nazwanej konstrukcji abstrakcyjnej, a nie przypadkowych
szczegw implementacji.

56

netbsdsrc/libexec/telnetd/defs.h: 124.

57

netbsdsrc/libexec/rpc.rusersd/rusers_proc.c: 86.

Rozdzia 3. Zaawansowane typy danych jzyka C

99

W poniszym przykadzie DBT definiuje baz danych thang, struktur zawierajc klucz
lub element danych58.
typedef struct {
void *data;
size_t size;
} DBT;

/* data */
/* data length */

Po tej deklaracji wszystkie podprogramy obsugi dostpu do bazy danych s definiowane jako dziaajce na obiektach DBT (i innych zdefiniowanych za pomoc deklaracji
59
typedef) .
int
int
int
int

__rec_get (const DB *, const DBT *, DBT *,


__rec_iput (BTREE *, recno_t, const DBT *,
__rec_put (const DB *dbp, DBT *, const DBT
__rec_ret (BTREE *, EPG *, recno_t, DBT *,

u_int);
u_int);
*, u_int);
DBT *);

Ze wzgldu na fakt, e w przypadku jzykw C i C++ szczegy sprztowe typw danych


jzyka zale od odpowiedniej architektury, kompilatora oraz systemu operacyjnego,
deklaracje typedef s czsto uywane w celu zwikszenia przenonoci programu albo
poprzez utworzenie przenonych nazw dla znanych wielkoci sprztowych, albo przez
szereg deklaracji zalenych od implementacji60
typedef
typedef
typedef
typedef
typedef
typedef
typedef
typedef

__signed
unsigned
short
unsigned
int
unsigned
long
unsigned

char
char
short
int
long

int8_t;
u_int8_t;
int16_t;
u_int16_t;
int32_t;
u_int32_t;
int64_t;
u_int64_t;

albo tworzc abstrakcyjne nazwy dla wielkoci o znanej reprezentacji sprztowej, przy
uyciu jednej z wczeniej zadeklarowanych nazw61.
typedef u_int32_t in_addr_t;
typedef u_int16_t in_port_t;

Wreszcie deklaracje typedef s rwnie czsto uywane w celu emulowania znanego


z jzykw C++ i Java mechanizmu, kiedy to deklaracja klasy wprowadza nowy typ.
W programach pisanych w C czsto spotyka si deklaracj typedef uyt w celu wprowadzenia nazwy typu dla struktury (jest to najblisza klasie konstrukcja wystpujca
w C) identyfikowanej przez t sam nazw. Zatem poniszy przykad deklaruje path
jako synonim dla struct path62.
typedef struct path path;
struct path {
[...]

58

netbsdsrc/include/db.h: 72 75.

59

netbsdsrc/lib/libc/db/recno/extern.h: 47 50.

60

netbsdsrc/sys/arch/alpha/include/types.h: 61 68.

61

netbsdsrc/sys/arch/arm32/include/endian.h: 61 62.

62

netbsdsrc/sbin/mount_portal/conf.c: 62 63.

100

Czytanie kodu. Punkt widzenia twrcw oprogramowania open-source

wiczenie 3.13. Zlokalizuj na pycie doczonej do ksiki pi rnych wystpie


kadego rodzaju uycia deklaracji typedef, o ktrych bya mowa.
wiczenie 3.14. W jaki sposb deklaracje typedef mog negatywnie wpywa na
czytelno kodu?

Dalsza lektura
Jeeli zagadnienia omwione w niniejszym rozdziale nie s znane Czytelnikowi, warto
poszerzy swoj znajomo jzyka C dziki lekturze pozycji [KR88]. Teoretyczne podstawy implementacji rekurencyjnych typw danych bez jawnego uycia wskanikw
przedstawiono w pozycji Hoareego [Hoa73]. Uycie wskanikw w jzyku C zostao
zwile przedstawione w pozycji Sethiego i Stonea [SS96], za wiele puapek zwizanych z ich uyciem omwiono w pozycji Koeniga [Koe88, s. 27 46]. W pozycji
[CWZ90] zawarto analiz wskanikw i struktur. Istnieje wiele interesujcych artykuw powiconych manipulowaniu strukturami danych opartymi na wskanikach
i wykorzystywaniu waciwoci wskanikw [FH82, Suz82, LH86]. Opis sposobu realizacji funkcji wirtualnych w implementacjach jzyka C++ (jak rwnie, zgodnie
z informacjami podanymi w niniejszym rozdziale, w jzyku C) mona znale w pozycji Ellis i Stroustrupa [ES90, s. 217 237]. Algorytmy zwizane z dynamicznym przydzielaniem pamici omwiono w pozycji Knutha [Knu97, s. 435 452], za zwizane
z nimi praktyczne implikacje w dwch innych rdach [Bru82, DDZ94]. Pojcie mechanizmu oczyszczania pamici z licznikiem referencji omwiono w pozycji Christophera
[Chr84], natomiast zarys implementacji konserwatywnego mechanizmu oczyszczania
pamici mona znale w pozycji Boehma [Boe88].

You might also like