מחסנית הקריאות
קריאות LIFO
הצגנו את מושג המחסנית מכיוון שמסתבר שכך נראת תמונת הזכרון בקריאה לפונקציות, ולכן המחסנית היא דרך טבעית להחזיק את המידע בזכרון. מה הכוונה? נבחן את התוכנית הבאה:
#include <stdio.h>
void f2(int x) {
printf("f2 started: arg = %d\n", x);
printf("f2 finished: arg = %d\n", x);
}
void f1(char c) {
printf("f1 started: arg = %c\n", c);
f2(42);
printf("f1 finished: arg = %c\n", c);
}
int main() {
printf("main started\n");
f1('a');
printf("main finished\n");
}
ננתח את סדר הקריאות בתוכנית:
- לאחר הקריאה לפונקציית ה-
main, אנחנו קופצים ל-f1, כאשרmainעוד לא סיימה. f1קוראת מיד ל-f2, כאשרmainו-f1עוד לא הסתיימו.f2מתחילה ביצוע, קוראת ל-printfואז מסיימת בעצמה -mainו-f1עוד לא הסתיימו.- לאחר החזרה מ-
f2, הפונקציהf1קוראת ל-printfואז מסיימת בעצמה -mainעדיין לא הסתיימה. - לאחר החזרה מ-
f1פונקציית ה-mainקוראת ל-printfואז מסתיימת בעצמה.
כלומר, סדר הקריאה הוא main->f1->f2, וסדר הסיום הוא f2->f1->main - בדיוק הפוך, או LIFO!
מחסנית הקריאות
למעשה, מתברר כי התוכנית שלנו מחזיקה מחסנית הנקראת מחסנית הקריאות על מנת לייצר את מנגנון הפונקציות. הקומפיילר משתמש במחסנית הקריאות בשביל לשמור את כל המידע הנדרש לביצוע הפונקציה - כשהכי רלוונטי לצרכינו הוא הארגומנטים והמשתנים הלוקאליים של הפונקציה הנקראת. מחסנית הקריאות היא חלק מהזכרון הכולל של התוכנית ולרוב נקראת פשוט ״המחסנית״ או ה-stack.
כיצד זה עובד?
-
כאשר פונקציה נקראת, התוכנית דוחפת (push) על מחסנית הקריאות שבזכרון אובייקט עם כל המידע הרלוונטי לביצוע הפונקציה - ארגומנטים, משתנים לוקאליים וכו׳.
-
אובייקט זה נקרא stack frame.
-
הפונקציה הנקראת משתמשת בזכרון שהוקצה לה על מחסנית הקריאות - קוראת ארגומנטים, משתמשת במשתנים לוקאליים וכו׳.
-
בסיום הפונקציה הנקראת, התוכנית מוציאה (pop) ממחסנית הקריאות את ה-stack frame האחרון, בו השתמשה בפונקציה הנקראת. פעולה זו נהוג לקרוא קיפול (fold) של המחסנית. במידה והיה ערך חזרה מהפונקציה, גם הוא נקרא מתוך מחסנית הקריאות לפני שהיא מקופלת.
-
הפונקציה הקוראת ממשיכה לבצע מאחרי הנקודה בה התבצעה הקריאה.
Note
חלק מעניין נוסף ב-stack frame שלא נתעסק בו בקורס הוא כתובת החזרה. במהלך קריאה לפונקציה, התוכנית שומרת את המקום ממנו קפצה לפונקציה על מנת שתדע לאן לחזור בסיום הפונקציה הנקראת.
Note
בהמשך נפגוש איזור נוסף בזכרון שאינו על המחסנית בשם הערימה (heap).
דוגמא
נצייר את מחסנית הקריאות של התוכנית הבאה, המחשבת ומדפיסה שטח של אליפסה:
#include <stdio.h>
#define PI 3.14
double calculate_ellipse_area(double r1, double r2) {
return PI * r1 * r2;
}
void show_ellipse_area(double r1, double r2) {
printf("area: %.2lf", calculate_ellipse_area(r1, r2));
}
int main() {
double r1, r2;
scanf("%lf%lf", &r1, &r2);
show_area(r);
}
בתחילת התוכנית התוכנית מחסנית הקריאות נראת כך:
כלומר, המחסנית מכילה רק את המידע הנדרש עבור main. לאחר הקריאה ל-show_ellipse_area נקבל את התמונה הבאה:
דחפנו על המחסנית עותקים של המשתנים r1 ו-r2. נדגיש - המשתנים r1, r2 בפונקציה main אינם באותו מקום בזכרון כמו r1, r2 של show_ellipse_area, או של calculate_ellipse_area. על אף שיש להם את אותו שם ואתו ערך, הם לא אותו משתנה וכתיבה לאחד מהם לא תשנה את האחר.
בצורה דומה לחלוטין דחפנו עותקים נוספים על המשתנים.
Note
לא ציירנו על המחסנית את הקריאה ל-
printfאבל היא גם נמצאת על מחסנית הקריאות.