DCSIMG
October 2009 - Posts - Liran Chen's Blog

Liran Chen's Blog

.Net Internals, Debugging, Multithreading - and More!

October 2009 - Posts

A Small But Helpful Tip

כמה פעמים כבר יצא לכם שבזמן שאתם כותבים קוד, פתאום קופץ לכם על המסך הקומבו-בוקס המוכר של ה-Intellisense, או אולי איזה קטע מהתיעוד ה-XML'י של הפונקציה שאתם מנסים לקרוא לה. ואותו חלון בדיוק נופל על קטע קוד שאתם מנסים לקרוא באותו הרגע. כך שבעצם קורה שאי אפשר לעבוד גם עם ה-Intellisense וגם לעיין בקוד שמעניין אותנו ונמצא בהמשך השורה, או אולי כמה שורות מתחתינו (תלוי עד כמה גדול חלון ה-Intellisense שלכם, שדרך אגב, אפשר לשנות אותו עם גרירה של העכבר כמו כל חלון רגיל). בדרך כלל אנחנו פשוט נאלצים להקיש על Esc, לסגור את החלון הקטן שנפתח, לקרוא את הקוד שעניין אותנו, להפעיל איזשהו טריגר שיגרום לאותו חלון לחזור להופיע, ואז לחזור בחזרה להקלדה.
ובכן, לא עוד. מסתבר שניתן להפוך את כל החלונות הקטנים האלה שקופצים (תיעוד/Intellisense וכו'..) לשקופים למחצה על ידי לחיצה על מקש ה-Ctrl. ברגע שנפסיק ללחוץ עליו, אותו חלון יחזור למצב התצוגה הרגיל שלו. כך שאנחנו יכולים לקרוא את הקוד המוסתר ולחזור להקלדה הרציפה במאמץ מינימלי.

לפני:


ואחרי:

Headaches with Prefix and Temporary Variables

לפני לא פחות מ-5 שנים, Luca Bolognese כתב פוסט שעסק בשאלה שעלתה ב-C# User Group. השאלה היתה מה יהיה הערך של x, בסוף ביצוע קטע הקוד הבא:

int x = 3;

x += x++;


אם אנחנו זוכרים את ההבדל בין Postfix ו-Prefix, אז לא צריכה להיות יותר מדי בעיה להבין שהתוצאה תהיה בסוף 6 מאחר ואין משמעות ל++ האחרון. כך שלמעשה, ניתן לפשט את הביטוי הזה ל: x = x + x, ועדיין נקבל את אותה ההתנהגות (שימו לב שזה המקרה ב-#C. ב-CPP למשל, אין הגדרה אמיתית לגבי מה הביטוי הזה צריך להחזיר).
אם כך, זאת נקודת הפתיחה שלנו. הרשתי לעצמי לקחת צעד אחד קדימה ולכתוב את שורת הקוד המאוד קריאה וברורה הזאת:

int x = 10;

x = --x + x + --x;


טוב, אז זה השלב שבו הדברים מתחילים להיות קצת יותר מעניינים.
מה לפי דעתכם יהיה הערך של x בסוף השורה המופלאה הזאת? אני מציע לקחת דקה של התבוננות עצמית ומחשבה, סך הכל הביטוי הזה יכול לעורר לא מעט בלבלול.
מוכנים עם התשובה? ובכן, בסופו של דבר הערך של x יהיה 26. למה? הרמז נמצא בכותרת של הפוסט.

מה שמבלבל בביטוי הזה, הוא שאנחנו כל הזמן צריכים לעקוב היכן בזכרון נשמרים הערכים במהלך החישוב. האם הנתון נמצא במשתנה המקורי? האם ברג'יסטר של המעבד? או אולי בכלל במקום אחר? מה שחשוב לשים לב אליו כאן הוא שבמהלך החישוב אנחנו למעשה מקצים int נוסף שישב על ה-stack, וישמור את "תוצאת הביניים" של החישוב.
בצעד הראשון, אנחנו מורידים ב-1 את ערכו של x, ומעדכנים את המשתנה בזכרון, כלומר ברגע זה x=9. לאחר מכן, אנחנו מחברים את x ב-x. את התוצאה של החישוב הזה אנחנו למעשה נשמור במשתנה זמני נוסף שיוקצה במיוחד למטרה הזאת על ה-stack. שימו לב לא להתבלבל, אנחנו לא מעדכנים את ערכו של x במקרה הזה. לאחר מכן, אנחנו מורידים שוב ב-1 את ערכו של x (עכשיו ל-8), ואז מחברים אותו למשתנה הזמני ממקודם (שערכו 18). את התוצאה של החישוב האחרון הזה, נשמור בתוך x. כך קיבלנו בסופו של דבר את התוצאה 26.

אפשר לראות את התהליך הזה בבירור ברגע שאנחנו בודקים את קוד האסמבלר שנוצר לנו בזמן הריצה:

00000019  mov  dword ptr [ebp-4],0Ah   // x = 10

00000020  dec  dword ptr [ebp-4]       // x = 9

00000023  mov  eax, dword ptr [ebp-4]  // x = 9, eax = 9

00000026  add  eax, dword ptr [ebp-4]  // x = 9, eax = 18

00000029  mov  dword ptr [ebp-8],eax   // x = 9, eax = 18, temp = 18

0000002c  dec  dword ptr [ebp-4]       // x = 8, eax = 18, temp = 18

0000002f  mov  eax, dword ptr [ebp-8]  // x = 8, eax = 18, temp = 18

00000032  add  dword ptr [ebp-4],eax   // x = 26