DCSIMG
Arithmetic Overflow Checking - Liran Chen's Blog

Liran Chen's Blog

.Net Internals, Debugging, Multithreading - and More!

Arithmetic Overflow Checking

שאלה: האם לתוכניות שרצות בדוט-נט יש בדיקה אוטומטית עבור Arithmetic Overflows?
קחו לדוגמה את התוכנית הבאה, מה לדעתכם יהיה הפלט? או שבכלל תזרק שגיאה?

class Program

{

static void Main(string[] args)

{

int foo = 1;

int bar = int.MaxValue + foo;

Console.WriteLine(foo);

}

}


אם הניחוש שלכם היה 2147483648- (שווה ערך ל int.MinValue), צדקתם.
זאת טעות נפוצה לחשוב שכברירת מחדל הסביבה תבדוק כל הזמן האם קיימת אי התאמה בין גודל הערך שאותו אנחנו מנסים להכניס למשתנה. רוב המפתחים מצפים שעל פעולה כמו בדוגמה למעלה תזרק אוטומטית שגיאה. אבל, לא.
הסיבה שאנחנו מקבלים את הערך "המוזר" הזה, הוא שהתוצאה של החישוב שלנו חורגת מהגודל ש-int יכול להכיל. כלומר, בסביבה של 32 ביט, גודלו של int הוא 4 בתים. לכן, המספר החיובי הכי גדול שהוא יכול להכיל הוא 2147483647 (הכוונה כאן היא כאמור ל-signed int). ברגע שנרצה להחזיק ערך גדול יותר, נהיה חייב להקצות עוד בתים. במקרה שלנו נצטרך להשתמש ב-long (שמחזיק 8 בתים).
אז מה שקורה בפועל זה שהבתים הנוספים שאמורים להשתמש בהם כדי להחזיק את הערך החדש, למעשה "מקוצצים" (truncated), וכך אנחנו מקבלים ערך חדש ומזובל (הביט האחרון ב-signed types מייצג את סימון המינוס, ומכאן המספר השלילי).

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

איך ניתן להתמודד עם הבעיה?
בדיקות Arithmetic Overflows בדוט נט זקוקות להוראות ספציפיות דרך ה-CIL. כלומר, זהו לא תפקידו של ה-CLR לפקח כל הזמן על כל פעולה קטנה שאנחנו עושים ולבדוק האם אנחנו גולשים בטעות מהגודל המותר. במקום זה, בכל פעם שאנחנו רוצים לבצע בדיקת Overflow הקוד המקופל חייב להכיל הוראה "תבדוק מה הולך כאן". רק במצב כזה תבוצע בדיקה על הערך ובמידה וה-CLR רואה שהולכת לקרות כאן גלישה, הוא יזרוק שגיאת OverflowException.
כדי לבצע את בדיקת החוקיות הזאת, אפשר לנקוט באחת משתי דרכים. האחת, שימוש במילת המפתח Checked כדי לתחום אזורים בקוד בהם אנחנו מעוניינים לבצע בדיקות Overflow. השניה, עדכון סוויץ' שעובר לקומפיילר בזמן קימפול הפרוייקט ולמעשה גורם לו לבצע בדיקת Overflow בכל מקום בקוד. כברירת מחדל האפשרות הזאת מבוטלת, אפשר לאפשר אותה דרך אפשרויות הפרוייקט, תפריט Build, דרך Advanced ואז לסמן את האפשרות Check for arithmetic overflow/underflow.


לכל בדיקות ה-Overflows האלו יכולה להיות השפעה לרעה על ביצועי התוכנית שלכם. לכן צריך לחשוב פעמיים לפני שהולכים ומפעילים את הבדיקות אוטומטית על כל הפרוייקט. לכן אפשר גם לשקול לאפשר את הבדיקות המלאות רק בתצורת ה-Debug של התוכנית, אבל לא ב-Release. אבל במצב כזה שוב פעם חזרנו לבעיה ש-Overflows יכולים להתקיים בלי שנדע מזה בצורה ברורה.
כאן נכנסת לתמונה מילת המפתח checked. אנחנו יכולים לתחום בעזרתה קטעי קוד "שמועדים לפורענות". מקומות שאנחנו מזהים ויודעים להגיד ששם קיימת סבירות .. סבירה, שיגרם Overflow. בצורה הזאת אנחנו נמנעים מלעשות בדיקות על מקומות חסרי טעם שסתם יבזבזו Cycle'ים של המעבד.
הנה מה שקורה לאותו הקוד ממקודם, לאחר שתחמנו את הפעולה הלא חוקית ב-checked:


רק לרקורד, קיימת גם מילת המפתח unchecked שעושה בדיוק ההפך. במידה והגדרתם שבדיקות Overflow יבוצעו אוטומטית עבור הפרוייקט, תוכלו לתחום אזורי קוד בהם אתם לא רוצים שהבדיקה תבוצע (יכול להיות שימוש למשל אם דורסים את GetHashCode ולא רוצים שהחישוב יעיף שגיאה).

נקודות נוספות
  • בדיקות Overflow מבוצעת אך ורק למספרים אינטגרלים. כלומר, אם למשל תנסו להכניס ערך גדול מדי ל-int - תקבלו שגיאה. אבל, אם תנסו להכניס ערך גדול מדי ל-double, לא תקבלו שום שגיאה, אלא רק ערכים שגויים.
  • לכל הבדיקות האלה קיימת השלכה מסויימת על הביצועים. כדאי לחשוב היטב איפה ומתי אנחנו באמת רוצים לעשות את הבדיקה הזאת, אם בכלל.
  • הקומפיילר ידע להזהיר אתכם על Overflows במידה והם ממש Hardcoded ו-"נראים לעין". זאת הסיבה שבדוגמה שנתנתי הייתי צריך להקצות את foo שיחזיק את הערך "1". במידה וממש הייתי רושם בקוד int32.MaxValue + 1 הקומפיילר היה מוציא שגיאת קומפילציה.
  • במידה ואתם כותבים קוד שאתם יודעים מראש שיכול לגרום ל-Overflow במידה והוא מקבל כקלט ערכים "גדולים מדי", אז אפשרות נוספות היא לעשות קצת Defensive Programming ולבדוק את חוקיות הקלט שמגיע אליכם. במידה והוא לא חוקי, תזרקו כבר שגיאה משלכם שתתריעו על הבעיה.

תוכן התגובה

spiritus asper כתב/ה:

אתה מתכוון ל-Compile Time Error? לא, הסיבה היחידה שהשתמשתי ב-Console.WriteLine היא בשביל.. להדפיס את הערך

אין לה חשיבות מעבר לכך (מה גם שלפחות ככה הוא לא צועק על זה שלא השתמשתי ב-bar..)

# June 15, 2009 7:42 PM
שלח תגובה

(שדה חובה)  

(שדה חובה)  

(אופציונלי)

(שדה חובה) 

Please add 1 and 2 and type the answer here:


Enter the numbers above: