instrukcja
Transkrypt
instrukcja
03 Metodyka i Techniki Programowania II Dynamiczne zarządzanie pamięcią dr inż. Jarosław Bułat 2013.03.10 Materiał tego laboratorium będzie poświęcony dynamicznemu zarządzaniu pamięcią i związanymi z nim technikami programowania. Ćwiczenie 1 przydzielanie pamięci Deklarując zmienną (np. tablicę tabStat w poniższym przykładzie) rezerwowane jest miejsce na stosie dla tej zmiennej. Jest to tzw. zmienna automatyczna. Czas jej istnienia określają odpowiednie reguły i w tym przypadku będzie ona ,,żyła'' do końca funkcji main(...), po czym miejsce na stosie zostanie zwolnione. Taki rodzaj zmiennych jest łatwy w użyciu, lecz nie mamy kontroli nad czasem ich ,,życia'', w przypadku tablic rozmiar musi być znany w momencie kompilacji kodu a zmienne przechowywane są na stosie, którego rozmiar jest ograniczony (np. 8 MB dla współczesnych systemów Linux). #include <stdio.h> #include <stdlib.h> #define TABSIZE 100 int main(void) { int tabStat[TABSIZE]; // pamięć rezerwowana statycznie (stos) int *tabDyn; // pamięć rezerwowana dynamicznie (sterta) int tabSize = 100; tabDyn = (int *)malloc( tabSize*sizeof(int) ); // tabDyn jest wskaźnikiem na pierwszy element 100-elementowej tablicy // zmiennych typu int free( tabDyn ); return 0; } Dynamiczne przedzielanie pamięci likwiduje ww. wady kosztem większej ilości pracy i odpowiedzialności spadającej na programistę. Funkcja malloc(...) służy do dynamicznej rezerwacji pamięci. Jako argument przyjmuje wielkość rezerwowanej pamięci. Funkcja ta zwraca wskaźnik na początek zarezerwowanej pamięci. Przykładowy sposób użycia został podany w drugiej części przykładu. Warto zauważyć, że wielkość rezerwowanej pamięci może być zmienną, której wartość będzie znana dopiero podczas wykonywania programu. Zarezerwowaną pamięć należy obowiązkowo zwolnić w chwili gdy wiemy, że nie będzie więcej używana. Służy do tego celu funkcja free(...). Funkcja rezerwująca pamięć zwraca wskaźnik na zarezerwowany obszar pamięci. Niestety konstrukcja języka ,,C'' nie pozwala na zwrócenie wskaźnika odpowiedniego typu więc zawsze jest to wskaźnik typu void. Oznacza to, że musimy go zamienić (rzutować) na odpowiedni typu (w tym przypadku int*). Zadania: 1. Wpisz liczby 0...99 do tablicy tabStat, przepisz je do tablicy tabDyn, wyświetl na ekranie w celu weryfikacji. 2. Wykonaj to samo, tylko zamiast przepisywać skopiuj pamięć zajmowaną przez tablicę za pomocą funkcji memcpy(...). 3. Funkcja calloc(...) różni się od malloc(...) tym, że zeruje zarezerwowaną pamięć. Napisz kod weryfikujący tą cechę. 4. Zwiększ TABSIZE do 1000000, wstaw ww. kod w pętlę wykonującą się 1e6 razy. Kod powinien wykonać się poprawnie. Następnie usuń linijkę z funkcją free(...) i sprawdź co się stanie – będzie to symulacja typowego błędy programisty, który zapomniał zwolnić pamięć. Ćwiczenie 2 dynamiczna rezerwacja struktur Pamięć można przydzielać dla dowolnych zmiennych. Poniżej przedstawiono kod rezerwujący statycznie i dynamicznie pamięć dla struktur. #include <stdio.h> #include <stdlib.h> struct record{ int wiek; char nazwisko[30]; char imie[20]; }; int main() { struct struct struct struct record record record record rec1Stat; rec2Stat[10]; *rec1Dyn = (struct record*)malloc( 1*sizeof(struct record) ); *rec2Dyn = (struct record*)malloc( 10*sizeof(struct record) ); free( rec1Dyn ); free( rec2Dyn ); return 0; } Zadania: 1. Wypełnij strukturę rec1Stat, przepisz ją do rec1Dyn. 2. Wypełnij 3-cią strukturę z tablicy rec2Stat i przepisz ją do 5-tej struktury z tablicy rec2Dyn. 3. Wyświetl na ekranie zawartość wypełnionych struktur aby zweryfikować poprawność operacji. W powyższym przykładzie, struktura posiada tablice o z góry założonej długości do przechowywania imienia i nazwiska. Jest to nieefektywne rozwiązane ze względu na zajmowaną pamięć. Zmodyfikuj deklarację typu struktury na następującą: struct record{ int wiek; char nazwisko*; char imie*; }; i użyj w powyższym przykładzie. Zadanie: Wypełnij wyłącznie drugi element rec2Dyn. Wczytaj wszystkie dane z klawiatury. Dla imienia i nazwiska zarezerwuj dokładnie tyle miejsca ile trzeba. Ilość miejsca będzie znana po wczytaniu danych z klawiatury. Pamiętaj, że przy zwalnianiu pamięci, najpierw należy zwolnić pamięć dla nazwiska i mienia a następnie dla struktury. Ćwiczenie 3 (nadobowiązkowe) lista jednokierunkowa A co jeżeli chcemy zmienić rozmiar dynamicznie zarezerwowanej tablicy w trakcie działania programu? Można do tego celu użyć funkcji realloc(...) lub wykorzystać listę jednokierukową. Jest to technika programowania szczególnie użyteczna przy tworzeniu ciągów struktur o zmiennej długości. Poniższy kod prezentuje taki przykład takiej listy. Przeanalizuj kod, uruchom a następnie uzupełnij. #include <stdio.h> #include <stdlib.h> typedef struct node{ int wiek; char nazwisko[20]; struct node *next; }Node; int main(void) { Node *head, *index; index = (Node *)malloc( sizeof(Node) ); head = index; // pierwszy element index->next = (Node *)malloc( sizeof(Node) ); index = index->next; // drugi element index->next = (Node *)malloc( sizeof(Node) ); index = index->next; // trzeci element index->next = NULL; // następnego elementu nie będzie // ustawianie wszystkich pól ,,wiek'' index = head; while( NULL != index ){ index->wiek = 18; index = index->next; } // dopisanie na końcu nowej struktury index = head; while( NULL != index->next ) index=index->next; index->next = (Node *)malloc( sizeof(Node) ); index = index->next; index->next = NULL; // czwarty element // wypisywanie wszystkich pól ,,wiek'' index = head; while( NULL != index ){ printf( "%d\n", index->wiek ); index = index->next; } // zaprojektuj funkcję rekurencyjną która zwróci wskaźnik do ostatniej // struktury na liście, wykorzystaj ją dodaj ,,Piąty element'' // ToDo // usuń drugi element listy // ToDo // usuń wszystkie elementy listy przy założeniu że nie wiesz jak długa jest lista // ToDo return 0; }