קורס מבוא למדעי המחשב סמסטר א' תשס"ח
שיעור שלושה-עשר: הקצאת זיכרון דינאמית http://online.shenkar.ac.il/moodle
הקצאת זיכרון דינאמית •
עד עכשיו תמיד הגדרנו בדיוק איזה משתנים יהיו לנו בתוכנית כבר כשכתבנו אותה )זה נקרא "הקצאה סטטית"(.
•
בפרט ,בהגדרת מערך קבענו את גודלו ,ולא יכולנו לשנות אותו בזמן הריצה )אם הגודל לא היה ידוע מראש הנחנו חסם עליון(.
•
למשל: )(int main { ;]int a[10 } •
עבור המערך הזה יוקצו עשרה תאים בזיכרון ,ולא נוכל להגדיל אותו תוך כדי הרצת התוכנית.
הקצאת זיכרון דינאמית •
לפעמים נירצה להגדיר משתנים נוספים בזמן הריצה ,בפרט להגדיר מערך בגודל שתלוי בקלט מהמשתמש )יתכן שלא ידוע מראש חסם-עליון על הגודל(.
•
אפשר לממש דבר כזה ב :C -יש פונקציה בשם ,mallocשמקצה מקום בזיכרון תוך כדי ריצת התוכנית. •
היא נמצאת בספריה .stdlib.h
•
מעבירים לפונקציה הזאת גודל זיכרון מבוקש )בבתים( ,והיא מחזירה מצביע לאזור בגודל הזה שהוקצה בזיכרון.
•
הקצאת זיכרון בזמן הריצה נקראת "הקצאה דינאמית".
הקצאת זיכרון דינאמית -דוגמא
•
)(int main { ;int * a )a = (int * ) malloc (10 * sizeof(int ; ) )sizeof(int } המשמעות היא :תקצה בזיכרון מקום רצוף ל 10-משתנים מסוג ,intותחזיר את כתובת ההתחלה של המקום שהוקצה ,כמצביע ל .int -ההקצאה מתבצעת בזמן ריצת התוכנית. • • •
הפונקציה sizeofמחזירה גודל בבתים של משתנה או טיפוס משתנה. למערך של int 10ים צריך 40בתים. לתוך aתיכנס הכתובת של השטח שהוקצה בזיכרון.
הקצאת זיכרון דינאמית -פורמלית )סוג המצביע( = המצביע )גודל תא * מס' תאים( ) mallocסוג ; )גודל למשל: )a = (int * ) malloc (10 * sizeof(int ; ) )sizeof(int •
•
•
* (intנדרש כיוון ה casting -לסוג המצביע המתאים )במקרה הזה *int שהפונקציה מחזירה כתובת של רצף בתים שטיפוסו לא מוגדר )* .(void צריך להעביר את זה לטיפוס המתאים כדי לוודא שניגש נכון לערכים שנמצאים שם )בהתאם לגודל המשתנה(. נוכל לגשת לתאי המערך כמו שניגשים אל מערך רגיל ,וגם לבצע חשבון עם המצביע הזה )למשל לגשת לכתובת ההתחלה ועוד .(1
דוגמא :הקצאת מערך בגודל משתנה { )(int main ;int *a ;int size, i \printf("Enter array size ;)"size\n scanf("%d", ;)scanf("%d", &size )a = (int *) malloc (size * sizeof(int ;))sizeof(int )for (i=0; i<size; i++ scanf("%d", ;)] scanf("%d", &a[i ;)]&a[i )for (i=0; i<size; i++ printf("%d", ;)]printf("%d", a[i ;)]a[i ;return 0 }
דוגמא :הקצאת מערך בגודל משתנה )(int main { ;int *a, size, i \printf("Enter array size קולטים את הגודל הדרוש ;)"size\n scanf("%d", ;)scanf("%d", &size )a = (int *) malloc (size * sizeof(int ;))sizeof(int מקצים מקום )for (i=0; i<size; i++ עכשיו אפשר למשל לקלוט scanf("%d", ;)] scanf("%d", &a[i ;)]&a[i ערכים ולהדפיס אותם ,כמו )for (i=0; i<size; i++ במערך רגיל printf("%d", ;)]printf("%d", a[i ;)]a[i ;return 0 }
הקצאת זיכרון דינאמית •
אם בקשת הקצאת הזיכרון נכשלת ,כלומר אין מספיק זיכרון להקצאה שביקשנו ,אז הפונקציה mallocמחזירה .NULL •
•
NULLהוא קבוע שערכו 0שמוגדר בספריה .stdlib.h
אחרי כל בקשת הקצאה אנחנו צריכים לוודא שאכן הזיכרון שביקשנו התקבל .למשל: *int ;int* a a = (int ;))(int *) malloc (1000 * sizeof(int ;))sizeof(int if (a==NULL ))(a==NULL { \printf("Out of memory ;)"memory\n … return }
הקצאת זיכרון דינאמית •
כל פונקציות ההקצאה הדינאמית נמצאות בספריה stdlib.h (void *malloc )unsigned int size *malloc(unsigned ;)size
•
מקצה sizeבתים .כאמור ,קריאה מוצלחת תחזיר את כתובת תחילת הזיכרון המוקצה ואחרת יוחזר .NULL •
פונקציה נוספת דומה מאוד: void *calloc )*calloc((unsigned int n, unsigned int size_el )size_el
•
מקצה מערך של nאיברים ,כל איבר בגודל size_elבתים ,כל בית מאותחל לאפס .קריאה מוצלחת תחזיר את כתובת תחילת הזיכרון המוקצה ואחרת יוחזר .NULL
הקצאת זיכרון דינאמית •
פונקציה שימושית נוספת: (void *realloc void *ptr, *realloc(void )ptr, unsigned int size ;)size
•
מקבלת מצביע לשטח בזיכרון שהוקצה דינאמית )אותו מצביע שמחזירות ,(malloc/callocומספר בתים .size הפונקציה משנה את גודל ההקצאה בהתאם לדרישה החדשה .אם הדרישה הייתה להגדיל את ההקצאה ואין אפשרות להגדיל את השטח הנוכחי ,מוקצה שטח חליפי במקום אחר ,והמידע מועתק לשם. קריאה מוצלחת תחזיר את כתובת תחילת הזיכרון המוקצה )שלא בהכרח השתנתה( ואחרת יוחזר .NULL
הדגמת קטע תוכנית עם realloc ;int * a ;int size, new_size ;new_size scanf("%d", ;)scanf("%d", &size )a = (int * ) malloc (size* sizeof(int ; ) )sizeof(int ……... ……... …… .. scanf("%d", ;) scanf("%d", &new_size ;)&new_size (a= (int *) realloc *realloc(a, new_size ;))new_size*sizeof(int ;))sizeof(int ……… ………
שחרור זיכרון שהוקצה דינאמית •
•
אמרנו בעבר שמשתנים שמוגדרים בתוך פונקציה נעלמים אוטומטית עם סיומה .זה נכון רק לגבי משתנים שמוגדרים סטטית. משתנים שמוקצים דינאמית לא נעלמים עם סיום הפונקציה. באחריותנו להחליט האם ומתי לשחרר את ההקצאה הזאת ,כפי שנסביר בשקפים הבאים. )(int main )(void f { { . ;int *x . ;))x = (int *)malloc(10 * sizeof(int ;)(f …. . } }
שחרור זיכרון שהוקצה דינאמית •
אם אנחנו לא רוצים להמשיך להשתמש בזיכרון שהקצנו דינאמית בפונקציה, אנחנו צריכים לשחרר את הזיכרון הזה בפונקציה באופן מפורש ,באמצעות פקודת ) freeשתודגם בשקף הבא(.
•
אם אנחנו רוצים להמשיך להשתמש בזיכרון שהקצנו דינאמית בתוך פונקציה אחרי שהיא מסתיימת ,אז היא צריכה להעביר את הכתובת של הזיכרון שהיא הקצתה למי שקרא לה )אחרת לא נדע איך לגשת לשם(. בכל מקרה ,כשנסיים להשתמש בזיכרון הזה נצטרך לשחרר אותו.
•
נשים לב :אם לא נשחרר זיכרון שהקצנו וסיימנו להשתמש בו ,אז אנחנו עלולים למלא את זיכרון המחשב באיזורים שלכאורה תפוסים אבל בעצם לא נגישים ,מה שנקרא "דליפת זיכרון" )עלול להיגמר לנו הזיכרון בגלל זה(.
•
בכל מקרה עד סוף התוכנית צריך לשחרר כל מה שהקצינו דינאמית.
שחרור זיכרון שהוקצה דינאמית • הפונקציה freeמשחררת את הזיכרון ש ptr-מצביע אליו. )void free(void *ptr )ptr ;)free(x • השימוש פשוט ע"י ;)free(x
• ptrצריך להכיל כתובת-התחלה של זיכרון שהוקצה לפני כן, כלומר מה שהוחזר ע"י ) mallocאו callocאו .(realloc • אם ננסה לשחרר משהו אחר ,למשל החל מהמקום השני שהקצינו ,או כתובת של משתנה שלא הוקצה דינאמית ,אז התוכנית תעוף. • כל הזיכרון שמוקצה דינאמית בתוכנית צריך להיות משוחרר לפני שהיא מסתיימת )פקודת reallocדואגת לשחרר אם היא מקצה מקום חדש(. • גם freeנמצאת בספריה stdlib.h
שחרור זיכרון שהוקצה דינאמית -הדגמה )(void f { ;int *x ;))x = (int *) malloc(10 * sizeof(int …. ;)free(x }
נקודה לתשומת-לב :מצביעים לא מאותחלים • חשוב לזכור ,שכמו כל משתנה גם מצביעים אינם מאותחלים בהגדרה שלהם ,וצריך לתת להם ערך. • אם ננסה לגשת לכתובת שנמצאת במצביע בלי שנתנו לו ערך התחלתי ,התוכנית "תעוף" .למשל: ;int *p הכתובת ב p -לא אותחלה ;*p=10 אפשר לגשת לכתובת שנמצאת במצביע רק אם שמנו בו קודם כתובת של משתנה )לא חשוב אם זה משתנה שהוקצה סטטית או דינאמית(.למשל: ;int i ;p=&i או: p=(int ;)) p=(int *) malloc (5*sizeof(int ;))(5*sizeof(int
נקודה לתשומת-לב :מצביעים לא מאותחלים • כזכור ,אפשרות נוספת שראינו למתן ערך למצביע היא ע"י הכנסת כתובת של מערך או של מצביע אחר אליו. ;]int a[10 ;p=a
עוד נקודה לתשומת-לב :מחרוזת קבועה • אם נכניס מחרוזת קבועה למצביע על charבלי לאתחל אותו )לא מחרוזת מהקלט( אז לא תיקרה תעופה ,אפילו שלא בוצעה הקצאה כלשהי ואפילו שלא ניתן להעתיק מחרוזות על-ידי השמה. • זה כיוון שזאת השמה של כתובות :הקומפיילר שומר בזיכרון כל מחרוזת קבועה שהוא מוצא בתוכנית ,ולכן למחרוזת הזאת כבר יש איזשהי כתובת בזיכרון שבה היא מאוחסנת .המצביע רק מצביע לשם. למשל אפשר לכתוב: char *pc, *ps ;;*ps ;"!pc="Hello pc ''?' '\0 '!' ''H' 'e' 'l' 'l' 'o ;"!ps="Hello ps ;"!ps="Hello ;'?'=]pc[5 אבל אחרי השורות: \printf("%s ;)printf("%s\n", ps ;)ps יודפס ? Helloכי שני המצביעים מצביעים לאותה מחרוזת קבועה בזיכרון, שהתו האחרון שלה השתנה.
הקצאת זיכרון דינאמית -סיכום •
אפשר להקצות מקום בזיכרון בזמן הריצה .זה מאפשר בפרט להגדיר מערך בגודל שתלוי בקלט )גודל שלא ידוע מראש(.
•
בספריה stdlib.hנמצאות הפקודות malloc,calloc, realloc, .free
•
צריך לזכור לשחרר מה שהקצינו כשמסיימים להשתמש בזה.
•
כתיבה לכתובת שנמצאת במצביע לא מאותחל תגרום לתעופה – צריך לזכור לאתחל אותו.
מבוא למדעי המחשב – שיעור שלושה-עשר
שאלות נוספות?