לדלג לתוכן

מה זו התנהגות לא מוגדרת בשפת C - Undefined Behavior, ולמה זה לא סתם באג

יש משפט ששומעים הרבה בקורסי C, ורוב האנשים לא לוקחים אותו ברצינות מספקת: "זה לא באג, זו התנהגות לא מוגדרת". זה נשמע כמו תירוץ טכני, אבל זה בעצם אחד הדברים הכי חשובים שיש להבין בשפה הזאת, ומי שמתעלם ממנו סופו לגלות את זה בדרך הכי כואבת - כשקוד שעבד מצוין אצלו על המחשב שבור לגמרי אצל מישהו אחר.

אז מה זה בעצם אומר "לא מוגדר"

תקן שפת C לא מגדיר מה קורה בכל מצב אפשרי. יש קבוצה של פעולות שהתקן פשוט אומר עליהן "אני לא מבטיח כלום". זו לא שגיאת קומפילציה, וזו גם לא בהכרח שגיאה שהקומפיילר יתפוס או יתריע עליה. זה חור בהגדרה של השפה, שבו טכנית מותר לקרות כל דבר, כולל דברים שנשמעים בלתי אפשריים - התוכנית יכולה לרוץ בסדר גמור, לקרוס, להדפיס פלט שגוי, או להתנהג אחרת בכל הרצה.

כמה דוגמאות אמיתיות שכדאי שתכירו

בואו נהיה קונקרטיים, כי זה נושא שקל לדבר עליו בהפשטה ולא להבין אותו בפועל:

  • גלישת מספר שלם מסומן - Signed Integer Overflow. חיבור של שני מספרים חתומים שהתוצאה שלהם חורגת מהטווח שהם יכולים להכיל.
  • הפניה למצביע ריק או תלוי - Null או Dangling Pointer. גישה למצביע שלא הצביע לשום דבר, או שהצביע לזיכרון ששוחרר כבר.
  • קריאה ממשתנה שלא אותחל. להשתמש בערך של משתנה לפני שקבעתם לו ערך כלשהו.
  • גישה מחוץ לגבולות מערך. לקרוא או לכתוב לאינדקס שלא באמת קיים במערך.
  • שינוי אותו משתנה פעמיים בין נקודות רצף. למשל ביטוי שבו אותו משתנה גם נקרא וגם משתנה יותר מפעם אחת, בלי שסדר הפעולות מוגדר בבירור.

כל אחת מהדוגמאות האלה יכולה "להצליח" להריץ תוכנית שנראית תקינה על המחשב שלכם, ולהתפוצץ אצל מישהו אחר, על קומפיילר אחר, או אפילו על אותו קומפיילר עם דגל אופטימיזציה שונה.

החלק המפתיע - למה קומפיילרים בעצם מנצלים את זה

הנה החלק שבאמת שווה להפנים, כי הוא משנה איך חושבים על השפה כולה. מכיוון שהתקן קובע שהתנהגות לא מוגדרת "לא אמורה לקרות" בתוכנית תקינה, קומפיילרים מקבלים רשות חוקית מלאה להניח שהיא פשוט לא קורית, ולבצע אופטימיזציות על סמך ההנחה הזאת. המשמעות המעשית היא שקוד שנראה כמו "באג קטן שעובד בכל זאת" יכול להתנהג בצורה שנראית הזויה. דוגמה קלאסית היא בדיקת גבולות שנכתבה כדי להגן מפני גלישה, שהקומפיילר "מוכיח" מבחינה לוגית שהיא תמיד נכונה בהינתן שההתנהגות הלא מוגדרת לא מתרחשת - ופשוט מוחק אותה לגמרי מהקוד הסופי, כי מבחינתו היא מיותרת.

זה בדיוק ההבדל בין "התוכנית שלי רצה אז היא נכונה" לבין "התוכנית שלי נכונה". C נותנת לכם כוח וביצועים עצומים, אבל כמעט בלי רשתות ביטחון, וזה מחיר שצריך להכיר כדי לכתוב קוד אמין באמת.

איך שורדים את זה בפועל

מתכנתי C מקצועיים לא סומכים על "אצלי זה עבד". הם נעזרים בכלים כמו סניטייזרים, למשל דגלי קומפילציה שמפעילים בדיקות בזמן ריצה, שתפקידם לתפוס בדיוק את סוגי ההתנהגות הלא מוגדרת האלה לפני שהן הופכות לבאג בלתי ניתן לאבחון בסביבת ייצור. זה בדיוק סוג ההבנה שאנחנו בונים לעומק בקורס ליבת המחשב, כי בלי להבין מה קורה מתחת לשפה עצמה, קשה מאוד לכתוב קוד C שאפשר לסמוך עליו.

ואם אתם כרגע מתמודדים עם באג שנראה בלתי הגיוני, יש סיכוי סביר שהתנהגות לא מוגדרת מעורבת בסיפור, ושווה להתייעץ עם אנשים שכבר נתקלו בזה.

הצטרפו לקהילה בדיסקורד

לסיכום

התנהגות לא מוגדרת היא לא באג ברשלנות של השפה, היא חלק מהעסקה שאתם עושים כשאתם בוחרים ב-C: שליטה וביצועים תמורת אחריות מלאה על מה שמותר ומה שאסור לעשות. ברגע שתפנימו את זה, תפסיקו לשאול "למה זה קרס" ותתחילו לשאול "איפה בקוד שלי אני נותן לקומפיילר רשות להניח דברים שאני לא באמת מבטיח".