13 זיכרון דינאמי

Page 1

‫קורס מבוא למדעי המחשב‬ ‫סמסטר א' תשס"ח‬

‫שיעור שלושה‪-‬עשר‪:‬‬ ‫הקצאת זיכרון דינאמית‬ ‫‪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‬‬

‫•‬

‫צריך לזכור לשחרר מה שהקצינו כשמסיימים להשתמש בזה‪.‬‬

‫•‬

‫כתיבה לכתובת שנמצאת במצביע לא מאותחל תגרום לתעופה‬ ‫– צריך לזכור לאתחל אותו‪.‬‬

‫מבוא למדעי המחשב – שיעור שלושה‪-‬עשר‬

‫שאלות נוספות?‬


Issuu converts static files into: digital portfolios, online yearbooks, online catalogs, digital photo albums and more. Sign up and create your flipbook.