8 ŚRODOWISKO SYSTEMU UNIX___________
Funkcja morecore otrzymuje pamięć od systemu operacyjnego. Szczegóły jej realizacji są różne w różnych systemach. Pobieranie pamięci od systemu jest operacją dosyć kosztowną, nie chcemy więc tego robić przy każdym wywołaniu funkcji malloc. Wobec tego funkcja morecore żąda co najmniej NALLOC porcji; taki duży blok będzie w miarę potrzeby „posiekany*’ na mniejsze kawałki. Po wypełnieniu składowej rozmiaru morecore wprowadza ten blok na scenę, wywołując funkcję free.
W systemie Unix odwołanie systemowe sbrk(n) zwraca wskaźnik do nowej porcji n bajtów pamięci. Jeżeli brakuje pamięci, to sbrk zwraca -1, chociaż NULL byłoby bardziej odpowiednie. Wartość -1 koniecznie trzeba zrzutować do typu char *, aby mogła być porównywana z wartością zwracaną przez funkcję. I znów dzięki rzutowaniu funkcja jest względnie odporna na szczegóły reprezentacji wskaźników w różnych maszynach. Niemniej jednak ciągle przyjmuje się założenie, że wskaźniki do różnych bloków dostarczanych przez sbrk mogą być sensownie porównywane. Tego standard nie gwarantuje; pozwala jedynie na porównania wskaźników w obrębie jednej tablicy. A zatem nasza wersja funkcji malloc jest przenośna tylko wśród tych maszyn, w których ogólne porównywanie wskaźników ma sens.
#define NALLOC 1024 /* minimalna liczba żądanych porcji */
/* morecore: poproś system o pamięć dla nu porcji */ static Header *morecore(unsigned nu)
char *cp, *sbrk(int);
Header *up;
if (nu < NALLOC) nu = NALLOC;
cp = sbrk(nu * sizeof(Header)); if (cp == (char *) -1) /* nie ma wolnej pamięci */ return NULL; up = (Header *) cp; up->s.size = nu; free((void *)(up+1)); return freep;
} : '' ; V :! ,•. jiii
Pozostała nam do napisania tylko funkcja free. W poszukiwaniu miejsca dla zwal' nianego bloku przegląda ona łańcuch wolnych bloków, rozpoczynając od freep. Miej-
8.7 PRZYKŁAD - DYSTRYBUTOR PAMIĘCI_.
sce to znajduje się albo między dwoma istniejącymi ców łańcucha. W każdym przypadku, jeżeli zwalniany blok przylega do któregokolwiek z sąsiadów, to przylegające bloki zostaną sklejone. Jedynym problemem jest zapewnienie, aby wskaźniki poprawnie wskazywały na właściwe obiekty o właściwych rozmiarach.
/* free: zwróć blok ap do łańcucha wolnych bloków */ void free(void *ap)
Header *bp, *p;
bp = (Header *)ap - 1; /* wskaż na nagłówek */ for (p = freep; ! (bp > p && bp < p->s.ptr); p = p->s.ptr) if (p >= p->s.ptr && (bp > p 11 bp < p—>s.ptr))
break; /* miejsce na jednym z końców łańcucha */
if (bp + bp->s.size == p—>s.ptr) { /* połącz z następnym blokiem */ bp->s.size += p->s.ptr->s.size; bp->s.ptr = p->s.ptr->s.ptr;
} else
bp->s.ptr = p->s.ptr;
if (p + p->s.size == bp) { /* połącz z poprzednim blokiem */ p->s.size += bp->s.size; p->s.ptr = bp->s.ptr;
} else
p->s.ptr = bp; /* samodzielny blok */
freep = p;
Przydział pamięci istotnie zależy od maszyny, pokazane przykłady ilustrują jednak, jak zależności te można kontrolować i ograniczać do bardzo małego fragmentu programu. Zastosowanie konstrukcji typedef razem z union zapewnia prawidłowe położenie obszaru (pod warunkiem, że funkcja sbrk daje odpowiedni wskaźnik). Rzuty wymuszają jawne przekształcenia wskaźników, a nawet radzą sobie ze źle zdefiniowanym łączem z systemem operacyjnym. Choć uwagi te dotyczą przydziału pamięci, takie ogólne podejście można równie dobrze stosować w wielu innych sytuacjach.
Ćwiczenie 8.6. Funkcja calloc(n,size) z biblioteki standardowej zwraca wskaźnik do n obiektów o rozmiarze size, których pamięć została wypełniona zerami.
249