ברוכים הבאים ליחידת הלימוד "ניהול זיכרון מתקדם" בקורס "תכנות מונחה עצמים". יחידה זו חיונית להבנה מעמיקה של אופן פעולת תוכניות C++ והשפעתה על ביצועים ויציבות. שליטה בניהול זיכרון דינמי היא מיומנות קריטית לכל מתכנת, ובמיוחד ב-C++ שבה הזיכרון מנוהל באופן ידני או באמצעות כלים מודרניים כמו מצביעים חכמים. נצלול לעקרונות, לכלים ולבעיות הנפוצות, תוך התמקדות בהיבטים הרלוונטיים לבחינה בטכניון.
יסודות ניהול זיכרון דינמי
ב-C++, אנו עובדים עם שני אזורי זיכרון עיקריים: ה-Stack (מחסנית) וה-Heap (ערימה). בעוד שה-Stack מנוהל אוטומטית עבור משתנים מקומיים, ה-Heap מאפשר הקצאת זיכרון דינמית, כלומר, בזמן ריצה, ודורש ניהול ידני.
מצביעים והפניות (Pointers and References)
מצביעים והפניות הם כלים בסיסיים לעבודה עם זיכרון ב-C++. הם מאפשרים לנו לגשת לנתונים במיקומים ספציפיים בזיכרון.
nullptr.nullptr.מצביעים
יכולים להצביע על nullptr. ניתן לשנות את האובייקט שהם מצביעים עליו. דורשים Dereferencing (באמצעות * או ->) כדי לגשת לערך. מאפשרים אריתמטיקת מצביעים.
הפניות
חייבות תמיד להצביע על אובייקט חוקי (לא יכולות להיות nullptr). לא ניתן לשנות את האובייקט שהן מפנות אליו לאחר אתחול. גישה לערך מתבצעת ישירות (כמו למשתנה רגיל). לא מאפשרות אריתמטיקת מצביעים.
הקצאה ושחרור זיכרון (new/delete)
כדי להקצות זיכרון ב-Heap, אנו משתמשים באופרטור new. שחרור הזיכרון מתבצע באמצעות delete.
new. חובה להשתמש ב-delete[] עבור זיכרון שהוקצה באמצעות new[]. אי-התאמה תוביל להתנהגות בלתי מוגדרת.בעיות זיכרון נפוצות
ניהול זיכרון ידני טומן בחובו סיכונים רבים העלולים להוביל לקריסות, התנהגות בלתי צפויה ופגיעה בביצועים.
מצביעים חכמים (Smart Pointers)
מצביעים חכמים הם עטיפות (wrappers) למצביעים גולמיים (raw pointers) המספקות ניהול זיכרון אוטומטי באמצעות עקרון RAII (Resource Acquisition Is Initialization). הם מבטיחים שזיכרון ישוחרר אוטומטית כאשר האובייקט המצביע החכם יוצא מהסקופ.
std::unique_ptr
בעלות בלעדית על המשאב. לא ניתן להעתיק, רק להעביר (move). מבטיח שרק מצביע אחד מנהל את המשאב בכל רגע נתון. אידיאלי כאשר ברור מי הבעלים של המשאב.
std::shared_ptr
בעלות משותפת על המשאב. משתמש במונה התייחסויות (reference count) כדי לעקוב אחר מספר המצביעים המפנים לאותו משאב. המשאב משוחרר כאשר מונה ההתייחסויות מגיע לאפס. ניתן להעתיק.
std::weak_ptr
מצביע חלש ללא בעלות. משמש בדרך כלל עם std::shared_ptr כדי למנוע רפרנסים מעגליים (circular references) שעלולים לגרום לדליפות זיכרון ב-shared_ptr. לא מגדיל את מונה ההתייחסויות.
שאלות לדיון
- הסבר את ההבדל בין הקצאת זיכרון ב-Stack לבין הקצאה ב-Heap, ומתי נבחר להשתמש בכל אחת מהן.
- נתון קטע קוד המכיל מצביע גולמי. זהה דליפת זיכרון או מצביע תלוי, הסבר מדוע הבעיה מתרחשת, והצע פתרון באמצעות מצביע חכם מתאים.
- כיצד
std::shared_ptrפותר את בעיית דליפות הזיכרון במקרים של בעלות משותפת? באילו מקרים הוא עלול ליצור דליפת זיכרון בעצמו וכיצדstd::weak_ptrמסייע בכך? - הסבר את עקרון RAII וכיצד הוא מיושם על ידי מצביעים חכמים לניהול זיכרון בטוח יותר.
נקודות לתשובת מודל
עבור שאלה העוסקת בזיהוי ותיקון בעיות זיכרון בקוד:
- זיהוי הבעיה: ציין במפורש אם מדובר בדליפת זיכרון, מצביע תלוי, שחרור כפול וכו'.
- הסבר הסיבה: תאר את שרשרת האירועים המובילה לבעיה (לדוגמה: "הזיכרון הוקצה באמצעות
newאך לא שוחרר לפני יציאה מהפונקציה/סקופ"). - השפעה: הסבר את ההשלכות של הבעיה (לדוגמה: "הדבר יוביל לניצול יתר של זיכרון המערכת לאורך זמן").
- פתרון: הצג קוד מתוקן או תאר את השינויים הנדרשים.
- הצדקת הפתרון: הסבר מדוע הפתרון המוצע (לרוב שימוש ב-
std::unique_ptrאוstd::shared_ptr) אכן פותר את הבעיה, תוך התייחסות לעקרונות כמו RAII או מנגנון מונה ההתייחסויות. - בחירת מצביע חכם: נמק את הבחירה במצביע חכם ספציפי (
unique_ptrלבעלות בלעדית,shared_ptrלבעלות משותפת,weak_ptrלמניעת מעגלים).