ניהול זיכרון בשפת C++ הוא בין הנושאים הקריטיים ביותר שיש להבין אותם. לכן כאשר מתבצעים הקצאות יש להבין כיצד יש לעשות את ההקצאה והאם יש דרך יותר טובה לעשות אותה?
איך לשמור על זיכרון ואיך למנוע טעויות בשימושו?
בפוסט הקצר הזה נלמד על מהי מחסנית, מה זה זיכרון Heap ומה ההבדל בין השניים.
מה זו מחסנית
מחסנית בהגדרתו הוא מבנה נתונים מסוג של LIFO - Last in First out. האיבר האחרון שהכנסנו הוא יהיה האחרון שייצא.
אולם, המחסנית כזיכרון הוא אזור זיכרון קטן שמוקצה מראש לדחיפה והוצאה של זיכרון בצורה מלאה.
מאפיינים של המחסנית
הקצאת זיכרון מהירה: כוללת הזזה בלבד של מצביע במחסנית.
גודל מוגבל: בדרך כלל הגודל קבוע מראש לכל מחסנית.
שחרור אוטומטי: מכיוון שהקצאה כוללת רק הזזה של מצביע - השחרור הוא כמעט אוטומטי ע”י הזזה חזרה של המצביע.
דוגמת קוד
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include<iostream>
voidUseStackMemory() { int stackArray[10]; // מערך בגודל 10 מוקצה במחסנית
הקצאה גדולה מדי של זיכרון או לחילופין קריאה רקורסיבית לפונקציה שאינה נגמרת תנצל את המחסנית עד הסוף. שגיאה כזו נקראת Stack overflow exception.
1 2 3 4 5 6 7 8 9 10 11 12 13
#include<iostream>
voidCauseStackOverflow(int count){ int largeArray[100000]; // יגרום לשטפון מחסנית אם יקרא יותר מדי פעמים std::cout << "Stack frame: " << count << std::endl; CauseStackOverflow(count + 1); }
intmain() { CauseStackOverflow(1); return0; }
מהו ה-Heap
זהו אזור זיכרון הרבה יותר רחב שמוקצה ממערכת ההפעלה לאפליקציה. במערכות הפעלה 32bit האזור היה אכן מוגבל רק ל-4 ג’יגה. בדרך כלל 2 היה מוקצה עבור מערכת ההפעלה ועוד 2 ג’יגה מוקצים לאפליקציה אז כמות הזיכרון הייתה עוד יותר מוגבלת.
במערכת הפעלה 64bit הגודל גדל למימד עצום מבחינה מעשית ולכן אין כל בעיה כיום.
מאפיינים
גודל דינאמי - ניתן להקצות עוד ועוד זיכרון כל עוד יש זיכרון פיזי שיכול לגבות אותו.
שחרור חצי ידני - יש לשחרר זיכרון באופן ידני או במכניקה אחרת כגון RAII.
הקצאות איטיות - מכיוון שזהו אזור גדול שיש לגדר אותו ולתפעל אותו ההקצאה יותר איטית. בדרך כלל זה צורך מהמערכת הפעלה לטעון קטעי זיכרון שלא טעונים לגישה מידית. זה נקרא עקרון ה-Cache.
דוגמת קוד
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
#include<iostream>
voidUseHeapMemory() { int* heapArray = newint[10]; // מערך בגודל 10 מוקצה בזיכרון דינאמי
מבנים מורכבים ודינאמיים שלא יודעים כמה ייקח זיכרון לפני
שימוש
סיכום
בעוד שהמחסנית מהירה ומנהלת זיכרון אוטומטית הגודל המוגבל שלה הופך אותה ללא מתאימה למבנים מורכבים או גדולים. זיכרון דינאמי מציע גמישות אך זה גורר הקצאות איטיות יותר והצורך לנהל את הזיכרון.
הבנה עמוקה של מבנה הזיכרון ותפקיד של כל מבנה זיכרון ייעזור בבניית תוכנות יעילות שלא מבזבזות משאבים!
בונוס - זיכרון דינאמי במערכות הפעלה
רוב הזיכרון שנצרוך הוא מהזיכרון הדינאמי. להבין איך מערכות הפעלה עובדות זה די ה-101 בשביל להבין איך לעבוד עם זיכרון.
בווינדוס יש לנו זיכרון דינאמי שנקרא - Low Fragmentation Heap. אחרי ויסטה זה נמצא בשימוש תמידי בתוך Processes.
בלינוקס יש שימוש במבנה אחיד ויחיד - Single Heap. ניתן כמובן לעבוד עם Sub-Processes בצורות שונות בהתאם לצרכי התוכנה.
Options: -x, --extended show details -X show even more details WARNING: format changes according to /proc/PID/smaps -XX show everything the kernel provides -c, --read-rc read the default rc -C, --read-rc-from=<file> read the rc from file -n, --create-rc create new default rc -N, --create-rc-to=<file> create new rc to file NOTE: pid arguments are not allowed with -n, -N -d, --device show the device format -q, --quiet do not display header and footer -p, --show-path show path in the mapping -A, --range=<low>[,<high>] limit results to the given range
-h, --help display this help and exit -V, --version output version information and exit