1.10 - תקינות והתנהגות לא מוגדרת - פתרון
פתרון - הבדיקה עצמה חייבת להיות מוגדרת¶
הקוד הבא בודק את החיבור בלי לחשב קודם תוצאה שעלולה לגלוש:
#include <cassert>
#include <limits>
#include <optional>
std::optional<int> checked_add(int a, int b) {
constexpr int high = std::numeric_limits<int>::max();
constexpr int low = std::numeric_limits<int>::min();
if (b > 0 && a > high - b) return std::nullopt;
if (b < 0 && a < low - b) return std::nullopt;
return a + b;
}
int main() {
constexpr int high = std::numeric_limits<int>::max();
constexpr int low = std::numeric_limits<int>::min();
assert(checked_add(0, 0) == 0);
assert(checked_add(4, 5) == 9);
assert(checked_add(-4, -5) == -9);
assert(checked_add(8, -3) == 5);
assert(checked_add(high, 0) == high);
assert(checked_add(low, 0) == low);
assert(!checked_add(high, 1));
assert(!checked_add(low, -1));
assert(checked_add(low, high) == -1);
}
כאשר b חיובי, הביטוי high minus b נשאר בתחום התקין. כאשר b שלילי, הביטוי low minus b מתקדם מן הגבול התחתון כלפי מעלה וגם הוא נשאר בתחום. רק לאחר שעברנו את שתי הבדיקות מבצעים את החיבור.
לא משתמשים ב־-b ללא בדיקה, מפני שהשלילה של הערך המזערי של int אינה ניתנת לייצוג באותו טיפוס. זהו מקרה שבו תיקון לכאורה יכול להכניס overflow חדש לתוך קוד הבדיקה.
תיקון חיי המשאב¶
אם הצרכן צריך להשתמש בערך באותו scope, התיקון יכול להיות פשוט:
#include <cstdio>
#include <memory>
int main() {
auto value = std::make_unique<int>(42);
std::printf("%d\n", *value);
}
ההדפסה מתבצעת כשהבעלים חי. בסוף scope המשאב משתחרר, ואין alias שנשמר לעבודה מאוחרת. אם הסיבה המקורית הייתה callback שנקרא אחרי reset, צריך לתקן את חוזה הcallback ולא רק להחליף new ב־make_unique.
בדיקת regression צריכה להפעיל את המסלול המקורי של שחרור וצריכה מאוחרת. אם רק מריצים main פשוט שכבר אינו כולל callback, אין לנו הוכחה שהתיקון במערכת הגדולה באמת מכסה את הנתיב שנשבר.
פירוש של כשל¶
ב־signed overflow הקוד מפר כלל שפה, ולכן צריך לשנות את החישוב או את הגדרת הטווח. בסדר הערכה שהתקן מתיר, צריך לכתוב קוד שהמשמעות שלו נכונה בכל סדר מותר או להוסיף סדר מפורש.
בכשל פתיחת קובץ, ה־API יכול להתנהג בדיוק לפי חוזהו ולהחזיר שגיאה. תגובת היישום היא טיפול בשגיאה, ניקוי משאבים ודיווח מתאים. שימוש בתוצאת הכשל כאילו הייתה משאב תקף הוא כבר טעות נפרדת שלנו.
דוח האבחון צריך לציין אילו בדיקות הורצו בפועל. ההוכחה הלוגית של הכשל אינה דורשת להמציא stack trace, וגם היעדר סביבת sanitizer אינו מצדיק דיווח על בדיקה שלא התקיימה.