Presentation is loading. Please wait.

Presentation is loading. Please wait.

הקצאות דינאמיות קרן כליף. © Keren Kalif 2 ביחידה זו נלמד:  מוטיבציה להקצאות דינאמיות  מהי הקצאה דינאמית  יצירת מערך בגודל שאינו ידוע מראש  החזרת מערך.

Similar presentations


Presentation on theme: "הקצאות דינאמיות קרן כליף. © Keren Kalif 2 ביחידה זו נלמד:  מוטיבציה להקצאות דינאמיות  מהי הקצאה דינאמית  יצירת מערך בגודל שאינו ידוע מראש  החזרת מערך."— Presentation transcript:

1 הקצאות דינאמיות קרן כליף

2 © Keren Kalif 2 ביחידה זו נלמד:  מוטיבציה להקצאות דינאמיות  מהי הקצאה דינאמית  יצירת מערך בגודל שאינו ידוע מראש  החזרת מערך מפונקציה  הקצאת מערך של מערכים  הגדלת מערך  הפונקציה strdup  דוגמאות משולבות של מבנים עם הקצאות דינאמיות

3 © Keren Kalif 3 מוטיבציה להקצאה דינאמית  בעזרת הקצאה דינאמית נוכל:  להקצות מערך בזמן ריצה, בלי לדעת את גודלו בזמן קומפילציה  נוכל להחזיר מערך מפונקציה

4 © Keren Kalif 4 מהי הקצאה דינאמית?  הקצאה דינאמית היא הקצאת שטח זיכרון בגודל מבוקש בזמן ריצת התוכנית  בניגוד להקצאה סטטית שמוקצית בתחילת התוכנית וגודלה ידוע כבר בזמן קומפילציה  הקצאה דינאמית מוקצית על שטח הזיכרון heap  בניגוד להקצאה סטטית שמוקצית על ה- stack של הפונקציה  ה- heap הוא שטח זיכרון המשותף לכל הפונקציות, בניגוד ל- stack  בהקצאת זיכרון דינאמית המתכנת מבקש ממערכת ההפעלה זיכרון בגודל מסוים המוגדר בבתים ומקבל את כתובת הבית הראשון בקטע הזיכרון שקיבל

5 © Keren Kalif 5 הגדרה נוספת ל- *  אנו יודעים שמשתנה מטיפוס * מכיל כתובת של משתנה מטיפוס כלשהו  כתובת התחלה של מערך מטיפוס היא גם כתובתו של האיבר הראשון במערך, שהוא משתנה מטיפוס  לכן משתנה מטיפוס * יכול להכיל גם כתובת התחלה של מערך  לכן נאמר ש- * הוא פוטנציאל למערך  כלומר, יכול להכיל כתובת התחלה של מערך (ולא רק כתובת של משתנה יחיד מטיפוס )

6 © Keren Kalif 6 פונקציות לשימוש בהקצאות דינאמיות  כדי לעבוד עם הקצאות דינאמיות יש להכליל את הספריה stdlib.h לצורך שימוש בפונקציות הבאות:  void* malloc(size_t size)  הפונקציה מקבלת את גודל הזיכרון המבוקש בבתים ומחזירה כתובת לתחילת שטח הזיכרון שהוקצה, ו-NULL במקרה שלא היה מספיק זיכרון פנוי להקצאה  נשים לב ש- size_t הוא typedef ל- int, כלומר הפונקציה מקבלת את מספר הבתים שעליה להקצות  הפונקציה מחזירה void* שזה הכללה למצביע מכל טיפוס  שטח הזיכרון המתקבל מכיל זבל  void* calloc(size_t n,size_t size_el)  כמו malloc אבל מקבלת את כמות האיברים שרוצה להקצות, ומה הגודל של כל איבר. מקצה את השטח ומאפסת אותו.  מקצה מערך של n איברים כל איבר בגודל size_el בתים, כל ביט מאותחל לאפס. קריאה מוצלחת תחזיר את כתובת ההתחלה של הזיכרון המוקצה, ו- NULL במקרה שלא היה מספיק זיכרון פנוי להקצאה

7 © Keren Kalif 7 malloc – הקצאת מערך בגודל שאינו ידוע מראש #include void main() { int size, *arr, i; printf("Please enter the size of the array: "); scanf("%d", &size); arr = (int*)malloc(size*sizeof(int)); if (!arr) // (arr == NULL) --> allocaton didn't succeed { printf("ERROR! Out of memory!\n"); return; } printf("Values in the array: "); for (i=0 ; i < size ; i++) printf("%d ", *(arr+i)); //*(arr+i) == arr[i] printf("\nPlease enter %d numbers: ", size); for (i=0 ; i < size ; i++) scanf("%d", &arr[i]); printf("Values in the array: "); for (i=0 ; i < size ; i++) printf("%d ", arr[i]); printf("\n"); } לצורך שימוש בהקצאות דנאמיות הזיכרון של ה- main int:size ??? 1000 int*: arr ??? 1004 int: i ??? 1008 זיכרון ה- heap int:size int*: arr ??? 1004 int: i ??? 1008 int ??? 3000 int ??? 3004 int ??? 3008 int:size int*: arr int: i ??? 1008 int int int נשים לב שלמשתנה על ה- heap אין שם, אלא רק יש אליו הצבעה מאחת הפונקציות!

