סטראקטים בשפת C מוסברים - איך בונים מבנה נתונים מהיסוד¶
בשפות מודרניות כמו פייתון או ג'אווהסקריפט, אתם פשוט מגדירים אובייקט וזהו. ב-C אין אובייקטים מובנים, אין קלאסים, אין שום דבר שמאגד כמה ערכים תחת ישות אחת - עד שבונים אותו בעצמכם, עם סטראקט. הבנת סטראקטים היא בדיוק הרגע שבו C מתחיל להרגיש כמו שפה שאפשר לבנות איתה תוכנות אמיתיות, ולא רק לחשב מספרים בודדים.
אז מה זה בעצם סטראקט¶
סטראקט - struct, הוא טיפוס מותאם אישית שמאגד כמה משתנים, אולי מטיפוסים שונים, תחת שם אחד. אם רוצים לייצג נקודה במרחב דו ממדי, אפשר להגדיר:
עכשיו struct Point הוא טיפוס חדש, וכל משתנה מהטיפוס הזה יכיל בתוכו גם x וגם y, ביחד, כישות אחת. גישה לשדות נעשית עם נקודה: p.x ו-p.y. זה לא הרבה יותר מקיבוץ נוח של משתנים - אבל הקיבוץ הזה הוא בדיוק מה שמאפשר לבנות מבני נתונים מציאותיים, מרשומת עובד עם שם וגיל ומשכורת, ועד צומת ברשימה מקושרת.
איך זה נראה בזיכרון בפועל¶
זה המקום שבו סטראקט מפסיק להיות רק נוחות תחבירית ומתחיל להיות עניין של הבנת חומרה. סטראקט לא "מרחף" איפשהו מופשט - הוא תופס בלוק רציף אחד בזיכרון, שבו כל שדה יושב אחרי הקודם, לפי הסדר שהוגדר בקוד. אם int תופס ארבעה בתים, אז struct Point שלנו תופס בסך הכל שמונה בתים - ארבעה ל-x, וארבעה ל-y, אחד אחרי השני.
אבל זה לא תמיד כל כך פשוט. מעבדים עובדים הכי יעיל כשמשתנים ממוקמים בכתובות שמתחלקות בגודל שלהם - עיקרון שנקרא יישור זיכרון - alignment. בגלל זה, המהדר לפעמים מוסיף בתים ריקים, padding, בין שדות בסטראקט, כדי לוודא שכל שדה מתחיל בכתובת "נוחה" למעבד. זו הסיבה שגודל של סטראקט לא תמיד שווה בדיוק לסכום הגדלים של השדות שלו - לפעמים הוא גדול יותר, בגלל ריפוד נסתר שהמהדר הוסיף.
פוינטר לסטראקט - החץ שכל מתכנת C מכיר¶
כשעובדים עם סטראקטים דרך פוינטר, במקום עם המשתנה עצמו, הגישה לשדות משתנה תחבירית. אם p הוא פוינטר לסטראקט, במקום לכתוב (*p).x, שזה נכון אבל מסורבל, C מציעה קיצור נוח:
האופרטור -> פשוט אומר "עשה דה-רפרנס לפוינטר, ואז ניגש לשדה הזה". זה בדיוק אותו רעיון שראינו עם פוינטרים רגילים, רק עם תחביר קצר יותר שנוח כשעובדים עם סטראקטים דרך כתובת ולא ישירות.
סטראקטים מקוננים ומבני נתונים אמיתיים¶
סטראקט יכול להכיל בתוכו שדה שהוא עצמו סטראקט אחר, וזה הבסיס לבניית מבני נתונים מורכבים. הדוגמה הכי מוכרת היא רשימה מקושרת - כל צומת הוא סטראקט שמכיל ערך, ופוינטר לצומת הבא מאותו טיפוס:
הצומת הזה מצביע על הצומת הבא, שמצביע על הבא אחריו, וכך נבנית שרשרת שלמה. כל עץ בינארי, כל רשימה כפולה, וכל מבנה נתונים דינמי בעולם ה-C, בסופו של דבר בנוי מהרעיון הפשוט הזה - סטראקטים שמכילים פוינטרים לסטראקטים נוספים.
למה זה חשוב גם מעבר לC עצמה¶
ההבנה איך סטראקט מסודר בזיכרון לא נשארת רק תיאורטית. פורמטים בינאריים כמו ELF, פרוטוקולי רשת, ואפילו הדרך שבה מערכת ההפעלה מתקשרת עם דרייברים, כל אלה מתוארים בפועל כסטראקטים עם פריסה מדויקת בזיכרון. מי שמבין padding ויישור זיכרון, מבין גם למה קריאה גולמית של קובץ בינארי לתוך סטראקט לא תמיד מסתדרת בקלות בין מערכות שונות.
זו בדיוק הרמה של הבנה שאנחנו בונים בקורס ליבת המחשב - לא רק איך כותבים סטראקט, אלא איך הוא נראה בפועל בזיכרון, ולמה זה משנה.
ואם הנושא הזה מסתבך אצלכם, יש קהילה שכבר עברה אותו ותשמח לעזור.
לסיכום¶
סטראקט הוא הדרך של C לתת לכם לבנות מבנה נתונים משלכם, מאפס. הוא מקבץ כמה שדות תחת שם אחד, נמצא כבלוק רציף בזיכרון, ולפעמים מכיל ריפוד נסתר בגלל יישור זיכרון. ברגע שמבינים איך הוא נראה מתחת למכסה, גם מבני נתונים מסובכים יותר כמו רשימות מקושרות ועצים מפסיקים להיות מסתורין ומתחילים להיראות כמו הרחבה טבעית של אותו רעיון פשוט.