מצביע האפס

ערך עבור מצביע שאינו מתייחס לאובייקט חוקי

במחשוב, מצביע האפסאנגלית: Null pointer) הוא ערך שמור שנועד לציין הצבעה לאובייקט בלתי חוקי. תוכניות מחשב משתמשות באופן שגרתי במצביע אפס כדי לייצג תנאים מסוימים כגון סוף רשימה או כישלון בפעולה כלשהי האמורה להציב ערך למצביע כלשהו. ניתן להשוות את מצביע האפס לערך "Nothing" או "None" המופיע במספר שפות תכנות.

מצביע אפס אינו מצביע שאינו מאותחל. שפות תכנות מסוימות מאפשרות שימוש במצביע שאינו מאותחל, דבר העלול לגרום להתנהגות בלתי צפויה (אנ')).עבור מצביע אפס, מובטח שהוא שונה מכל מצביע אשר מצביע על אובייקט חוקי או על מקום תקף בזיכרון ולכן שימוש בו יוביל בשפות תכנות רבות בהכרח לשגיאה.

בשפת C

עריכה

ב-C, מובטח ששני מצביעי אפס מכל סוג יהיו שווים.[1] המאקרו NULL מוגדר כקבוע,[2] וב-C99 ניתן לבטאו כ-(0(void *)) – כלומר המרה של הערך 0 לסוג void* (מצביע לסוג void).[3] תקן C אינו קובע שמצביע האפס אמור להיות זהה לכתובת המצביע לכתובת 0, אם כי זה עשוי להיות המקרה בפועל. התייחסות (אנ') (כלומר, ניסיון לקרוא את תוכן הזיכרון בערך המצביע) למצביע אפס היא התנהגות לא מוגדרת ב-C.[4]

בפועל, התייחסות למצביע null עלולה לגרום לניסיון קריאה או כתיבה מזיכרון שאינו ממופה, ולעורר שגיאת גישה לזיכרון, דבר העשוי לגרום לקריסת תוכנית, או להפוך לחריגת תוכנה הניתנת לטיפול על ידי קוד התוכנית. עם זאת, ישנן נסיבות מסוימות שבהן לא תיווצר בהכרח שגיאה. כך לדוגמה, ב- x86, הכתובת 0000:0000 ניתנת לעיתים לקריאה או לכתיבה, והפניית מצביע לכתובת זו היא פעולה חוקית לחלוטין אך בדרך כלל לא רצויה שעלולה להוביל להתנהגות לא מוגדרת (אך לא קריסה) באפליקציה. ישנם מקרים שבהם הפניית המצביע לכתובת אפס היא מכוונת ומוגדרת היטב; לדוגמה, קוד BIOS שנכתב ב-C עבור התקני x86 של 16 סיביות עשוי לכתוב את טבלת הפסיקות בכתובת פיזית 0 של המכונה על ידי הפניה של מצביע null לכתיבה. במימוש מהדר ניתן לבצע אופטימיזציה המניחה שאין בקוד התייחסות למצביע null. מימוש זה יייתן ביצועים טובים יותר למהדר בקוד תקין, אך עשוי לגרום להתנהגות בלתי צפויה במידה והקוד מכיל בטעות התייחסויות למצביע האפס.

ב-++C, בעוד NULL עבר בירושה מ-C, המספר השלם של אפס הועדף באופן מסורתי כדי לייצג קבוע מצביע אפס.[5] עם זאת, בגרסת C++11 הוסף הקבוע המפורש nullptr לשימוש ייעודי כמצביע האפס.

שפות אחרות

עריכה

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

שפות תכנות שונות משתמשות בטרמינולוגיה שונה עבור מצביע האפס. ב-Python, למשל, הערך קרוי None. בפסקל ובסוויפט, מצביע אפס נקרא nil. באייפל, void.

התייחסות למצביע האפס

עריכה

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

  • ב-C, התייחסות למצביע אפס היא התנהגות לא מוגדרת.[4] יישומים רבים גורמים לקוד כזה לגרום לעצירת התוכנית, מכיוון שייצוג המצביע null נבחר להיות כתובת שלעולם לא מוקצית על ידי המערכת לאחסון אובייקטים. עם זאת, התנהגות זו אינה אוניברסלית וגם אינה מובטחת, שכן מהדרים רשאים לבצע אופטימיזציה של תוכניות בהנחה שהם נקיים מהתנהגות לא מוגדרת.
  • ב-Delphi ובמימושים אחרים של Pascal, הקבוע nil מייצג מצביע לכתובת הראשונה בזיכרון המשמשת גם לאתחול משתנים. התייחסות אליו תגרום לחריגת מערכת הפעלה.
  • ב-Java, התייחסות ל- null מפעילה NullPointerException (NPE), אשר ניתנת לטיפול על ידי קוד טיפול בשגיאות, אך הנוהג המועדף הוא לנסות להבטיח שחריגים כאלה לעולם לא יתרחשו.
  • ב-.NET, גישה ל- null גורמת ל-NullReferenceException. טיפול בה אינו מומלץ אך אפשרי.
  • ב-Objective-C, הודעות עשויות להישלח לאובייקט nil (שהוא מצביע null) מבלי לגרום להפרעה לתוכנית. התכנית מתעלמת מההודעה, וערך ההחזרה (אם ישנו) הוא nil או 0, תלוי בסוג.[6]
  • לפני ההשקה של SMAP, ניתן היה לנצל באג של הפניית מצביע null על ידי מיפוי pagezero לתוך מרחב הכתובות של התוקף, ומכאן לגרום למצביע null להצביע לאזור זה, מה שעלול להוביל לביצוע קוד זדוני במקרים מסוימים.[7]

טיפול בבאגים

עריכה

ישנן טכניקות כדי להקל על ניפוי באגים הנגרמים מהתייחסות למצביע אפס.[8][9] הוצע[8] לשנות את JVM כדי לעקוב אחר התפשטות של ערך אפס. הרעיון של מערכת Casper[9] הוא להשתמש בטרנספורמציה של קוד מקור על מנת לעקוב אחר התפשטות זו, מבלי לשנות את ה-JVM. במקרים מסוימים, ניתן ליצור אוטומטית תיקון כדי לתקן חריגות של מצביע האפס.[10]

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

כאשר שפה אכן מספקת אפשרות לשימוש במצביעים, ייתכן שניתן יהיה להפחית או להימנע מהפניות מצביע אפס בזמן ריצה על ידי בדיקה בזמן הידור, באמצעות ניתוח סטטי או טכניקות אחרות. ניתן לראות גישה זו בגרסאות מודרניות של שפת התכנות אייפל, [11] D,[12] ו-Rust . [13]

ניתן לבצע ניתוח דומה באמצעות כלים חיצוניים.

היסטוריה

עריכה

בשנת 2009, סר טוני הואר הצהיר[14] שהוא המציא את מצביע האפס בשנת 1965 כחלק משפת ALGOL W. בהתייחסות זו משנת 2009 מתאר הואר את המצאתו כ"טעות של מיליארדי דולרים":

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

ראו גם

עריכה

הערות שוליים

עריכה
  1. ^ ISO/IEC 9899, clause 6.3.2.3, paragraph 4.
  2. ^ ISO/IEC 9899, clause 7.17, paragraph 3: NULL... which expands to an implementation-defined null pointer constant...
  3. ^ ISO/IEC 9899, clause 6.3.2.3, paragraph 3.
  4. ^ 1 2 ISO/IEC 9899, clause 6.5.3.2, paragraph 4, esp. footnote 87.
  5. ^ Stroustrup, Bjarne (במרץ 2001). "Chapter 5:
    The const qualifier (§5.4) prevents accidental redefinition of NULL and ensures that NULL can be used where a constant is required.". The C++ Programming Language (14th printing of 3rd ed.). United States and Canada: Addison–Wesley. p. 88. ISBN 0-201-88954-4.
    {{cite book}}: (עזרה)
  6. ^ The Objective-C 2.0 Programming Language, section "Sending Messages to nil".
  7. ^ OS X exploitable kernel NULL pointer dereference in AppleGraphicsDeviceControl
  8. ^ 1 2 Bond, Michael D.; Nethercote, Nicholas; Kent, Stephen W.; Guyer, Samuel Z.; McKinley, Kathryn S. (2007). "Tracking bad apples". Proceedings of the 22nd annual ACM SIGPLAN conference on Object oriented programming systems and applications - OOPSLA '07. p. 405. doi:10.1145/1297027.1297057. ISBN 9781595937865.
  9. ^ 1 2 Cornu, Benoit; Barr, Earl T.; Seinturier, Lionel; Monperrus, Martin (2016). "Casper: Automatic tracking of null dereferences to inception with causality traces". Journal of Systems and Software. 122: 52–62. arXiv:1502.02004. doi:10.1016/j.jss.2016.08.062. ISSN 0164-1212.
  10. ^ Durieux, Thomas; Cornu, Benoit; Seinturier, Lionel; Monperrus, Martin (2017). "Dynamic patch generation for null pointer exceptions using metaprogramming" (PDF). 2017 IEEE 24th International Conference on Software Analysis, Evolution and Reengineering (SANER). IEEE: 349–358. arXiv:1812.00409. doi:10.1109/SANER.2017.7884635. ISBN 978-1-5090-5501-2.
  11. ^ "Void-safety: Background, definition, and tools". נבדק ב-2021-11-24.
  12. ^ Bartosz Milewski. "SafeD – D Programming Language". נבדק ב-17 ביולי 2014. {{cite web}}: (עזרה)
  13. ^ "Fearless Security: Memory Safety". ארכיון מ-8 בנובמבר 2020. נבדק ב-4 בנובמבר 2020. {{cite web}}: (עזרה)
  14. ^ Tony Hoare (2009-08-25). "Null References: The Billion Dollar Mistake". InfoQ.com.