8 © Keren Kalif 8 calloc – הקצאת מערך בגודל שאינו ידוע מראש #include void main() { int size, *arr, i; printf("Please enter the size of the array: "); scanf("%d", &size); arr = (int*)calloc(size, sizeof(int)); if (!arr) // (arr == NULL) --> allocaton didn't succeed { printf("ERROR! Out of memory!\n"); return; } printf("Values in the array: "); for (i=0 ; i < size ; i++) printf("%d ", *(arr+i)); //*(arr+i) == arr[i] printf("\nPlease enter %d numbers: ", size); for (i=0 ; i < size ; i++) scanf("%d", &arr[i]); printf("Values in the array: "); for (i=0 ; i < size ; i++) printf("%d ", arr[i]); printf("\n"); } הזיכרון של ה- main int:size ??? 1000 int*: arr ??? 1004 int: i ??? 1008 זיכרון ה- heap int:size int*: arr ??? 1004 int: i ??? 1008 int int int int:size int*: arr int: i ??? 1008 int int int

9 © Keren Kalif 9 שחרור הזיכרון שהוקצה  אחריות המתכנת לשחרר את כל זיכרון שהוקצה דינאמית  במילים אחרות, המתכנת אחראי להחזיר למערכת ההפעלה כל שטח זיכרון שביקש ממנה בזמן ריצה  הסיבה:  שטח ה- heap עליו מוקצות ההקצאות הדינאמיות מוגבל בשטחו ומשותף לכל התוכניות, ואז התוכנית הבאה שתבקש זיכרון עלולה לקבל NULL כי אנחנו לא החזרנו את הזיכרון שביקשנו בסיום העבודה...  צריך לזכור: כאשר יש סביבת עבודה משותפת, צריך להתחשב גם באחרים (וזה שיעור חשוב בכלל לחיים ;-))  הקומפיילר לא מתריע על אי-שחרור הזיכרון ואין שום אינדיקציה לדעת זאת, לכן חייבים לשים לב!!

10 © Keren Kalif 10 פונקציה לשחרור הקצאות דינאמיות void free(void* ptr)  הפונקציה מקבלת את כתובת ההתחלה של הזיכרון שברצוננו לשחרר  הפונקציה מקבלת void* שזה הכללה למצביע מכל טיפוס  גם פונקציה זו נמצאת ב- stdlib.h

11 © Keren Kalif 11 free – שחרור זיכרון #include void main() { int size, *arr, i; printf("Please enter the size of the array: "); scanf("%d", &size); arr = (int*)malloc(size*sizeof(int)); if (!arr) // (arr == NULL) --> allocaton didn't succeed { printf("ERROR! Out of memory!\n"); return; } printf("Values in the array: "); for (i=0 ; i < size ; i++) printf("%d ", *(arr+i)); //*(arr+i) == arr[i] printf("\nPlease enter %d numbers: ", size); for (i=0 ; i < size ; i++) scanf("%d", &arr[i]); printf("Values in the array: "); for (i=0 ; i < size ; i++) printf("%d ", arr[i]); printf("\n"); free(arr); } הזיכרון של ה- main int:size ??? 1000 int*: arr ??? 1004 int: i ??? 1008 זיכרון ה- heap int:size int*: arr ??? 1004 int: i ??? 1008 int ??? 3000 int ??? 3004 int ??? 3008 int:size int*: arr int: i ??? 1008 int int int

12 © Keren Kalif 12 תזכורת: החזרת מערך מפונקציה  ראינו כי כאשר מעבירים מערך לפונקציה, מעבירים רק את כתובת ההתחלה שלו, ולא עותק של כל המערך  ובאופן דומה, כאשר מחזירים מערך שהוגדר בפונקציה, חוזרת כתובת ההתחלה שלו, ולא עותק של כל המערך  הבעייתיות: כאשר יוצאים מהפונקציה שטח הזיכרון שלה משתחרר ויש לנו מצביע לזיכרון שנמחק...  הפתרון: הקצאה דינאמית

13 © Keren Kalif 13 תזכורת: הבעייתיות בהחזרת מערך מפונקציה - דוגמא #define SIZE 3 int* readArray() { int arr[SIZE], i; printf("Please enter %d numbers: ", SIZE); for (i=0 ; i < SIZE ; i++) scanf("%d", &arr[i]); return arr; } void main() { int* arr, i; arr = printf("The array is: \n"); for (i=0 ; i < SIZE ; i++) printf("%d ", arr[i]); printf("\n"); } הקומפיילר נותן warning: returning address of local variable or temporary שפירושה שאנחנו מחזירים כתובת למשתנה בזיכרון שישתחרר הזיכרון של ה- main הזיכרון של readArray int[]: arr ??? 2000 ??? 2004 ??? 2008 int: i ??? 2012 int*: arr ??? 1000 int: i ??? 1004 לעולם לא נחזיר מפונקציה כתובת של משתנה שהוגדר בה מקומית! readArray(); int[]: arr int: i int*: arr int: i ??? 1004

14 © Keren Kalif 14 הפתרון: הקצאת המערך דינאמית #include #define SIZE 3 int* buildArray() { int i; int* arr = (int*)malloc(SIZE*sizeof(int)); if (!arr) { printf("ERROR! Not enough memory!\n"); exit(1); // to exit the program immedietly. // use only when memory allocation fails } for (i=0 ; i < SIZE; i++) arr[i] = i+1; return arr; } void main() { int *arr, i; arr = printf("Values in the array: "); for (i=0 ; i < SIZE ; i++) printf("%d ", arr[i]); printf("\n"); free(arr); } הזיכרון של ה- main זיכרון ה- heap int*: arr ??? 1000 int: i ??? 1004 int ??? 3000 int ??? 3004 int ??? 3008 buildArray(); הזיכרון של buildArray int:i ??? 2000 int*: arr ??? 2004 int:i ??? 2000 int*: arr int int int int*: arr int: i ??? 1004 אם אנחנו יוצאים מפונקציה שהקצתה דינאמית ולא שחררה, חובה להחזיר את כתובת ההתחלה של ההקצאה, כדי שנוכל לשחרר אותה בהמשך! אחרת כאשר נצא מהפונקציה כבר לא יהיה משתנה שיכיל את מיקום ההקצאה!

