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

הפונקציה malloc משמשת להקצאת זיכרון דינמית בזמן ריצה. בשפת C, כאשר אנו מכריזים על משתנים רגילים (כמו int x; או char str[100];), הם נשמרים בזיכרון סטטי או אוטומטי – כלומר, הקומפיילר קובע מראש כמה זיכרון להקצות, ולכמה זמן. לעומת זאת, כשאנו רוצים להקצות זיכרון בגודל משתנה, או זיכרון שיחזיק מעמד מעבר לסיום פונקציה, אנחנו חייבים להשתמש בהקצאה דינמית.

הפונקציה malloc (קיצור של "memory allocate") מקצה כמות זיכרון שנרצה – ומחזירה מצביע (pointer) לתחילת האזור הזה. אם ההקצאה הצליחה – נקבל כתובת. אם היא נכשלה (למשל בגלל חוסר בזיכרון) – נקבל NULL.

int *arr = malloc(10 * sizeof(int));

השורה הזאת מקצה מקום ל־10 מספרים שלמים בזיכרון, כלומר 10 תאים של sizeof(int) בתים כל אחד (בדרך כלל 4). הפונקציה מחזירה כתובת למקום הראשון – ואנחנו שומרים אותה במשתנה מסוג int*.

מאפיינים חשובים:

  • התאים לא מאותחלים – התוכן שלהם יכול להיות אשפה (זיכרון אקראי מהעבר).
  • המשתמש אחראי לשחרור הזיכרון (בעזרת הפונקציה free), אחרת נגרם דליפת זיכרון. (זכרון שקיים בתוכנה ואף אחד לא עושה לו free, והוא לוקח משאבים)
  • הקצאה דינמית נשמרת ב־heap, (אזור זכרון מוגדר) כלומר קיימת עד שנשחרר אותה ידנית.
    כל הזכרון הדינמי נמצא בheap, כמו שיש לנו את הdata segment, ואת הcode segment, ואת הstack, יש לנו גם את הheap. (בהמשך נלמד יותר כיצד הוא עובד.)

דוגמה:

int *arr = malloc(5 * sizeof(int));
if (arr == NULL) {
    printf("שגיאה בהקצאת זיכרון\n");
    exit(1);
}
arr[0] = 10;
arr[1] = 20;
// ...
free(arr);

הפונקציה calloc גם משמשת להקצאת זיכרון דינמית, אך בשונה מ־malloc, היא מאפסת את כל הזיכרון לאפס. כלומר, כל תא יהיה מאותחל ל־0 (או \0 במקרה של תווים).

int *arr = calloc(5, sizeof(int));

המשמעות: הקצה מקום ל־5 מספרים שלמים (int-ים) – והפוך את כולם ל־0.

בנוסף, הפונקציה לא מקבלת רק גודל בבתים של buffer שהיא צריכה לאלקץ, אלה גם פרמטר של גודל הסוג של הbuffer (כלומר הtype), וגם הגודל שלו (קצת יותר נוח מmalloc)

למה להשתמש ב־calloc ולא ב־malloc?

  • אם אתה רוצה שכל המשתנים יהיו אפס כבר בהתחלה – עדיף calloc.

  • אם אתה מתכוון לדרוס את כל הערכים ידנית מיד לאחר מכן – עדיף malloc כי הוא מהיר יותר.


הפונקציה realloc מאפשרת לשנות את גודל ההקצאה הקודמת. כלומר, אם הקצית מקום ל־10 תאים ואתה רוצה עכשיו 20 – לא צריך להקצות מחדש ולהעתיק ידנית, אלא פשוט:

arr = realloc(arr, 20 * sizeof(int));

אם יש מספיק מקום בזיכרון – ההרחבה תיעשה במקום. אם לא – המערכת תקצה מקום חדש, תעתיק לשם את התוכן הישן, ותשחרר את ההקצאה הישנה.

שימושים עיקריים:

  • בניית מערכים שגדלים בזמן ריצה.

  • קריאה של קבצים לא ידועים בגודלם.

יש לבדוק תמיד אם realloc הצליחה – ואם לא, להשאיר את ההקצאה הקודמת זמינה:

int *temp = realloc(arr, 20 * sizeof(int));
if (temp == NULL) {
    // לא הצליח – arr נשאר תקף
    printf("שגיאת הקצאה\n");
    free(arr);
    exit(1);
}
arr = temp;

הפונקציה free משחררת הקצאת זיכרון שנעשתה בעזרת malloc, calloc, או realloc. חובה להשתמש בה כדי להחזיר את הזיכרון למערכת – אחרת התוכנית צוברת זיכרון מיותר, מה שנקרא דליפת זיכרון (memory leak).

int *arr = malloc(100 * sizeof(int));
// שימוש ב־arr
free(arr);

אחרי הקריאה ל־free, אסור לגשת יותר ל־arr – המצביע עדיין קיים, אבל התוכן שלו שייך עכשיו למערכת. גישה אליו היא שימוש בזיכרון לא חוקי ועלולה לגרום לקריסה.

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

free(arr);
arr = NULL;

לסיכום:

  • malloc – הקצאה ללא אתחול

  • calloc – הקצאה עם אפסים

  • realloc – שינוי גודל של הקצאה קיימת

  • free – שחרור ההקצאה

כל הקצאת זיכרון שנעשית ב־heap נשארת בזיכרון עד שנשחרר אותה בעצמנו. לעומת זאת, משתנים רגילים (כמו int x = 3;) משוחררים אוטומטית כשפונקציה מסתיימת או כשבלוק קוד נגמר.


למה צריך הקצאה דינמית?

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

אם אתה כותב תוכנה עם מבנים שמשתנים בגודל במהלך הריצה – תצטרך לדעת לנהל הקצאה דינמית בצורה בטוחה ויעילה.

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