הקצאת זיכרון דינאמית מבוא כללי למדעי המחשב

Slides:



Advertisements
Similar presentations
מבוא למדעי המחשב לתעשייה וניהול
Advertisements

הרצאה 04 הקצאות דינאמיות קרן כליף.
קורס תכנות שיעור שנים-עשר: ניהול זיכרון 1. הקצאת זיכרון דינאמית עד עכשיו עשינו "הקצאה סטטית" הגדרנו את משתני התוכנית כבר כשכתבנו אותה הקומפיילר הקצה עבורם.
הקצאות דינאמיות קרן כליף.
1 מבוא למדעי המחשב הקצאה דינאמית. 2 הקצאת זיכרון דינאמית  כאשר אנו משתמשים במערכים, אנו מקצים אוטומטית את הזיכרון המקסימלי שנצטרך.  בפועל, אנו משתמשים.
תכנות מונחה עצמים Object Oriented Programming (OOP) אתגר מחזור ב'
רקורסיות נושאי השיעור פתרון משוואות רקורסיביות שיטת ההצבה
תכנות תרגול 11 שבוע : מבנים מטרת המבנים היא לאפשר למתכנת להגדיר טיפוסי משתנים חדשים אשר מתאימים ספציפית לבעיה שאותה התוכנית פותרת. מטרת המבנים.
תכנות תרגול 9 שבוע : הקשר בין מערכים למצביעים נרצה לעמוד על הקשר בין מערך למצביע מאחר ומערכים הם הכללה של משתנים הרי שברור שלמערך ולכל אחד מאיבריו.
מבוא לשפת C חידות ונקודות חשובות נכתב על-ידי יורי פקלני. © כל הזכויות שמורות לטכניון – מכון טכנולוגי לישראל.
מבוא למדעי המחשב תרגול 8 - מחרוזות שעת קבלה : יום שני 11:00-12:00 דוא " ל :
מבוא כללי למדעי המחשב תרגול. הבית האדום כתובת : רחוב קוקוריקו 2 הבית הירוק כתובת : רחוב קוקוריקו 4 הבית הצהוב כתובת : רחוב קוקוריקו 1 הבית הורוד כתובת.
1 Formal Specifications for Complex Systems (236368) Tutorial #5 Refinement in Z: data refinement; operations refinement; their combinations.
תכנות תרגול 9 שבוע : מערכים int a; a=5; int a[10]; a[2] = 5; 5 a a[0] a[1] a[2] a[9]  5 משתנה בודד מערך גישה למשתנה השלישי במערך.
תוכנה 1 - חזרה שולי לב יהודי 2 Arrays and Strings מערך - אוסף משתנים בעלי שם משותף. הפנייה לכל איבר נעשית ע ” י אינדקס. ב -C מערך מוגדר.
11 Introduction to Programming in C - Fall 2010 – Erez Sharvit, Amir Menczel 1 Introduction to Programming in C תרגול
מבוא למדעי המחשב © אריק פרידמן 1 מצביעים כמערכים דוגמה.
שיעור שישי: מערכים ופונקציות
מבוא למדעי המחשב מחרוזות, מצביעים וכתובות
11 Introduction to Programming in C - Fall 2010 – Erez Sharvit, Amir Menczel 1 Introduction to Programming in C תרגול
מבוא כללי למדעי המחשב שיעור 5
תכנות – שיעור 7. חזרה -מערכים נגדיר בעזרתו קבוצת משתנים כאשר יהיה לנו מספר רב של משתנים זהים נגדיר בעזרתו קבוצת משתנים כאשר יהיה לנו מספר רב של משתנים.
תכנות תרגול 14 שבוע:
תכנות תרגול 10 שבוע : הקשר בין מערכים למצביעים נרצה לעמוד על הקשר בין מערך למצביע מאחר ומערכים הם הכללה של משתנים הרי שברור שלמערך ולכל אחד מאיבריו.
קורס תכנות – סימסטר ב ' תשס " ח שיעור שישי: מערכים
מבוא כללי למדעי המחשב רשימות מקושרות
מבוא למדעי המחשב תרגול 6 - מערכים שעת קבלה : יום שני 11:00-12:00 דוא " ל :
Data Structures, CS, TAU, Perfect Hashing 1 Perfect Hashing בעיה : נתונה קבוצה S של n מפתחות מתחום U השוואה ל - Hash : * טבלה קבועה (Hash רגיל - דינאמי.
תכנות תרגול 5 שבוע : הגדרת פונקציות return-value-type function-name(parameter1, parameter2, …) הגדרת סוג הערכים שהפונקציה מחזירה שם הפונקציהרשימת.
1 Data Structures, CS, TAU, Perfect Hashing בעיה: נתונה קבוצה S של n מפתחות מתחום U השוואה ל- Hash : * טבלה קבועה (Hash רגיל - דינאמי) * רוצים זמן קבוע.
מערכים עד היום כדי לייצג 20 סטודנטים נאלצנו להגדיר עד היום כדי לייצג 20 סטודנטים נאלצנו להגדיר int grade1, grade2, …, grade20; int grade1, grade2, …, grade20;
תכנות מונחה עצמים Object Oriented Programming (OOP) אתגר מחזור ב' Templates תבניות.
מבוא למדעי המחשב תרגול 3 שעת קבלה : יום שני 11:00-12:00 דוא " ל :
Last time on Clang משתנה: "פתק" המשמש את המחשב לשמירת מידע. לכל משתנה יש שם וטיפוס כללים לשמות משתנים –חייבים להכיל רק אותיות, מספרים ו '_' –חייבים להתחיל.
תוכנה 1 - תרגול שיעור 10 Pointers (2) שולי לב יהודי
מבוא כללי למדעי המחשב הקצאת זיכרון דינאמית
1 מבוא למדעי המחשב סיבוכיות. 2 סיבוכיות - מוטיבציה סידרת פיבונאצ'י: long fibonacci (int n) { if (n == 1 || n == 2) return 1; else return (fibonacci(n-1)
מבוא כללי למדעי המחשב תרגול 7. מבנים מטרת המבנים היא לאפשר למתכנת להגדיר טיפוסי משתנים חדשים אשר מתאימים ספציפית לבעיה שאותה התוכנית פותרת. מטרת המבנים.
מבוא למדעי המחשב תרגול 12 – הקצאת זיכרון דינאמית שעת קבלה : יום שני 11:00-12:00 דוא " ל :
תכנות תרגול 8 שבוע : מערכים עד היום התוכניות שלנו לא ידעו לשמור כמות גדולה של מידע ללא הגדרת כמות גדולה של משתנים. עד היום התוכניות שלנו לא.
Structure. מה לומדים היום ? דרך לבנות מבנה נתונים בסיסי – Structure מייצר " טיפוס " חדש מתאים כאשר רוצים לאגד כמה משתנים יחד דוגמאות : עובד : שם, טלפון,
הגדרת משתנים יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר 2015 יום שישי 18 ספטמבר.
קורס תכנות שיעור שמיני: הקצאת זיכרון דינאמית, הצצה לייצוג ועיבוד תמונות 1.
מבנה נתונים ואלגוריתמים ) לשעבר - עיבוד מידע( ד"ר אבי רוזנפלד ד"ר אריאלה ריכרדסון.
קורס תכנות שיעור עשירי: מיונים, חיפושים, וקצת סיבוכיות חישוב.
מבנים קרן כליף. ביחידה זו נלמד :  מהו מבנה (struct)  איתחול מבנה  השמת מבנים  השוואת מבנים  העברת מבנה לפונקציה  מבנה בתוך מבנה  מערך של מבנים.
הגדרת משתנים יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016 יום שלישי 14 יוני 2016.
מחרוזות – הטיפוס String
Programming Arrays.
מבוא למדעי המחשב סיבוכיות.
הקצאות דינאמיות בשילוב מבנים
מערכים ומצביעים הקצאה דינאמית של מערכים דו-מימדיים
שיעור חמישי: מערכים ומחרוזות
מצביעים קרן כליף.
תירגול 14: מבני נתונים דינאמיים
קורס תכנות – סמסטר ב' תשס"ח
הקצאות דינאמיות קרן כליף.
מבנים קרן כליף.
שיעור שישי: מחרוזות, מצביעים
Introduction to Programming in C
מצביעים קרן כליף.
מבוא כללי למדעי המחשב פונקציות
מבוא כללי למדעי המחשב תרגול 6
סוגי משתנים קרן כליף.
מערכים של מצביעים הקצאה דינאמית
Programming in C תרגול Introduction to C - Fall Amir Menczel.
תוכנה 1 תרגול 13 – סיכום.
מבוא למדעי המחשב מצביעים.
תירגול 8:מצביעים והקצאה דינאמית
מצביעים וכתובות מבוא כללי למדעי המחשב
Presentation transcript:

הקצאת זיכרון דינאמית מבוא כללי למדעי המחשב www.cs.tau.ac.il/courses/cs4math/10b

על פונקציות ומצביעים פונקציה יכולה להחזיר מצביע (כלומר כתובת של משתנה). אבל לא נחזיר כתובת של משתנה שהוגדר בתוך הפונקציה, כי אחרי שהפונקציה מסתיימת המשתנה כבר לא נמצא שם (גישה לשם תגרום לתעופה).

עוד על פונקציות ומצביעים למשל: int *address_of _the_higher_number(int *a, int*b) { if(*a > *b) return a; return b; }

עוד על פונקציות ומצביעים למשל: int *address_of _the_higher_number(int *a, int*b) { if(*a > *b) return a; return b; } int main() { int *ptr, i=10, j=0; ptr = address_of _the_higher_number(&i, &j); return 0; מוחזרת כתובת המשתנה שמכיל את הערך הגדול יותר מבין השניים והשימוש ניראה כך למשל:

עוד על פונקציות ומצביעים אמרנו שהערך שמוחזר מפונקציה יכול להיות מצביע: int *address_of _the_higher_number(int *a, int*b); אבל החזרת כתובת משתנה שהוגדר בתוך הפונקציה תגרום לשגיאה. כי המשתנים שמוגדרים בתוך הפונקציה נעלמים עם סיומה. למשל הפונקציה הבאה תגרום לשגיאה: int *give_pointer_to_zero() { int i=0; return &i; } i

גישה לתאי-מערך לפי הכתובת אם הגדרנו למשל: int array[10]; int *array_ptr; array_ptr=array; אז אפשר לגשת לתאי-המערך גם לפי הכתובת שלהם, כמו משתנים אחרים. 3 הפעולות הבאות עושות אותו דבר (שמות 100 בתא מס' 5 במערך): array[5]=100; *(array+5)=100; *(array_ptr+5)=100; - המחשב יודע כמה בתים להתקדם כשאנחנו מבקשים להתקדם ב-5 תאים קדימה, כי הגדרנו שטיפוס הערכים הוא int והוא יודע כמה בתים כל int תופס (כאמור, ערכי המערך שמורים בזיכרון ברצף). (מצביע על התא הראשון במערך) גישה לתא שנמצא 5 תאים אחרי התא הראשון

גישה למערך לפי כתובת – דוגמא נוספת נאמר שמוגדרים: int i,array[10]={0}; int *ptr; אז 3 הקטעים הבאים עושים בדיוק אותו דבר (מדפיסים את המערך): for (i=0; i<10; i++) printf(“%d ”, array[i]); printf(“%d ”, *(array+i)); for (ptr=array; ptr <= &array[9]; ptr++) printf(“%d ”, *ptr); 1) הדפסה על-ידי גישה רגילה לתאי המערך 2) הדפסה על-ידי גישה לכל תא לפי הכתובת שלו יחסית לתא הראשון 3) הדפסה ע"י קידום מצביע מכתובת התא הראשון עד כתובת התא האחרון