15 © Keren Kalif 15 הקצאה בתוך פונקציה  אחריות שלנו כמתכנתים לשחרר את כל הזיכרון שהקצינו  יש לשים לב בייחוד במקרים בהם ההקצאה מתבצעת בפונקציה אחת, והשחרור צריך להיות בפונקציה אחרת!

16 © Keren Kalif 16 החזרת מערך מפונקציה by pointer  למדנו שפונקציה יכולה להחזיר ערכים ע"י קבלת כתובת לעדכון התשובה  למשל: int getSum(int arr[], int size);  לעומת: void getSum(int arr[], int size, int* sum);  באותו אופן ניתן גם להחזיר מערך שייוצר בתוך הפונקציה  לא לשכוח להעביר את המערך כ- **

17 © Keren Kalif 17 החזרת מערך מפונקציה by pointer - דוגמא #include void buildArray(int* arr, int size) { int i; arr = (int*)malloc(size*sizeof(int)); if (!arr) } printf("ERROR! Not enough memory!\n"); exit(1); // to exit the program immedietly. // use only when memory allocation fails { for (i=0 ; i < size ; i++) arr[i] = i+1; { void main() { int size, *arr=NULL, i; printf("Please enter the size of the array: "); scanf("%d", &size); buildArray(arr, size); printf("Values in the array: "); for (i=0 ; i < size ; i++) printf("%d ", arr[i]); printf("\n"); free(arr); } הזיכרון של ה- main זיכרון ה- heap int:size ??? 1000 int*: arr ??? 1004 int: i ??? 1008 int ??? 3000 int ??? 3004 int ??? 3008 הזיכרון של buildArray int:size int: i ??? 2004 int*: arr NULL 2008 int int int int:size ??? 1000 int*: arr NULL 1004 int: i ??? 1008 int:size int*: arr NULL 1004 int: i ??? 1008 int:size int: i ??? 2004 int*: arr פה התוכנית תעוף...

18 © Keren Kalif 18 החזרת מערך מפונקציה by pointer - התיקון #include void buildArray(int** arr, int size) } int i; *arr = (int*)malloc(size*sizeof(int)); if (!*arr) } printf("ERROR! Not enough memory!\n"); exit(1); // to exit the program immedietly. // use only when memory allocation fails { for (i=0 ; i < size ; i++) (*arr)[i] = i+1; { void main() { int size, *arr=NULL, i; printf("Please enter the size of the array: "); scanf("%d", &size); buildArray(&arr, size); printf("Values in the array: "); for (i=0 ; i < size ; i++) printf("%d ", arr[i]); printf("\n"); free(arr); } הזיכרון של ה- main זיכרון ה- heap int:size ??? 1000 int*: arr ??? 1004 int: i ??? 1008 int ??? 3000 int ??? 3004 int ??? 3008 הזיכרון של buildArray int:size int: i ??? 2004 int**: arr int int int int:size ??? 1000 int*: arr NULL 1004 int: i ??? 1008 int:size int*: arr NULL 1004 int: i ??? 1008 int:size int*: arr int: i ??? 1008

19 © Keren Kalif 19 הקצאת מערך של מערכים (1)  כעת אנחנו יכולים לייצר מטריצה שבכל שורה יש מספר שונה של איברים

20 © Keren Kalif 20 הקצאת מערך של מערכים (2) #include void main() { int rows, **matrix, i, j, *sizes; printf("Enter number of rows in the matrix: "); scanf("%d", &rows); matrix = (int**)malloc(rows*sizeof(int*)); sizes = (int*)malloc(rows*sizeof(int)); for ( ; ; ) { printf("Enter size of row #%d: ", i+1); scanf("%d", &sizes[i]); matrix[i] = (int*)calloc(sizes[i], sizeof(int)); for (j=0 ; j < sizes[i] ; j++) matrix[i][j] = (i+1)*10+j+1; } printf("The matrix is:\n"); for (i=0 ; i < rows ; i++) { for (j=0 ; j < sizes[i] ; j++) printf("%d ", matrix[i][j]); printf("\n"); } free(sizes); for ( ; ; ) free(matrix[i]); free(matrix); } הזיכרון של ה- main זיכרון ה- heap int* ??? 3000 int* ??? 3004 int* ??? 3008 int:rows ??? 1000 int**: matrix ??? 1004 int: i ??? 1008 int: j ??? 1012 int*: sizes ??? 1016 int:rows int**: matrix ??? 1004 int: i ??? 1008 int: j ??? 1012 int*: sizes ??? 1016 int:rows int**: matrix int: i ??? 1008 int: j ??? 1012 int*: sizes ??? 1016 int ??? 4000 int ??? 4004 int ??? 4008 int:rows int**: matrix int: i ??? 1008 int: j ??? 1012 int*: sizes i=0i < rowsi++ int:rows int**: matrix int: i int: j ??? 1012 int*: sizes int int ??? 4004 int ??? 4008 int int int* int* ??? 3004 int* ??? 3008 int int int:rows int**: matrix int: i int: j ??? 1012 int*: sizes int int int ??? 4008 int int int int* int* int* ??? 3008 int int int int:rows int**: matrix int: i int: j ??? 1012 int*: sizes int int int int int int int int* int* int* int int int int int:rows int**: matrix int: i int: j ??? 1012 int*: sizes i=0i < rowsi++ int:rows int**: matrix int: i int: j ??? 1012 int*: sizes int:rows int**: matrix int: i int: j ??? 1012 int*: sizes int:rows int**: matrix int: i int: j ??? 1012 int*: sizes int:rows int**: matrix int: i int: j ??? 1012 int*: sizes

