מה זה לינקר - Linker, והאיש שבאמת אשם ב-undefined reference שלכם¶
תודו שזה קרה לכם. כתבתם קוד ב-C, הכל נראה תקין, אין שגיאות אדומות בעורך, ואז אתם מקמפלים ומקבלים שורה מפחידה עם "undefined reference to" ושם של פונקציה שאתם בטוחים שכתבתם. הבעיה היא שרוב האנשים חושבים שזו שגיאת קומפילציה, אבל היא לא. היא שגיאה של שלב אחר לגמרי בתהליך, שלב שכמעט אף אחד לא מסביר: הלינקר.
מה בעצם קורה כשאתם מקמפלים קובץ C¶
כשאתם מריצים קומפיילר על קובץ מקור בודד, הוא לא הופך אותו ישר לתוכנה רצה. הוא הופך אותו לקובץ ביניים שנקרא קובץ אובייקט. קובץ האובייקט מכיל שני דברים עיקריים: קוד מכונה בפועל, וטבלת סמלים. טבלת הסמלים היא בעצם רשימה של שני סוגי דברים - סמלים שהקובץ הזה מגדיר, למשל פונקציות שכתבתם בו, וסמלים שהקובץ הזה צריך אבל לא מגדיר בעצמו, כמו קריאה לפונקציה שנמצאת בקובץ אחר, או בספרייה חיצונית.
וזה בדיוק המקום שבו הקומפיילר מסיים את העבודה שלו ומרים ידיים. הוא לא בודק אם הסמלים החסרים באמת קיימים איפשהו. הוא פשוט סומך על כך שמישהו אחר יסתדר עם זה בהמשך.
מקובצי אובייקט לתוכנה אחת רצה¶
זה בדיוק התפקיד של הלינקר. הוא לוקח את כל קובצי האובייקט שנוצרו מכל קבצי המקור שלכם, ולפעמים גם ספריות חיצוניות, ומאחד את כולם לקובץ הרצה אחד. העבודה המרכזית שלו היא לפתור כל הפניה לסמל, ולקשר אותה לכתובת האמיתית שבה הקוד או המשתנה הזה יושב בזיכרון. קראתם לפונקציה שהוגדרה בקובץ אחר? הלינקר הוא זה שמוצא איפה היא נמצאת ומחבר את הקריאה אליה. לא הצליח למצוא אותה באף אחד מהקבצים או הספריות שסיפקתם לו? זה הרגע שבו אתם מקבלים undefined reference. זו לא שגיאת תחביר, וזה לא באג בקוד שלכם במובן הרגיל, זה פשוט סמל שהובטח אבל לא סופק.
קישור סטטי מול קישור דינמי - Static ו-Dynamic Linking¶
עכשיו לחלק שבאמת שווה להבין, כי הוא משפיע על כל תוכנית שאתם מריצים: איך בדיוק הקוד של ספרייה חיצונית, כמו הספרייה הסטנדרטית, מגיע לתוכנית שלכם.
בקישור סטטי, קוד הספרייה פשוט מועתק פיזית לתוך קובץ ההרצה הסופי שלכם. התוצאה היא קובץ עצמאי, שלא תלוי בשום דבר חיצוני כדי לרוץ, אבל גם גדול יותר, כי הוא נושא בתוכו עותק שלם של כל מה שהוא צריך.
בקישור דינמי, קובץ ההרצה שלכם לא מכיל את קוד הספרייה בכלל. הוא רק שומר הפניה לספרייה משותפת, כמו הספרייה הסטנדרטית של השפה, וברגע שהתוכנית רצה, מערכת ההפעלה טוענת את הספרייה הזאת לזיכרון ומקשרת אותה בזמן אמת. היתרון כפול: קובץ ההרצה נשאר קטן, ואם כמה תוכניות במערכת משתמשות באותה ספרייה, כולן חולקות עותק אחד שלה בזיכרון במקום שכל אחת תסחוב עותק משלה.
למה זה שווה להבין ולא רק לדעת בעל פה¶
אם פעם תיתקלו בשגיאת בנייה מוזרה, בהבדל התנהגות בין הרצה על המחשב שלכם להרצה על שרת, או בקובץ הרצה שגדל בלי סיבה ברורה, יש סיכוי טוב שהתשובה נמצאת בדיוק בשלב הזה. זה גם אחד הדברים שמתחילים להבין באמת רק כשעובדים איתם בפועל, לא כשקוראים עליהם. בקורס ליבת המחשב אנחנו עוברים על כל התהליך הזה שלב אחר שלב, מקובץ מקור בודד ועד תוכנית רצה בזיכרון, כולל הקישור שבין הכל.
ואם אתם באמצע הדרך ומתחבטים בשגיאת לינקר שלא נותנת לכם מנוח, יש קהילה שלמה שיכולה לעזור.
לסיכום¶
הלינקר הוא השלב השקט בתהליך שאף אחד לא מדבר עליו, אבל הוא זה שבאמת הופך קבצי קוד נפרדים לתוכנה אחת שלמה שרצה. בפעם הבאה שתראו undefined reference, אתם כבר תדעו בדיוק מה קרה: הקומפיילר עשה את העבודה שלו בסדר גמור, והלינקר פשוט לא מצא איפה הבטחה מסוימת מומשה.