Dangling & Null Pointers
מעתה והלאה, כמעט כל תוכנית שנכתוב תכלול מצביעים בדרך כזו או אחרת. בהתאמה, חשוב להבין בצורה עמוקה את תמונת הזכרון של התוכנית שלנו. קל מאוד לעשות טעויות בעבודה עם מצביעים המוביל לבאגים קשים. נבחן את הדוגמא הבאה:
#include <stdio.h>
int main() {
int *ptr;
scanf("%d", ptr);
printf("%d\n", *ptr);
}
זהו לכאורה קטע קוד תקין לחלוטין - אנחנו מעבירים מצביע לפונקציה scanf כמו שראינו בעבר. עם זאת, קטע קוד זה יכשל מיד. מדוע? נבחן את תמונת הזכרון:
ננתח את התוכנית:
-
ב-stack frame של הפונקציה
mainנמצא המשתנהint *ptr. הוא מצביע, אבל הוא אינו מאותחל. -
כפי שראינו, משתנים לא מאותחלים מכילים ערך זבל. ערך הזבל הוא הוא מספר כלשהו, אבל הוא בבירור לא כתובת מדויקת של אף משתנה בתוכנית שלנו.
-
scanfתקרא את ערך הזבל הזה ותנסה לפרש אותו ככתובת של משתנה בזכרון על מנת לכתוב אליו את התוצאה. -
גישה לכתובת לא חוקית בזכרון היא שגיאה ידועה לשמצה בשפת C הנקראת בדרך כלל segmentation fault.
Dangling Pointer
מצביע שאינו מאותחל ו/או מצביע לאף כתובת חוקית נקרא dangling pointer. באופן כללי, ביצוע dereference ל-dangling pointer גורם ל-segmentation fault שמקריסה את התוכנית.
בגלל פוטנציאל הסיכון הגדול בשימוש במצביעים לא חוקיים, כדאי לנקוט משנה זהירות בכל שימוש במצביע. מסקנה אחת נדרשת היא שכדאי לאתחל את המצביעים שלנו תמיד. עם זאת, לאיזה ערך כדאי לאתחל?
השפה מציע עבורנו ערך ברירת מחדל למצביעים בשם NULL שבהגדרה לא מצביע לאף כתובת חוקית. נהוג לאתחל מצביעים ל-NULL כאשר לא ניתן מיד לתת להם כתובת של המשתנה אליו הם מצביעים. הקבוע NULL מוגדר (בנוסף למקומות אחרים) בקובץ <stdio.h> בו עשינו כבר לא מעט שימוש.
נהוג גם “להגן” על פונקציות משימוש בפרמטרים לא חוקיים - למשל במקרה של swap:
#include <stdio.h>
void swap(int* a, int* b) {
if (a == NULL || b == NULL) {
printf("passed NULL pointer!\n");
return;
}
// ... rest of swap
}
לרוב לא נשתמש בסינטקס של ptr == NULL, אלא בצורה הבאה השקולה לו:
#include <stdio.h>
void swap(int* a, int* b) {
if (!a || !b) {
printf("passed NULL pointer!\n");
return;
}
// ... rest of swap
}
Note
הסיבה ששתי הצורות
ptr == NULLו-!ptrשקולות היא ש-NULLמוגדר להיות הערך0רק בצורה של מצביע:
#define NULL ((void*)0)
כך שתוצאת הביטוי NULL! היא 0!, שבפירוש כמשתנה בוליאני היא true.