10.2 התנהגות לא מוגדרת תרגול
תרגול - התנהגות לא מוגדרת - undefined behavior¶
תרגול 1 - זיהוי UB בקוד¶
לפניכם קטעי קוד. לגבי כל אחד, ענו: האם יש כאן UB? אם כן, באיזה סוג מדובר ומה עלול לקרות?
קטע א:
קטע ב:
קטע ג:
קטע ד:
קטע ה:
קטע ו:
תרגול 2 - signed overflow ואופטימיזציות¶
- כתבו את הפונקציה הבאה:
- קמפלו ללא אופטימיזציות (
gcc -O0) והריצו עםx = INT_MAX. מה התוצאה? - קמפלו עם אופטימיזציות (
gcc -O2) והריצו עםx = INT_MAX. מה התוצאה? - הסבירו את ההבדל.
- כתבו גרסה בטוחה של הבדיקה שעובדת נכון גם עם INT_MAX (רמז: בדקו לפני החיבור אם הוא יגלוש).
תרגול 3 - שימוש ב-sanitizers¶
- כתבו תוכנית קצרה שמכילה את כל סוגי הUB הבאים (כל אחד בפונקציה נפרדת):
- גלישת signed integer
- גישה מחוץ לגבולות מערך
- שימוש במשתנה לא מאותחל
- חילוק באפס
- קמפלו עם
-fsanitize=undefinedוהריצו. שימו לב להודעות השגיאה - הsanitizer מראה בדיוק באיזו שורה ומאיזה סוג הUB. - קמפלו עם
-fsanitize=addressוהוסיפו פונקציה עם use-after-free. הריצו וראו את הפלט.
תרגול 4 - strict aliasing¶
- כתבו את הקוד הבא:
- קמפלו עם
-O2 -fstrict-aliasing -Wallובדקו אם יש אזהרות. - כתבו תוכנית שמשתמשת בשתי הגרסאות כדי להמיר את הerך
0x40490FDB(שזה הייצוג הבינארי של pi ב-IEEE 754) ל-float. האם שתי הגרסאות נותנות את אותו התוצאה? - הסבירו למה הגרסה עם
memcpyבטוחה ולמה הגרסה עם cast היא UB.
תרגול 5 - כתיבת קוד בטוח¶
כתבו את הפונקציות הבאות באופן בטוח, ללא שום UB:
int safe_add(int a, int b, int *result)- חיבור בטוח שמחזיר 0 בהצלחה ו-(-1) אם יש גלישה (בדקו לפני החיבור).int safe_div(int a, int b, int *result)- חילוק בטוח שמחזיר (-1) אם b הוא 0 או אם מדובר ב-INT_MIN / (-1) (שגם הוא UB).int safe_shift(int val, int shift, int *result)- הזזה בטוחה שמחזירה (-1) אם ה-shift לא חוקי.
קמפלו עם -fsanitize=undefined ובדקו שאף אחת מהפונקציות לא מפעילה UB, גם עם קלטים קיצוניים כמו INT_MAX, INT_MIN, 0, ומספרים שליליים.