מערכים ופונקציות למשל הפונקציות שהזכרנו על מחרוזות (כמו strcmp, strlen) מוגדרות למעשה עבור מצביעים מסוג char *. באחריותנו להפעיל אותן באמת על מחרוזת (כלומר רצף תווים שמסתיים ב- '0\') ולא סתם על מצביע לתו או מערך של תווים (נקבל תוצאות שגויות אם אין את תו הסיום). כמו-כן, כזכור, אם פונקציה מוגדרת על מערך בגודל מסויים, ניתן להעביר אליה גם מערך בגודל אחר של אותו טיפוס (כי כאמור מה שמועבר זה רק כתובת ההתחלה). שוב, זה באחריותנו שתתבצע בפונקציה פעולה הגיונית ושהיא תדע מה גודל המערך ולא תחרוג ממנו.

"מצביע על כלום" לפעמים נירצה לסמן שמצביע לא מצביע לשום משתנה (למשל לפני שהתחלנו להשתמש בו). מקובל לעשות זאת ע"י זה שנכניס אליו את הכתובת 0 (שאיננה כתובת אפשרית של משתנה מטיפוס כתובת). למעשה, במקום לרשום 0 מקובל לרשום NULL. זהו קבוע שערכו 0, שמוגדר ע"י #define בספריה stdlib.h. למשל: #include<stdlib.h> int *ptr=NULL; לא לבלבל בין הכתובת NULL לבין תו סיום מחרוזת ‘\0’

דוגמא: חיפוש תו במחרוזת #include<stdlib.h> char *my_strchr(char *str, char tav) { while((*str != ‘\0’) && (*str != tav)) str++; if (*str == tav) return str; return NULL } מקבלת מצביע לתחילת מחרוזת ותו מתקדמים עד שמצאנו את התו או הגענו לסוף המחרוזת אם מצאנו מוחזר מצביע להופעה הראשונה אם לא מצאנו מחזירים NULL

דוגמא: חיפוש תו במחרוזת #include<stdlib.h> char *my_strchr(char *str, char tav) { while((*str != ‘\0’) && (*str != tav)) str++; if (*str == tav) return str; return NULL } מקבלת מצביע לתחילת מחרוזת ותו מתקדמים עד שמצאנו את התו או הגענו לסוף המחרוזת אם מצאנו מוחזר מצביע להופעה הראשונה אם לא מצאנו מחזירים NULL הדגמת אופן הקריאה לפונקציה הזו int main() { char word[]=“HELLO”, tav=‘L’, *place; place=my_strchr(word, tav); return 0; }

מערכים ופונקציות – נקודה לתשומת לב אם ננסה להחזיר מפונקציה מערך שהגדרנו בתוכה או מצביע על מערך כזה, אז נקבל שגיאה. int * zero_array() { int array[100]={0}; return array; } זה מכיוון שמוחזרת כתובת של המערך, אבל כל מה שהוגדר בתוך הפונקציה נמחק כשהיא מסתיימת.

מצביעים ומערכים - סיכום משתנה מערך הוא סוג של מצביע: הוא מכיל כתובת (של התא הראשון במערך), אבל אי-אפשר לשנות אותה. זו הסיבה שבהעברת מערך לפונקציה המערך המקורי מושפע. אפשר לגשת לתא במערך גם בעזרת חישוב כתובתו יחסית לכתובת התחלת המערך. למשל: *(array+5)=100;

נקודה לתשומת-לב: איתחול מה שמוצבע ככלל, לפני שכותבים ערך לכתובת שנמצאת במצביע, צריך שהמצביע יכיל כתובת של משתנה כלשהו שהגדרנו (אחרת תיקרה תעופה). int *ptr; *ptr=10; בהתאם, בהגדרת מצביע לא ניתן לכתוב ערכים למקום שהוא מצביע עליו. למשל לא ניתן לרשום: int *my_array={1,2,3}; יכתוב לכתובת שנמצאת במצביע לפני שאיתחלנו אותו

מבנים - תזכורת אפשר להגדיר בשפת C טיפוסי משתנים חדשים, שמתאימים יותר לבעיה שרוצים לפתור. למשל: מס' מרוכב, נקודה במישור, מלבן, סטודנט... טיפוס חדש כזה נקרא "מבנה". מבנה מוגדר על-ידי אוסף של משתנים מטיפוסים שכבר קיימים ("שדות"). למשל מבנה ליצוג נקודה במישור יכלול שני שדות שהם מספרים ממשיים: קואורדינטת X וקואורדינטת Y. מבנה ליצוג סטודנט יכול לכלול שם, ת.ז., טלפון, וכו'.

תזכורת: דוגמאות שראינו למבנים נקודה במישור: struct point { double x; double y; }; מספר מרוכב: struct complex { double real; double img;

תזכורת: עוד דוגמא שראינו מבנה ליצוג סטודנט: struct student{ int id; char name[40]; double average; };

תזכורת: מבנים – הגדרה וגישה #include<stdio.h> struct point { double x; double y; }; int main() { struct point P1,P2; P1.x = 6; P1.y = 7; P2.x = 4; P2.y = 2; printf(“%g\n”, P1.y); return 0; } P2 P1 x y 4 x y 6 2 7

תזכורת: שם מקוצר לטיפוס משתנה חדש struct point { double x; double y; }; typedef struct point point; זה מאפשר לכתוב point במקום לכתוב struct point. אפשר גם לרשום את זה כך: typedef struct point { } point; טיפוס קיים שם חדש

תזכורת: התכונות של מבנים למשתנים מטיפוס חדש (מבנה) יש חלק גדול מהתכונות של משתנים מטיפוסים רגילים: אפשר לבצע השמה בין שני משתנים מאותו טיפוס מבנה p1=p2; גם שדה שהוא מערך יועתק אל המשתנה החדש (לא רק כתובת המערך) אפשר להגדיר מערך של מבנים מטיפוס כלשהו (למשל point a[10]). אפשר להעביר ולהחזיר מבנה מפונקציה. מה שמועבר הוא עותק של המבנה (גם מערכים שנמצאים בתוכו יועתקו). אפשר להגדיר מצביע למשתנה מטיפוס מבנה (למשל complex *). שדה של מבנה יכול להיות מטיפוס שהוא מבנה (אחר). לא ניתן להשתמש בפעולות החשבון וההשוואה הרגילות על מבנים - יש להגדיר פונקציות עבורן בהתאם לצורך.

מבנים ומצביעים אפשר להגדיר מצביע גם לטיפוס שהוא מבנה (כתובת ההתחלה של מבנה היא כתובת השדה הראשון שלו). נשתמש בזה כדי לשנות מבנה מתוך פונקציה, או כדי לחסוך את העתקת השדות בהעברת המבנה אליה. למשל: struct point P={5,6}; struct point *ptr; ptr = &P ; ptr 5 6 P.x P P.y

מצביעים ומבנים – גישה לשדות המבנה כדי להגיע לשדות של P דרך ptr שמצביע על P, אפשר להשתמש ב- * כרגיל: (*ptr).x = 3; שקול ל- P.x = 3 (*ptr).y = 7; שקול ל- P.y = 7 אבל בדרך-כלל נשתמש לצורך זה בסימון מקוצר: חץ <- ptr->x = 3; שקול ל- P.x = 3 ptr->y = 7; שקול ל- P.y = 7 כלומר משמעות החץ היא גישה לשדה במבנה שמצביעים עליו. (צריך סוגריים כי אחרת לנקודה יש קדימות)

תזכורת: מבנים ומצביעים struct point P={5,6}; struct point *ptr; ptr = &P ; ptr ptr -> x 5 6 P.x P ptr -> y P.y אם השדה x היה בעצמו מטיפוס מצביע למבנה אחר שיש בו שדה a, אז היה אפשר לרשום ptr->x->a

מבנים ומצביעים אם המבנה שלנו מורכב מהרבה מאוד משתנים, אז העתקת כל המבנה לתוך הפרמטרים של הפונקציה דורשת זמן וזיכרון רבים. כמו-כן, כמו במשתנים רגילים, לפעמים נירצה לשנות יותר ממבנה אחד מתוך הפונקציה (ולא רק להחזיר מבנה אחד). כדי לטפל בשני הנושאים האלה נוכל לשלוח לפונקציה מצביעים למבנים, כמו ששלחנו מצביעים למשתנים מסוגים אחרים. בקריאה לפונקציה ישוכפל רק המצביע לכל מבנה, ובעזרת המצביע נוכל לשנות את המבנה המקורי.

מבנים ומצביעים P.x P P.y ptr בדיוק כמו טיפוסי-משתנים שהכרנו בעבר, אפשר להגדיר מצביע לטיפוס שהוא מבנה (שיכיל את כתובת ההתחלה שלו). למשל: struct point P={5,6}; struct point *ptr; ptr = &P ; 5 6 P.x P P.y ptr

מצביעים ומבנים – גישה לשדות המבנה בדוגמא הזאת, כדי להגיע לשדות של P דרך ptr שמצביע על P, אפשר להשתמש ב- * כרגיל: (*ptr).x = 3; שקול ל- P.x = 3; (*ptr).y = 7; שקול ל- P.y = 7; (צריך סוגריים כי אחרת לנקודה יש קדימות)

מצביעים ומבנים – גישה לשדות המבנה בדוגמא הזאת, כדי להגיע לשדות של P דרך ptr שמצביע על P, אפשר להשתמש ב- * כרגיל: (*ptr).x = 3; שקול ל- P.x = 3; (*ptr).y = 7; שקול ל- P.y = 7; אבל בדרך-כלל נשתמש לצורך זה בסימון מקוצר: חץ <- ptr->x = 3; שקול ל- P.x = 3; ptr->y = 7; שקול ל- P.y = 7; כלומר משמעות החץ היא גישה לשדה במבנה שמצביעים עליו. (צריך סוגריים כי אחרת לנקודה יש קדימות)

מצביעים ומבנים - דוגמא פונקצית ההשוואה כשמועברים מצביעים: int equal( struct point *p, struct point *q) { return ((p->x == q->x) && (p->y == q->y)); } (הגישה עם חץ היא לשדה של המבנה שמצביעים עליו) הקריאה לפונקציה ע"י equal(&P1,&P2) int equal(struct point p, struct point q) במקום: { return ( (p.x == q.x) && (p.y == q.y)); } (הגישה עם נקודה היא לשדה של מבנה) הקריאה לפונקציה ע"י equal(P1, P2)

מצביעים ומבנים – עוד דוגמא נחשב את המרחק כשמועברים מצביעים ולא המבנים עצמם: double dist(struct point *p, struct point *q) { double d,dx,dy; dx = p->x - q->x; dy = p->y - q->y; d = dx*dx + dy*dy; return sqrt(d); } איך תיראה הקריאה ב-main? printf(“The distance is %g\n”, dist(&P1,&P2)(;

הקצאת זיכרון דינאמית

הקצאת זיכרון דינאמית עד עכשיו תמיד הגדרנו בדיוק איזה משתנים יהיו לנו בתוכנית כבר כשכתבנו אותה (זה נקרא "הקצאה סטטית"). בפרט, בהגדרת מערך קבענו את גודלו, ולא יכולנו לשנות אותו בזמן הריצה (אם הגודל לא היה ידוע מראש הנחנו חסם עליון). למשל: int main() { int a[10]; } עבור המערך הזה יוקצו עשרה תאים בזיכרון, ולא נוכל להגדיל אותו תוך כדי הרצת התוכנית.

הקצאת זיכרון דינאמית לפעמים נירצה להגדיר משתנים נוספים בזמן הריצה, בפרט להגדיר מערך בגודל שתלוי בקלט מהמשתמש (יתכן שלא ידוע מראש חסם-עליון על הגודל). אפשר לממש דבר כזה ב- C: יש פונקציה בשם malloc, שמקצה מקום בזיכרון תוך כדי ריצת התוכנית (הגודל יכול להיות תלוי בקלט). היא נמצאת בספריה stdlib.h. מעבירים לפונקציה הזאת גודל זיכרון מבוקש (בבתים), והיא מחזירה מצביע לאזור בגודל הזה שהוקצה בזיכרון. הקצאת זיכרון בזמן הריצה נקראת "הקצאה דינאמית".

הקצאת זיכרון דינאמית – דוגמא int main() { int num, *a; scanf(“%d”, &num); a = (int *) malloc (num * sizeof(int) ) ; } המשמעות היא: תקצה בזיכרון מקום רצוף בגודל num משתנים מסוג int, ותחזיר את כתובת ההתחלה של המקום שהוקצה, כמצביע ל- int. ההקצאה מתבצעת בזמן ריצת התוכנית. הפונקציה sizeof מחזירה גודל בבתים של משתנה או טיפוס משתנה (int דורש בדרך-כלל 4 בתים). לתוך a תיכנס הכתובת של השטח שהוקצה בזיכרון.

הקצאת זיכרון דינאמית - פורמלית המצביע = (סוג המצביע) malloc (מס' תאים * גודל תא) ; למשל: a = (int * ) malloc (10 * sizeof(int) ) ; ה- casting לסוג המצביע המתאים (במקרה הזה int *) נדרש כיוון שהפונקציה מחזירה כתובת של רצף בתים שטיפוסו לא מוגדר (void *). צריך להעביר את זה לטיפוס המתאים כדי לוודא שניגש נכון לערכים שנמצאים שם (בהתאם לגודל המשתנה). נוכל לגשת לתאי המערך כמו שניגשים אל מערך רגיל, וגם לבצע חשבון עם המצביע הזה (למשל לגשת לכתובת ההתחלה ועוד 1).

דוגמא: הקצאת מערך בגודל משתנה int main() { int *a, size,i; printf(“Enter array size\n”); scanf(“%d”, &size); a = (int *) malloc (size * sizeof(int)); for (i=0; i<size; i++) scanf(“%d”, &a[i]); printf(“%d”, a[i]); return 0; } קולטים את הגודל הדרוש מקצים מקום עכשיו אפשר למשל לקלוט ערכים ולהדפיס אותם, כמו במערך רגיל (אבל שימו לב ש- a הוא מצביע ולא מערך, לכן ניתן למשל לשנות את ערכו כך שהוא יצביע אחר-כך למקום אחר)

הקצאת זיכרון דינאמית אם בקשת הקצאת הזיכרון נכשלת, כלומר אין מספיק זיכרון להקצאה שביקשנו, אז הפונקציה malloc מחזירה NULL. NULL הוא כזכור קבוע שערכו 0 שמוגדר בספריה stdlib.h. אחרי כל בקשת הקצאה אנחנו צריכים לוודא שאכן הזיכרון שביקשנו התקבל. למשל: int *a; a = (int *) malloc (1000 * sizeof(int)); if (a==NULL) { printf(“Out of memory\n”); exit(1); }

הקצאת זיכרון דינאמית כל פונקציות ההקצאה הדינאמית נמצאות בספריה stdlib.h void *malloc(unsigned int size); מקצה size בתים. כאמור, קריאה מוצלחת תחזיר את כתובת תחילת הזיכרון המוקצה ואחרת יוחזר NULL. פונקציה נוספת דומה מאוד: void *calloc(unsigned int n, unsigned int size_el); מקצה מערך של n איברים, כל איבר בגודל size_el בתים, כל בית מאותחל לאפס. קריאה מוצלחת תחזיר את כתובת תחילת הזיכרון המוקצה ואחרת יוחזר NULL. דוגמת שימוש: ptr=(int *) calloc(10,sizeof(int));

הקצאת זיכרון דינאמית פונקציה שימושית נוספת: void *realloc(void *ptr, unsigned int size); מקבלת מצביע לשטח בזיכרון שהוקצה דינאמית (אותו מצביע שמחזירות malloc/calloc), ומספר בתים size (שהוא הגודל החדש הדרוש). הפונקציה משנה את גודל ההקצאה בהתאם לדרישה החדשה. אם הדרישה הייתה להגדיל את ההקצאה ואין אפשרות להגדיל את השטח הנוכחי, מוקצה שטח חליפי במקום אחר, והמידע מועתק לשם. קריאה מוצלחת תחזיר את כתובת תחילת הזיכרון המוקצה (שלא בהכרח השתנתה) ואחרת יוחזר NULL.

הדגמת קטע תוכנית עם realloc int * a; int size, new_size; scanf(“%d”, &size); a = (int *) malloc ( size * sizeof(int) ) ; ……... …… .. scanf(“%d”, &new_size); a= (int *) realloc(a, new_size * sizeof(int) ); ………

שחרור זיכרון שהוקצה דינאמית כזכור, אמרנו בעבר שמשתנים שמוגדרים בתוך פונקציה נעלמים אוטומטית עם סיומה. זה נכון רק לגבי משתנים שמוגדרים סטטית. משתנים שמוקצים דינאמית לא נעלמים עם סיום הפונקציה. באחריותנו לשחרר את ההקצאה הזאת כשנסיים להשתמש בה, כפי שנסביר בשקפים הבאים. int main() { . f(); } void f() { int *x; x = (int *)malloc(10 * sizeof(int)); …. }

שחרור זיכרון שהוקצה דינאמית אם אנחנו לא רוצים להמשיך להשתמש בזיכרון שהקצנו דינאמית בפונקציה, אנחנו צריכים לשחרר את הזיכרון הזה בפונקציה באופן מפורש, באמצעות פקודת free (שתודגם בהמשך). אם אנחנו רוצים להמשיך להשתמש בזיכרון שהקצנו דינאמית בתוך פונקציה אחרי שהיא מסתיימת, אז היא צריכה להעביר את הכתובת של הזיכרון שהיא הקצתה למי שקרא לה (אחרת לא נדע איך לגשת לשם). בכל מקרה, כשנסיים להשתמש בזיכרון הזה נצטרך לשחרר אותו.

שחרור זיכרון שהוקצה דינאמית נשים לב: אם לא נשחרר זיכרון שהקצנו וסיימנו להשתמש בו, אז אנחנו עלולים למלא את זיכרון המחשב באיזורים שלכאורה תפוסים אבל בעצם לא נגישים, מה שנקרא "דליפת זיכרון" (עלול להיגמר לנו הזיכרון בגלל זה). בכל מקרה עד סוף התוכנית צריך לשחרר כל מה שהקצינו דינאמית.

שחרור זיכרון שהוקצה דינאמית הפונקציה free, שנמצאת בספריה stdlib.h, מקבלת מצביע ומשחררת את הזיכרון שהוא מצביע אליו. הגדרתה היא: void free(void *ptr) השימוש פשוט ע"י free(x); המצביע שמועבר ל-free חייב להכיל כתובת-התחלה של זיכרון שהוקצה דינאמית לפני כן (כלומר כתובת שהוחזרה ע"י malloc או calloc או realloc), ואז ההקצאה הזאת מבוטלת. אם ננסה לשחרר משהו אחר, למשל החל מהמקום השני שהקצינו, או כתובת של משתנה שלא הוקצה דינאמית, אז התוכנית "תעוף". לגבי פקודת realloc: אם היא מקצה שטח חדש אז היא משחררת בעצמה את השטח הישן (צריך לשחרר בעצמנו רק את השטח החדש).

שחרור זיכרון שהוקצה דינאמית - הדגמה void f() { int *x; x = (int *) malloc(10 * sizeof(int)); …. free(x); }

החזרת זיכרון שהוקצה דינאמית - הדגמה int * allocate_int_array(int size) { int *ptr; ptr = (int *) malloc(size * sizeof(int)); if (ptr==NULL) printf(“Out of memory”); exit(1); } return ptr; הפונקציה מקצה דינאמית מערך של מס' שלמים בגודל המבוקש, בודקת שההקצאה הצליחה, ומחזירה את כתובת המערך שהוקצה. אפשר להחזיר את כתובת המערך הזה, שלא משוחרר בסוף הפונקציה כי הוא הוקצה דינאמית, אבל מי שקרא לפונקציה צריך לדאוג לשחררו בסיום השימוש בו.

נקודה לתשומת-לב: מצביעים לא מאותחלים חשוב לזכור, שכמו כל משתנה גם מצביעים אינם מאותחלים בהגדרה שלהם, וצריך לתת להם ערך. אם ננסה לגשת לכתובת שנמצאת במצביע בלי שנתנו לו ערך התחלתי, התוכנית "תעוף". למשל: int *p; *p=10; אפשר לגשת לכתובת שנמצאת במצביע רק אם שמנו בו קודם כתובת של משתנה או של שטח שהוקצה דינאמית. למשל: int i; p=&i; או: p=(int *) malloc (5*sizeof(int)); הכתובת ב- p לא אותחלה

נקודה לתשומת-לב: מצביעים לא מאותחלים כזכור, אפשרות נוספת שראינו למתן ערך למצביע היא ע"י הכנסת כתובת של מערך או של מצביע אחר אליו. int a[10]; p=a;

עוד נקודה לתשומת-לב: מחרוזת קבועה אם נכניס מחרוזת קבועה למצביע על char בלי לאתחל אותו (לא מחרוזת מהקלט) אז לא תיקרה תעופה, אפילו שלא בוצעה הקצאה כלשהי ואפילו שלא ניתן להעתיק מחרוזות על-ידי השמה. זה כיוון שזאת השמה של כתובות: הקומפיילר שומר בזיכרון כל מחרוזת קבועה שהוא מוצא בתוכנית, ולכן למחרוזת הזאת כבר יש איזשהי כתובת בזיכרון שבה היא מאוחסנת. המצביע רק מצביע לשם. למשל אפשר לכתוב: char *pc, *ps; pc=“Hello!”; ps=“Hello!”; קומפיילרים סטנדרטיים לא יאפשרו לשנות מחרוזת קבועה כזו (כי שינוי ישפיע על כל הופעותיה בתוכנית). pc ‘H’ ‘e’ ‘l’ ‘o’ ‘!’ ‘\0’ ps

הקצאת זיכרון דינאמית - סיכום אפשר להקצות מקום בזיכרון בזמן הריצה. בפרט, אפשר ליצור מערך בגודל שתלוי בקלט (גודל שלא ידוע מראש). בספריה stdlib.h נמצאות הפקודות malloc, calloc, realloc, free. כשמקצים מקום בזיכרון מוחזרת הכתובת שלו, ונשמור אותה במצביע (בהתאם לסוג המשתנה שנירצה לשמור במקום הזה). אי-אפשר לתת שם למשתנה/מערך החדש, אבל אפשר לגשת אליהם דרך המצביע. צריך לזכור לשחרר מה שהקצינו כשמסיימים להשתמש בזה. כתיבה לכתובת שנמצאת במצביע לא מאותחל תגרום לתעופה – צריך לזכור לאתחל אותו.