21 © Keren Kalif 21 הגדלת מערך  בדוגמא הבאה אנו קולטים מהמשתמש מספרים לתוך מערך, לא ידוע כמה איברים המשתמש יכניס  כל פעם כאשר כבר אין מקום במערך צריך להגדיל אותו פי 2  האלגוריתם:  קרא את האיבר החדש, אם 1- צא  אם אין מקום במערך, הקצה מערך גדול פי 2  העתק למערך החדש את האיברים מהמערך הישן  שחרר את המערך המקורי  שנה את מצביע המערך להצביע למערך החדש

22 © Keren Kalif 22 הגדלת מערך - הפלט

23 © Keren Kalif 23 הגדלת מערך – הקוד void main() { int num, i; int physSize = 2, logicSize = 0; int* arr = (int*)calloc(physSize, sizeof(int)); int* tmp; printf("Please enter numbers, -1 to stop:\n"); while (1) { scanf("%d", &num); if (num == -1) break; if (physSize == logicSize) { physSize *= 2; tmp = (int*)calloc(physSize, sizeof(int)); for (i=0 ; i < logicSize ; i++) tmp[i] = arr[i]; free(arr); arr = tmp; printf("Doubled the array size to %d\n", physSize); } printf("Read number is %d\n", num); arr[logicSize] = num; logicSize++; } printf("The array has %d elements (physSize=%d):\n", logicSize, physSize); for (i=0 ; i < logicSize ; i++) printf("%d ", arr[i]); printf("\n"); free(arr); } הזיכרון של ה- main זיכרון ה- heap int int 0 int: num ??? 1000 int: i ??? 1004 int: physSize ??? 1008 int: logicSize ??? 1012 int*: arr ??? 1016 int*: tmp ??? 1020 int: num ??? 1000 int: i ??? 1004 int: physSize int: logicSize int*: arr ??? 1016 int*: tmp ??? 1020 int: num ??? 1000 int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp ??? 1020 int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp ??? 1020 int int 0 int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp ??? 1020 int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp ??? 1020 int int 4 int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp ??? 1020 int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp ??? 1020 int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp ??? 1020 int int int int int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp int int int int int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp int int int int int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp int: num 1000 int: i ??? 1004 int: physSize int: logicSize int*: arr int*: tmp

