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;
}

Podobne dokumenty