24 © Keren Kalif 24 הפונקציה realloc  שפת C נותנת לנו פונקציה שיודעת להגדיל מערך:  הפונקציה יודעת להקצות מקום חדש בגודל המבוקש ולהעתיק אליו את האיברים מהמערך הישן, ובסוף גם לשחרר את הזיכרון של המערך המקורי  void* realloc(void* ptr, size_t size; (  הפונקציה מקבלת כפרמטר את כתובת ההתחלה של המערך שאותו ברצונה להגדיל וכן את גודל שטח הזיכרון החדש שרוצים  הפונקציה מחזירה את כתובת ההתחלה של השטח החדש שהוקצה  יתכן וזו תהיה הכתובת המקורית, במידה והיה מספיק מקום להגדלה במקום המקורי בו המערך הוקצה

25 © Keren Kalif 25 הגדלת מערך – הקוד בשימוש realloc void main() { int num, i; int physSize = 2, logicSize = 0; int* arr = (int*)calloc(physSize, sizeof(int)); printf("Please enter numbers, -1 to stop:\n"); while (1) { scanf("%d", &num); if (num == -1) break; if (physSize == logicSize) { physSize *= 2; arr = (int*)realloc(arr, physSize*sizeof(int)); printf("Doubled the array size to %d\n", physSize); } printf("Read number is %d\n", num); arr[logicSize] = num; logicSize++; } printf("The array has %d elements (physSize=%d):\n", logicSize, physSize); for (i=0 ; i < logicSize ; i++) printf("%d ", arr[i]); printf("\n"); free(arr); } הזיכרון של ה- main זיכרון ה- heap int int 0 int: num ??? 1000 int: i ??? 1004 int: physSize ??? 1008 int: logicSize ??? 1012 int*: arr ??? 1016 int int int int int int int int int int int: num ??? 1000 int: i ??? 1004 int: physSize int: logicSize int*: arr ??? 1016 int: num ??? 1000 int: i ??? 1004 int: physSize int: logicSize int*: arr int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int: num int: i ??? 1004 int: physSize int: logicSize int*: arr int: num 1000 int: i ??? 1004 int: physSize int: logicSize int*: arr

26 © Keren Kalif 26 מה יהיה פלט התוכנית הבאה? #include void main() { char* c = (char*)malloc (sizeof(char)*5); char** s = (char**)malloc (sizeof(char*)*3); int i; for( ; ; ) { printf("enter word : "); gets(c); s[i] = c; { for(i=0; i<3; i++) printf("strings : %s \n",s[i]); free(c); free(s); } הזיכרון של ה- main זיכרון ה- heap char*: c ??? 1000 char**: s ??? 1004 int: i ??? 1008 char ??? 4300 char ??? 4301 char ??? 4302 char ??? 4303 char ??? 4304 char*: c char**: s ??? 1004 int: i ??? 1008 char* ??? 5300 char* ??? 5304 char* ??? 5308 char*: c char**: s int: i ??? 1008 i=0i < 3i ++ char*: c char**: s int: i char ‘h’ 4300 char ‘i’ 4301 char char ??? 4303 char ??? 4304 char* char* ??? 5304 char* ??? 5308 char*: c char**: s int: i char ‘b’ 4300 char ‘y’ 4301 char ‘e’ 4302 char char ??? 4304 char* char* char* ??? 5308 char*: c char**: s int: i char ‘g’ 4300 char ‘o’ 4301 char ‘o’ 4302 char ‘d’ 4303 char char* char* char* char*: c char**: s int: i

27 © Keren Kalif 27 התיקון לתוכנית הקודמת void main() { char* c; char** s = (char**)malloc (sizeof(char*)*2); int i; for( ; ; ) { c = (char*)malloc (sizeof(char)*5); printf("enter word : "); gets(c); s[i] = c; { for(i=0; i<2; i++) printf("strings : %s \n",s[i]); for (i=0 ; i < 2 ; i++) free(s[i]); free(s); } הזיכרון של ה- main זיכרון ה- heap char*: c ??? 1000 char**: s ??? 1004 int: i ??? 1008 char ??? 4300 char ??? 4301 char ??? 4302 char ??? 4303 char ??? 4304 char* ??? 5300 char* ??? i=0i < 2i ++ char ‘h’ 4300 char ‘i’ 4301 char char ??? 4303 char ??? 4304 char* char* ??? 5304 char ??? 6300 char ??? 6301 char ??? 6302 char ??? 6303 char ??? 6304 char ‘b’ 6300 char ‘y’ 6301 char ‘e’ 6302 char char ??? 6304 char* char* char*: c ??? 1000 char**: s int: i ??? 1008 char*: c ??? 1000 char**: s int: i char*: c char**: s int: i char*: c char**: s int: i char*: c char**: s int: i char*: c char**: s int: i

28 © Keren Kalif 28 הפונקציה strdup char* strdup(const char *str)  מקבלת מחרוזת ומחזירה העתק שלה:  מקצה דינאמית על ה- heap מערך של תווים בגודל המחרוזת המקורית, מעתיקה אליו את התוכן ומחזירה את כתובת ההתחלה שלו  תחזיר NULL במידה וההקצאה נכשלה  אחריות המתכנת לשחרר את המחרוזת שחזרה!!

29 © Keren Kalif 29 char: str1[] ??? 1000 ??? 1001 ??? 1002 char*: str2 ??? 1003 char*: newStr1 ??? 1007 char*: newStr2 ??? 1011 strdup – דוגמא #include void main() } char str1[] = "hi"; char* str2 = "bye"; char* newStr1 = strdup(str1); char* newStr2 = strdup(str2); printf("The first duplicated string: |%s|\n", newStr1); printf("The second duplicated string: |%s|\n", newStr2); free(newStr1); free(newStr2); { הזיכרון של ה- main זיכרון ה- heap char: str1[] ‘h’ 1000 ‘i’ 1001 ‘\0’ 1002 char*: str2 ??? 1003 char*: newStr1 ??? 1007 char*: newStr2 ??? 1011 char ‘h’ 4300 char ‘i’ 4301 char ‘\0’ 4302 char ‘b’ 6300 char ‘y’ 6301 char ‘e’ 6302 char ‘\0’ 6303 char: str1[] ‘h’ 1000 ‘i’ 1001 ‘\0’ 1002 char*: str char*: newStr1 ??? 1007 char*: newStr2 ??? 1011 char: str1[] ‘h’ 1000 ‘i’ 1001 ‘\0’ 1002 char*: str char*: newStr char*: newStr2 ??? 1011 char: str1[] ‘h’ 1000 ‘i’ 1001 ‘\0’ 1002 char*: str char*: newStr char*: newStr זיכרון ה- static storage char ‘b’ 5300 char ‘y’ 5301 char ‘e’ 5302 char ‘\0’ 5303

30 © Keren Kalif 30 הקצאת מערך של מבנים #include struct Point { int x, y; } typedef point_t; void main() { int size, i; point_t* points; printf("How many points? "); scanf("%d", &size); points = (point_t*)calloc(size, sizeof(point_t)); if (!points) { printf("ERROR! Out of memory!\n"); return; } printf("Points are: "); for (i=0 ; i < size ; i++) printf("(%d, %d) ", points[i].x, points[i].y); printf("\nPlease enter %d points: \n", size); for (i=0 ; i < size ; i++) { printf("Point #%d: ", i+1); scanf("%d %d", &points[i].x, &points[i].y); } printf("Points are: "); for (i=0 ; i < size ; i++) printf("(%d, %d) ", points[i].x, points[i].y); printf("\n"); free(points) } אין שום שינוי פרט לטיפוס ב- calloc/malloc

31 © Keren Kalif 31 תרגיל  הגדר מבנה המכיל שני שדות: תו ומספר  כתוב פונקציה המקבלת מחרוזת ומעדכנת בפרמטר פלט את מספר התווים השונים במחרוזת. בנוסף, הפונקציה תחזיר מערך מסוג המבנה שהוגדר ובו עבור כל תו במחרוזת יש לשמור את מספר המופעים שלו. במערך זה כל תו יופיע מקסימום פעם אחת בלבד  הצג את המערך שהוחזר  דוגמה:  עבור המחרוזת xbxdcc פרמטר הפלט יתעדכן להיות 4 (כי יש 4 תווים שונים במחרוזת) והמערך שיוחזר יראה כך: cdbx 2112

32 © Keren Kalif 32 דרכים שונות להגדרת מערך 1. מערך של Student בגודל הידוע בזמן קומפילציה student_t arr[3]; 2. מערך של Student בגודל שאינו ידוע בזמן קומפילציה scanf(“%d”, &size) student_t* arr = (student_t*)malloc(sizeof(student_t)*size); 3. מערך של מצביעים ל- Student בגודל הידוע בזמן קומפילציה 4. student_t* arr[3]; 4. מערך של מצביעים ל- Student בגודל שאינו ידוע בזמן קומפילציה scanf(“%d”, &size) student_t** arr = (student_t**)malloc(sizeof(student_t*)*size);

33 © Keren Kalif 33 דרכים להגדרת מערך (1)  מערך של Student בגודל הידוע בזמן קומפילציה student_t arr[3];  במקרה זה כל איברי המערך נמצאים על ה- stack  מימוש זה בזבזני במידה ולא נשתמש בכל איברי המערך  יעיל מבחינת ביצועים (אין הקצאות דינאמיות) student[]: arr[0]: name arr[0]: id arr[1]: name arr[1]: id arr[2]: name arr[2]: id

34 © Keren Kalif 34 דרכים להגדרת מערך (2)  מערך של Student בגודל שאינו ידוע בזמן קומפילציה scanf(“%d”, &size) student_t* arr = (student_t*)malloc(sizeof(student_t)*size);  במקרה זה רק כתובת ההתחלה של המערך נמצאת על ה- stack, בעוד המבנים עצמם נמצאים על ה- heap  גם מימוש זה בזבזני במידה ולא נשתמש בכל איברי המערך  יעיל מבחינת ביצועים (יש רק הקצאה אחת) student_t*: arr int: size student_t: name “yoyo” 2200 id student_t: name “gogo” 2236 id

35 © Keren Kalif 35 דרכים להגדרת מערך (3)  מערך של מצביעים ל- Student בגודל הידוע בזמן קומפילציה student_t* arr[3];  במקרה זה יש מערך של 3 כתובות על ה- stack, והמבנים עצמם יוקצו דינאמית על ה- heap בעת הצורך, או יצביעו למבנים קיימים  מימוש זה אופטימלי מבחינת מקום, כלומר נקצה מקום לנתוני המבנה רק בעת הצורך. פחות יעיל מבחינת ביצועים (כל אחד מהאיברים מוקצה דינאמית) student_t*[]: arr NULL student_t: name “yoyo” 2200 id 1111 student_t: name “yoyo” 5400 id 1111

36 © Keren Kalif 36 דרכים להגדרת מערך (4)  מערך של מצביעים ל- Student בגודל שאינו ידוע בזמן קומפילציה scanf(“%d”, &size); student_t** arr = (student_t**)malloc(sizeof(student_t*)*size);  על ה- stack תהיה רק כתובת ההתחלה של מערך הכתובות  מערך הכתובות יוקצה על ה- heap שכן גודלו אינו ידוע בזמן קומפילציה Student**: arr ??? 1000 int: size Student: name “yoyo” 2200 id 1111 Student: name “yoyo” 5400 id 1111 Student*[]: students ??? 4400 ??? Student**: arr int: size Student*[]: students

37 © Keren Kalif 37 מערך מבנים בתוך מבנה  בדוגמא הבאה יש לנו את המבנה "כיתה" שמכיל מערך של MAX_STUDNETS סטודנטים  בכל איבר במערך יהיו נתונים של סטודנט  כלומר, גודל המערך קבוע - כמות הסטודנטים המקסימלית בכל כיתה זהה

38 © Keren Kalif 38 דוגמא – כיתה עם סטודנטים #include #define MAX_STUDENTS 10 struct Student { char name[10]; int id; } typedef student_t; struct Class { char teacherName[10]; int registeredStudents; student_t students[MAX_STUDENTS]; } typedef class_t; void printClass(class_t c) { int i; printf("The teacher is %s and the %d students are:\n", c.teacherName, c. registeredStudents); for (i=0 ; i < c. registeredStudents ; i++) printf(" %d- Name: %s\tId: %d\n", i+1, c.students[i].name, c.students[i].id); } void main() { class_t c = {"Keren", 0}; char answer; int fContinue = 1; printf("Enter name and id for each student:\n"); do { printf("Enter another student? "); flushall(); scanf("%c", &answer); if (answer == 'n' || answer == 'N') fContinue = 0; else { printf("Student #%d: ", c.registeredStudents+1); scanf("%s %d", c.students[c.registeredStudents].name, &c.students[c.registeredStudents].id); c.registeredStudents++; } } while (fContinue && c.registeredStudents < MAX_STUDENTS); printClass(c); } // main

39 © Keren Kalif 39 החיסרונות כאשר גודל המערך קבוע  לא ניתן להגדיר כיתות בהן המספר המקסימלי של הסטודנטים שונה  למשל עבור קורסים עם קבוצות לימוד קטנות, או עבור שיעור שהוא תרגול  יתכן ו- numOfRegistered קטן משמעותית מ- MAX_STUDENTS ואז יש ביזבוז רב של מקום  מבנה תופס יחסית הרבה מקום בזיכרון כי הוא מכיל אוסף של שדות

40 © Keren Kalif 40 הקצאת מערך מבנים בתוך מבנה  בדוגמא הבאה יש לנו את המבנה "כיתה" שיכול להכיל מערך של תלמידים  בכל איבר במערך יהיו נתונים של סטודנט  מספר התלמידים המקסימלי אינו ידוע מראש וניתן ע"י המשתמש בזמן ריצה  לכן מערך התלמידים שבתוך המבנה "כיתה" מוקצה דינאמית  דוגמא, המשתמש בחר מערך בגודל 4, בכל איבר יהיו נתוני סטודנט

41 © Keren Kalif 41 struct Student { char name[10]; int id; } typedef student_t; struct Class { char teacherName[10]; int numOfStudents; student_t* students; }typedef class_t; void printClass(class_t c) { int i; printf("The teacher is %s and the %d students are:\n", c.teacherName, c.numOfStudents); for (i=0 ; i < c.numOfStudents ; i++) printf(" %d- Name: %s\tId: %d\n", i+1, c.students[i].name, c.students[i].id); } דוגמא – הקצאת סטודנטים בכיתה (הקוד בלבד, כך שאפשר לראות אותו ) void main() { int i; class_t c = {"Keren"}; printf("How many students? "); scanf("%d", &c.numOfStudents); c.students = (student_t*)calloc(c.numOfStudents, sizeof(student_t)); // check if allocation succeeded.. printf("Enter name and id for each student:\n"); for (i=0 ; i < c.numOfStudents ; i++) { printf("Student #%d: ", i+1); scanf("%s %d", c.students[i].name, &c.students[i].id); } printClass(c); free(c.students); }

42 © Keren Kalif 42 דוגמא – הקצאת סטודנטים בכיתה #include struct Student { char name[10]; int id; } typedef student_t; struct Class { char teacherName[10]; int numOfStudents; student_t* students; }typedef class_t; void printClass(class_t c) { int i; printf("The teacher is %s and the %d students are:\n", c.teacherName, c.numOfStudents); for (i=0 ; i < c.numOfStudents ; i++) printf(" %d- Name: %s\tId: %d\n", i+1, c.students[i].name, c.students[i].id); } void main() { int i; class_t c = {"Keren"}; printf("How many students? "); scanf("%d", &c.numOfStudents); c.students = (student_t*)calloc(c.numOfStudents, sizeof(student_t)); // check if allocation succeeded.. printf("Enter name and id for each student:\n"); for (i=0 ; i < c.numOfStudents ; i++) { printf("Student #%d: ", i+1); scanf("%s %d", c.students[i].name, &c.students[i].id); } printClass(c); free(c.students); } הזיכרון של ה- main int:i ??? 1000 class_t: char[10]:c.teacherName ??? 1004 int: c.numOfStudents ??? student_t*:c.students ??? זיכרון ה- heap student_t: name id student_t: name id int:i ??? 1000 class_t: char[10]:c.teacherName “keren” 1004 int: c.numOfStudents ??? student_t*:c.students ??? int:i ??? 1000 class_t: char[10]:c.teacherName “keren” 1004 int: c.numOfStudents 2 student_t*:c.students ??? int:i ??? 1000 class_t: char[10]:c.teacherName “keren” 1004 int: c.numOfStudents 2 student_t*:c.students 2200 student_t: name “yoyo” 2200 id student_t: name “gogo” 2236 id הזיכרון של printClass int:i ??? 3000 class_t: char[10]:c.teacherName “keren” 3004 int: c.numOfStudents 2 student_t*:c.students 2200 שינוי שם המורה בתוך הפונקציה לא ישנה את הנתון המקורי, אבל שינוי ערכים במערך הסטודנטים כן ישתנו.

43 © Keren Kalif 43 אבל מה אם נרצה להקצות מערך ולהשתמש רק בחלק מהאיברים?  החיסרון במימוש המחזיק מערך של סטודנטים, בין אם הוקצה דינאמית או לא, הוא כאשר לא מנצלים את כל איברי המערך, ואז יש ביזבוז זיכרון  לכן נקצה מערך של מצביעים למבנים בגודל המקסימלי שאנחנו רוצים, וכל איבר שיהיה בשימוש יצביע למבנה שיוקצה דינאמית

44 © Keren Kalif 44 הקצאת מערך של מצביעים למבנים בתוך מבנה  בדוגמא הבאה יש לנו את המבנה "כיתה" שיכול להכיל אוסף של תלמידים  מספר הסטודנטים המקסימלי ידוע מראש ויש מערך של מצביעים ל"סטודנט"  בתחילה רשומים לכיתה 0 סטודנטים, וכל פעם נוסיף סטודנט נוסף לכיתה  כל איבר יהיה מצביע ל"סטודנט". כל עוד לא נרשם סטודנט המצביע הוא NULL  דוגמא: כיתה שיכולים להיות בה מקסימום 4 סטודנטים  לאחר רישום סטודנט

45 © Keren Kalif 45 #define MAX_STUDENTS 2 struct Student { char name[10]; int id; } typedef student_t; struct Class { char teacherName[10]; int registeredStudents; student_t* students[MAX_STUDENTS]; }typedef class_t; void printClass(class_t c) { int i; printf("The teacher is %s and the %d students are:\n", c.teacherName, c.registeredStudents); for (i=0 ; i < c.registeredStudents ; i++) printf(" %d- Name: %s\tId: %d\n", i+1, c.students[i]->name, c.students[i]->id); } מערך של מצביעים דוגמא – רישום סטודנטים לכיתה void main() { int i, fExit=0; char answer; class_t c = {"Keren”, 0}; do { if (c.registeredStudents == MAX_STUDENTS) { printf("Class is full!\n"); break; } printf("Register a student (Y/N)? "); flushall(); scanf("%c", &answer); if (answer == 'N') fExit = 1; else { c.students[c.registeredStudents] = (student_t*)calloc(1, sizeof(student_t)); printf("Enter name and id: "); scanf("%s %d", c.students[c.registeredStudents]->name, &c.students[c.registeredStudents]->id); c.registeredStudents++; } } while (fExit==0); printClass(c); for (i=0 ; i < c.registeredStudents ; i++) free(c.students[i]); } (הקוד בלבד, כך שאפשר לראות אותו ) בדיקה אם יש מקום לסטודנט נוסף פניה לאיבר הפנוי הבא בעזרת registeredStudents פניה לשדות איברי המערך בעזרת <- (כי הם מצביעים)

46 © Keren Kalif 46 #include #define MAX_STUDENTS 2 struct Student { char name[10]; int id; } typedef student_t; struct Class { char teacherName[10]; int registeredStudents; student_t* students[MAX_STUDENTS]; }typedef class_t; void printClass(class_t c) { int i; printf("The teacher is %s and the %d students are:\n", c.teacherName, c.registeredStudents); for (i=0 ; i < c.registeredStudents ; i++) printf(" %d- Name: %s\tId: %d\n", i+1, c.students[i]->name, c.students[i]->id); } void main() { int i, fExit=0; char answer; class_t c = {"Keren"}; do { if (c.registeredStudents == MAX_STUDENTS) { printf("Class is full!\n"); break; } printf("Register a student (Y/N)? "); flushall(); scanf("%c", &answer); if (answer == 'N') fExit = 1; else { c.students[c.registeredStudents] = (student_t*)calloc(1, sizeof(student_t)); printf("Enter name and id: "); scanf("%s %d", c.students[c.registeredStudents]->name, &c.students[c.registeredStudents]->id); c.registeredStudents++; } } while (fExit==0); printClass(c); for ( ; ; ) free(c.students[i]); } דוגמא – רישום סטודנטים לכיתה הזיכרון החלקי של ה- main class_t: char[10]:c.teacherName ??? 1000 int: c.registeredStudents ??? student_t*[] :c.students ??? זיכרון ה- heap student_t: name id 0 class_t: char[10]:c.teacherName “Keren” 1000 int: c.registeredStudents 0 student_t*[] :c.students NULL Y class_t: char[10]:c.teacherName “Keren” 1000 int: c.registeredStudents 0 student_t*[] :c.students 2200 NULL student_t: name “momo” 2200 id 1111 class_t: char[10]:c.teacherName “Keren” 1000 int: c.registeredStudents 1 student_t*[] :c.students 2200 NULL student_t: name id 0 class_t: char[10]:c.teacherName “Keren” 1000 int: c.registeredStudents 1 student_t*[] :c.students student_t: name “gogo” 5300 id 2222 class_t: char[10]:c.teacherName “Keren” 1000 int: c.registeredStudents 2 student_t*[] :c.students i=0i < c.registeredStudentsi++ i=0 i=1i=2

47 © Keren Kalif 47 הקצאת מערך של מצביעים למבנים בתוך מבנה  בדוגמא הבאה יש לנו את המבנה "כיתה" שיכול להכיל אוסף של תלמידים  מספר הסטודנטים המקסימלי אינו ידוע מראש וניתן ע"י המשתמש בזמן ריצה  בתחילה רשומים לכיתה 0 סטודנטים, וכל פעם נוסיף סטודנט נוסף לכיתה  בכל איבר יהיה מצביע ל"סטודנט". כל עוד לא נרשם סטודנט המצביע הוא NULL  דוגמא: כיתה שהמשתמש החליט שיכולים להיות בה מקסימום 4 סטודנטים:  לאחר רישום סטודנט

48 © Keren Kalif 48 דוגמא – רישום סטודנטים לכיתה דינאמית (1) #include struct Student { char name[10]; int id; } typedef student_t; struct Class { char teacherName[10]; int numOfStudents; int registeredStudents; student_t** students; }typedef class_t; void printClass(class_t c) { int i; printf("The teacher is %s and the %d students are:\n", c.teacherName, c.registeredStudents); for (i=0 ; i < c.registeredStudents ; i++) printf(" %d- Name: %s\tId: %d\n", i+1, c.students[i]->name, c.students[i]->id); } מערך בגודל שאינו ידוע עדיין ושכל איבר בו יהיה מצביע: כוכבית אחת כי זהו מערך שמוצקה דינאמית, כדי להכיל את כתובת תחילת המערך כוכבית שניה כי כל איבר במערך הוא כתובת

49 © Keren Kalif 49 דוגמא – רישום סטודנטים לכיתה דינאמית (2) void main() { int i, fExit=0; char answer; class_t c = {"Keren"}; printf("How many max students? "); scanf("%d", &c.numOfStudents); c.students = (student_t**)malloc(sizeof(student_t*)*c.numOfStudents); // check if allocation succeeded do { if (c.registeredStudents == c.numOfStudents) { printf("Class is full!\n"); break; } printf("Register a student (Y/N)? "); flushall(); scanf("%c", &answer); if (answer == 'N') fExit = 1; else { c.students[c.registeredStudents] = (student_t*)calloc(1, sizeof(student_t)); printf("Enter name and id: "); scanf("%s %d", c.students[c.registeredStudents]->name, &c.students[c.registeredStudents]->id); c.registeredStudents++; } } while (fExit==0); printClass(c); for (i=0 ; i < c.registeredStudents ; i++) free(c.students[i]); free(c.students); } קבלת מספר הסטודנטים המקסימלי מהמשתמש הקצאת מערך של מצביעים לסטודנט שיחרור כל אחד מאיברי המערך שיחרור מערך המצביעים שגם הוקצה דינאמית (כמו בדוגמא "מערך של מערכים")

50 © Keren Kalif 50 השוואה בזכרון בין מערך מבנים למערך מצביעים  נניח כי מבנה Student תופס 16 בתים בזכרון  נניח כי יש כיתה עם פוטנציאל ל- 100 סטודנטים, אבל בפועל רשומים רק 40 מערך מבניםמערך מצביעים גודל הזכרון למבנים16x100 = x 40 = 640 גודל הזכרון למצביעים04 x 100 = 400 סה"כ הזכרון שבשימוש סה"כ הזכרון המבוזבז16x60 = 9604 x 60 = 240

51 ביחידה זו למדנו:  מוטיבציה להקצאות דינאמיות  מהי הקצאה דינאמית  יצירת מערך בגודל שאינו ידוע מראש  החזרת מערך מפונקציה  הקצאת מערך של מערכים  הגדלת מערך  הפונקציה strdup  דוגמאות משולבות של מבנים עם הקצאות דינאמיות 51 © Keren Kalif


Download ppt "הקצאות דינאמיות קרן כליף. © Keren Kalif 2 ביחידה זו נלמד:  מוטיבציה להקצאות דינאמיות  מהי הקצאה דינאמית  יצירת מערך בגודל שאינו ידוע מראש  החזרת מערך."

Similar presentations


Ads by Google