Download presentation
Presentation is loading. Please wait.
1
ניפוי שגיאות - Debugging
מבוא לתכנות מערכות מבוא לתכנות מערכות
2
סוגי שגיאות שגיאות קומפילציה שגיאות זמן ריצה שגיאות זיכרון
משתנים לא מאותחלים נזילות זיכרון לולאות אינסופיות התנהגות לא נכונה מבוא לתכנות מערכות
3
שגיאות קומפילציה שגיאות שהקומפיילר מסוגל לזהות:
בד"כ הקוד אינו מציית לכללי התחביר של השפה. אי התאמה בין טיפוסים. קומפיילר טוב גם יפלוט אזהרות בקשר לשורות אשר חוקיות בשפה אך מצביעות על באגים. מבוא לתכנות מערכות
4
שגיאות קומפילציה Gender stringToGender(const char* string) {
if (strcmp(“Male", string) = 0) { return MALE; } if (strcmp(“Female", string) == 0) { return FEMALE; assert(false); השגיאה כאן היא שימוש באופרטור ההשמה "=" במקום באופרטור ההשוואה "==". המונח lvalue מתאר בשפות תכנות ערך. ערך שהינו lvalue הוא ערך אשר יכול להיות בצידו השמאלי של אופרטור ההשמה "=". (ומכן שמו, lvalue עבור left value) במקרה שלנו הביטוי strcmp("Regular", string) הוא ערך זמני ואינו משתנה ולכן לא ניתן לבצע אליו השמה. במקרה זה קל לנחש שכוונת המשורר הייתה להשוואת ערך החזרה של הפונקציה strcmp עם 0. gcc -O0 -g3 -Wall -c -fmessage-length=0 -std=c99 –o person.o person.c ..\person.c: In function `stringToGender': ..\person.c: error: invalid lvalue in assignment מבוא לתכנות מערכות
5
שגיאות קומפילציה הקומפיילר מספק תיאור לכל שגיאת קומפילציה
לעתים ההסבר אינו מספק ניתן לחפש הסברים לשגיאות באינטרנט לצורך כך חפשו בגוגל. חפשו רק לפי מילים בתיאור הבעיה שאינן ייחודיות לתכנית. ירוק = טוב מבוא לתכנות מערכות
6
אזהרות קומפילציה int* allocateArray(int size) { if (size = 0) {
exit(1); } int* array = malloc(size * sizeof(int); return array; זו דוגמה לאזהרה שבד"כ מצביעה על באג. המתכנת במקרה זה התבלבל ורשם = במקום == כמו מקודם, אבל במקרה זה הקוד תקין ומתקמפל. אז למה זהו באג? כי המתכנת רצה לבדוק את מספר הפרמטרים שנקראו ולהחזיר NULL במקרה המתאים. ואילו הפונקציה הנוכחית *תמיד* תחזיר NULL. ערך הביטוי n=4 הוא ערכו החדש של n. ערך זה הוא 4 כמובן וזהו ערך אמת. לכן הפונקציה תמיד תחזיר NULL. משמעות אזהרת הקומפיילר במקרה זה היא לבקש מהמתכנת לשים סוגריים נוספים מעל השמות בתוך משפט if כדי למנוע בלבול ולציין שכוונה כאן היא אכן להשמה. שימו לב שקיימים מקרים בהם ההשמה הזו הגיונית, למשל: if ((n = fscanf(input, "[%s %s %s %lf]\n", typename, firstname, lastname, &balance) != 4) { … } חשוב לציין שמומלץ להימנע מהשמות בתוך משפטי if ולולאות while. השמות אלו יוצרות קוד מסובך ומבלבל ועלולות לגרום להכנסת באגים בקלות יתר. gcc -O0 -g3 -Wall -c -fmessage-length=0 -std=c99 –o person.o ..\person.c ..\person.c: In function `allocateArray': ..\person.c:76: warning: suggest parentheses around assignment used as truth value מבוא לתכנות מערכות
7
שגיאות זמן ריצה מתרחשות רק בזמן ריצת הקוד.
ייתכן והופעתן תלויה בקלט. שגיאות זיכרון: גישה לזיכרון בצורה לא חוקית. אפשרות א' - שגיאת Segmentation Fault. אפשרות ב' - התכנית תתנהג בצורה לא צפויה. דליפות זיכרון: התכנית תתנהג כצפוי אך בפעולה ממושכת תאט את כל המערכת עקב גדילת צריכת הזיכרון. שגיאות לוגיות: התנהגות לא נכונה של הקוד. בד"כ טעות בהבנת הבעיה ע"י המתכנת או טעות בשימוש בקוד קיים. באגים מצורת Segmentation Fault נוחים למפתח, למרות התגובה הפתאומית של התוכנה מאחר והם קלים לאיתור ולהבנה (יחסית). התנהגות לא מוגדרת היא הרבה יותר בעייתית, בעיקר מהאחר והיא מופיעה רק אחרי השגיאה שגרמה לה, ולפעמים לא בסמיכות. דבר זה מקשה מאוד על בידוד השגיאה ומציאת הפונקציה או אפילו המודול הגורמים לה. מבוא לתכנות מערכות
8
שגיאת זיכרון char* duplicateString(char* string) {
char* result = malloc(strlen(string)); if (!result) { return NULL; } return strcpy(result, string); השגיאה כאן היא הקצאה לא נכונה של זיכרון. במקרה זה מוקצה זיכרון לאחסון מחרוזת. אורך המחרוזת הוא strlen(string), אבל על מנת לאחסן אותה יש צורך לשמור גם את תו ה-‘\0’ בזיכרון. ולכן יש צורך ב-strlen(string)+1 בתים. מאחר ולא מוקצה זיכרון מספק הפונקציה strcpy רושמת את תו ה-‘\0’ לתוך זכרון שאינו מוקצה למחרוזת התוצאה. שגיאה זו לא תתגלה ישירות, אך לאחר שימוש ממושך יירשם התו ‘\0’ על זיכרון אחר המוקצה לתכנית ויגרום להתנהגות לא צפויה שגיאות דומות מתרחשות כאשר מקצים גודל לא נכון בגלל התבלבלות בטיפוסים, למשל: malloc(sizeof(string)+1) במקרה זה האופרטור sizeof מתייחס לטיפוס אותו הוא מקבל ולכן הערך של sizeof(string) הוא בעצם sizeof(char*) וערך זה הוא בד"כ 4 (תלוי במכונה). כלומר לא יוקצה מספיק מקום למחרוזת ארוה מ-4 תווים. char* result = malloc(strlen(string)); char* result = malloc(strlen(string) + 1); מבוא לתכנות מערכות
9
קצת טיפים המנעו משורות המצהירות על משתנה ללא אתחול
אנו עלולים להכניס קוד בין השורות, ולהשתמש במשתנה לא מאותחל אם אינכם יודעים כיצד לאתחל את המשתנה, אל תכריזו עליו! int n; int n; n = 5; מבוא לתכנות מערכות
10
קצת טיפים המנעו מגושי הכרזה בתחילת הקוד
העדיפו ערך ברירת מחדל על פני ערך זבל מבטיח התנהגות עקבית של הקוד הקפידו במיוחד לאתחל מצביעים ל-NULL! כך התכנית תקרוס עם השגיאה הראשונה int x; scanf(“%d”, &x); int x = 0; scanf(“%d”, &x); מבוא לתכנות מערכות
11
קריאה מזיכרון לא חוקי Person* changePerson(Person* person) {
if (person->name == NULL) { return NULL; } return doSomething(person->name); if (person->name == NULL) { ... שגיאה קלאסית שקורית הרבה במיוחד בהתחלה היא התעלמות מערכי NULL בכתיבת פונקציה. למזלנו שגיאה זו קלה למציאה מאחר והתכנית פשוט תתרסק בנסיון הראשון לקרוא מצביע שערכו NULL. ההתרסקות תתרחש ברגע קריאת המצביע, באנגלית dereferencing. הקריאה מתבצעת או ע"י אופרטור ה-* או ע"י אופרטור ה- <-. כלומר בדוגמה שלנו התרסקות התכנית תהיה בביטוי database->type. אם אינכם חושבים שהפונקציה צרכיה לטפל במצביע שערכו NULL הוסיפו וידוא בתחלית הפונקציה ע"י שימוש ב-assert: assert(ptr); וידוא זה יעזור לקורא עתידי של הקוד להבין שאסור לשלוח NULL לפונקציה זו. ובמקרה של שליחת NULL התכנית תיעצר עם שגיאה נוחה יותר למתכנת מאשר Segmentation fault. if (person == NULL) { return NULL; } if (person->name == NULL) { ... מבוא לתכנות מערכות
12
כתיבה לזיכרון שלא הוקצה
Person personInitialize(char* name) { Person person; person.spouse = NULL; strcpy(person.name, name); return person; } strcpy(person.name, name); במקרה הזה חשוב לשים לב מה קורה כאשר אנו מקצים זיכרון. הזיכרון המוקצה בפונקציה הוא בגודל של struct account_t. מבנה זה מכיל מספר שדות, אך המחרוזות הנשמרות בו הן לא יותר ממצביעים ולכן הקצאת המקום למבנה זה כוללות מקום אחסון מצביע, לא לאחסון מחרוזת (שאורכה לא ידוע). לכן בשביל לשמור העתק של המחרוזות ב-account החדש יהיה עלינו להקצות מקום להעתקת כל אחת מהמחרוזות ואל הזיכרון המוקצה הזה נצביע מהמבנה שהקצנו בשורה הראשונה. מאחר ופעולת הקצאת מחרוזת והעתקתה היא פעולה פופולרית במיוחד ניצור פונקצית עזר לשכפול מחרוזת אשר עושה בדיוק את זה (הפונקציה הופיעה לפני מספר שקפים בהקשר שגיאות אחר). person.name = duplicateString(name); מבוא לתכנות מערכות
13
דליפת זיכרון Person personInitialize(char* name, char* lastname) {
person.spouse = NULL; person.name = duplicateString(name); if (person.name == NULL) { return person; } person.lastname = duplicateString(lastname); if (person.lastname == NULL) { נזילת זיכרון מתרחשת כאשר שוכחים לשחרר זיכרון מוקצה דינאמית. שגיאות מסוג זה אינן משפיעות בד"כ על תוצאות התכנית, אך תכניות בעלות נזילות זיכרון אשר רצות לאורך זמן יתנפחו בזיכרון יצרכו משאבים רבים ויאטו את עבודת המחשב. התוצאה המורגשת היא תכונה שאחת לכמה זמן יש לאתחל אותה בכדי שתרוץ ביעילות. במקרה זה החזרה השנייה מהפונקציה אינה מתחשבת בזיכרון שכבר הוקצה בהצלחה: הזיכרון עבור result ואולי אף אחד ממחרוזות השם firstname ו-lastname. ולכן במקרים כאלו קיימת נזילת זיכרון. פתרון לא יצירתי המשכפל קוד הוא בדיקת הצלחה לאחר כל הקצאה ושחרור כל המשאבים הקודמים במקרה של כשלון. זהו פתרון רע מאחר ובכל הקצאה נוספת נוספת עוד שורה לשחרורים, הפונקציה מתארכת נעשית לא קריאה והסיכוי לבאגים גדל. שיטה אלגנטית לטיפול בבעיה זו היא הקצאת המשתנה הבסיסי ובדיקתו. לאחר מכן הקצאת כל המשתנים הנוספים ובדיקת משותפת של כולם, במקרה של כשלון נקרא לפונקצית השחרור המתאימה (צריכה להיות לנו אחת כזו לכל מבנה). יותר קל לנו להתאים את פונקצית השחרור לטיפול במבנים בנויים חלקית מהתאמת הפונקציה הזו. if (person.lastname == NULL) { return person; } if (person.lastname == NULL || person.name == NULL ) { personDestroy(person); return NULL; } מבוא לתכנות מערכות
14
פתרון באגים שחזרו את הבאג צמצמו את מרחב הבעיה נסו למצוא את המקור לבעיה
מצאו תרחיש שבו הבאג תמיד קורה רק כך נוכל לוודא שהבאג אכן תוקן צמצמו את מרחב הבעיה כלל אצבע: רוב הבאגים בקורס ניתנים לשחזור ע"י קובץ בדיקה של פחות מחמש שורות באגים במודול יחיד ניתנים לשחזור ע"י קובץ main קצר שגורם להופעת השגיאה נסו למצוא את המקור לבעיה אם לא ניתן למצוא את הפתרון חיזרו לשלב הקודם מבוא לתכנות מערכות
15
מציאת מקור הבאג הדפסות זמניות (printf) דיבאגר
מאפשר לדעת איזה חלק בקוד רץ ומתי מאפשר להדפיס ערכי משתנים בשלבים שונים מאפשר הדפסה בכל פורמט שנוח לנו דיבאגר מאפשר להריץ את התכנית בצורה מבוקרת מאפשר לראות את ערכי המשתנים בשלבים השונים מאפשר גם ביטויים מורכבים יותר ועצירה במקומות שונים מבוא לתכנות מערכות
16
הדפסות זמניות השיטה הבסיסית ביותר לאבחון מצב התכנית
כשמם כן הן – זמניות. יש לזכור למחוק אותן לפני ההגשה אם יש שימוש בבדיקות אוטומטיות קשה לפספס את זה. ניתן לסמן אותן ע"י TODO או FIXME היתרון של הדפסות זמניות הוא השליטה הנוחה על פורמט הפלט. ניתן להדפיס עצמים מסובכים בשורה אחת בצורה קריאה. ניתן להדפיס רשימות שלמות בשורה אחת בצורה קריאה. ניתן להסתכל בקלות על מצב התוכנה לאורך ציר הזמן במבט אחד ולמצוא את נקודת המפנה. כתיבה נכונה של קוד בד"כ תגרום לקיום פונקציות אשר מקלות על הדפסות זמניות נוחות וקריאות. הדפסות אלו יכולות להישאר גם כאשר אין באגים מתוך כוונה לעזור למתכנת לעקוב אחר המתרחש בקוד, למשל ע"י שמירת יומן כלשהו לקובץ. מבוא לתכנות מערכות
17
הדפסות זמניות שיטה זו נוחה במיוחד לשגיאות התנהגות.
הדפסת הקלט והפלט של פונקציות מפתח מאפשרת הדפסת עצמים מורכבים בפורמט נוח מערכים וכדומה... מבוא לתכנות מערכות
18
הדפסות זמניות נניח שבידינו פונקציה שמקבלת Point ומבצעת עליה פעולה
נרצה לבדוק את מצב הנקודה באמצע הפונקציה Point doSomethingWithPoint(Point point) { ... printf(“Point is (%d, %d, %d)\n", point.x, point.y, point.z); // TODO return point; } ניתן כאמור להגדיר תגיות ב-Eclipse אשר קל למצאן אח"כ להסרה. Point is (11, 12, 13) מבוא לתכנות מערכות
19
הדפסות זמניות ניתן לנצל את ה-Preprocessor לשליטה בהדפסות זמניות
נגדיר מאקרו עבור הדפסה זמנית. המאקרו יוגדר פעמיים, פעם אחת כאשר DEBUG_ON מוגדר ופעם נוספת כאשר הוא אינו מוגדר. ההדפסות יתבצעו רק אם נוסיף את השורה: שימו לב: את השורה יש להוסיף לפני שורת ה-#ifdef. כדי להשתמש במאקרו נחליף את ההדפסה מהדוגמה בקריאה למאקרו: #ifdef DEBUG_ON #define DEBUG_PRINTF(...) printf(__VA_ARGS__) #else #define DEBUG_PRINTF(...) #endif המאקרו בשימוש בדוגמה זו עושה שימוש בתכונה של C99 והיא הגדרת מאקרו עם מספר פרמטרים משתנה. כדי להכריז על מאקרו כזה ניתן לפרמטר האחרון אליו את השם "..." וכדי להשתמש בפרמטר זה נשתמש ב-__VA_ARGS__ אשר יוחלף בשימוש במאקרו ברשימת הפרמטרים שנשלחו למאקרו (עם פסיקים ביניהם). השימוש העיקרי במאקרו כזה הוא עטיפת פונקציות ממשפחת printf. הערה: מאקרו המשתמש ב-... ו-__VA_ARGS__ הוא סטנדרטי ב-C99 אך אינו חלק מהסטנדרט ב-C++03, הסטנדרט הנוכחי של שפת ++C. עם זאת ניתן להשתמש בתכונה זו ב-++C ברוב הקומפיילרים (בפרט בכל הפופולריים) והיא צפויה להפוך לרשמית עם יציאת הסטנדרט החדש C++0x ב-2010 או 2011 (או אחרי שבית הסטודנט ייפתח) #define DEBUG_ON DEBUG_PRINTF("Values (%s,%s,%s,%lf)\n", typename, firstname, lastname, balance); מבוא לתכנות מערכות
20
דיבאגר "דיבאגר" היא תוכנה שבעצם מאפשרת גישה נוחה למידע השמור בזיכרון בזמן ריצת התכנית. ראינו שימוש ב-GDB, דיבאגר ללא מנשק גרפי. בשיעור זה נראה שימוש בדיבאגר המוסיף מנשק גרפי מעל GDB. מבוא לתכנות מערכות
21
Eclipse Debugger כדי לדבג ב-Eclipse יש להריץ את התכנית במצב Debug.
ע"י Run, Debug as, Local C/C++ Application. Eclipse שומר קונפיגורציות "דיבוג" בדיוק כמו קונפיגורציות הרצה. ניתן לערוך פרמטרים לקונפיגורציות אלו תחת Run, Debug Configurations. Eclipse יציע לעבור לפרספקטיבת Debug אשר מסדרת את החלונות בצורה נוחה יותר למצב זה. מומלץ להשתמש בסידור החלונות המוצע. כדי לחזור לסידור החלונות הרגיל יש לבחור ב-C/C++ בפינה הימנית ועליונה של סביבת העבודה. מבוא לתכנות מערכות
22
Eclipse Debugger מבוא לתכנות מערכות
23
התקדמות בקוד כברירת מחדל התכנית תיעצר בכניסה לפונקצית main.
כפתור Resume ימשיך בהרצת הקוד עד ה-Breakpoint הבא או עד סיום התכנית. כפתור Step into יתקדם שורה בקוד או ייכנס לפונקציה הנקראת מהשורה הנוכחית. כפתור Step over יתקדם לשורה הבאה. כפתור Step return יתקדם עד ליציאה מהפונקציה הנוכחית. כפתור Suspend יעצור את ריצת הקוד זמנית (למשל עבור לולאה אינסופית). השתמשו ב-Breakpoints במקום. השתמשו בקלט מתוך קובץ בזמן הדיבוג. כברירת מחדל התכנית תיעצר בכניסה לפונקצית main. השורה הנוכחית בקוד (כאשר הוא אינו רץ) מסומנת בירוק. ניתן לראות את מחסנית הקריאות. מבוא לתכנות מערכות
24
Variables חלון ה-Variables במצב ה-Debug מציג את ערכי המשתנים המקומיים בהקשר הנוכחי. לכל משתנה מוצג ערכו. בתחתית החלון מוצג ערך המשתנה הנבחר בצורה נוחה יותר. למשל עבור משתנה מטיפוס char* תוצג המחרוזת המתאימה. מבוא לתכנות מערכות
25
Breakpoints בד"כ הקוד אותו נרצה לבדוק אינו נמצא בתחילת התכנית. לא נרצה להגיע אליו ע"י התקדמות צעד-צעד. Breakpoints, נקודות עצירה, משמשות לעצירת הריצה בשורה מסוימת. כאשר הקוד רץ ומגיע לשורה המסומנת כ-Breakpoint ה"דיבאגר" יעצור את ריצת הקוד. כדי להוסיף Breakpoint יש ללחוץ לחיצה ימנית על השוליים השמאליים של הקוד ולבחור ב-Toggle Breakpoint. ניתן לקבוע תנאי שרק קיומו יגרום לעצירה בנקודה. נקודות העצירה הקיימות יסומנו על שולי הקוד. אדום = רע בנוסף חלון ה-Breakpoints מציג רשימה מסודרת של כל נקודות העצירה. מבוא לתכנות מערכות
26
Watch Expressions ניתן להוסיף ביטויים למעקב ע"י ה"דיבאגר".
ערכי הביטויים יחושבו בכל עצירה של הקוד ויוצגו במסגרת Watches. ניתן להוסיף ביטוי חדש ע"י לחיצה על כפתור ימני בחלון הקוד או חלון ה-Expressions ובחירה ב-Add Watch Expression. הביטויים יחושבו בהקשר הנוכחי. לכן אם הביטוי תלוי במשתנים הוא יחושב רק אם קיימים משתנים בשם זה בהקשר הנוכחי של הקוד. מבוא לתכנות מערכות
27
Valgrind Valgrind היא מעין "דיבאגר" לשגיאות זיכרון בלבד.
למשל עבור התכנית bank: בזמן ההרצה מודפסות גישות לא חוקיות לזיכרון. בסוף ההרצה מודפס סיכום השגיאות הכולל את כל נזילות הזיכרון. Valgrind רצה על לינוקס בלבד. מאחר והיא משתמשת במנשק טקסטואלי בלבד ניתן להריץ אותה בקלות על ה-T2. > valgrind <flags> <command> > valgrind --leak-check=full ./bank database.txt out.txt < test1.in מבוא לתכנות מערכות
28
הרצת Valgrind כאשר אין שגיאות זיכרון או נזילות זיכרון הפלט של valgrind יהיה רק בתחילת וסוף הריצה וייראה כך: החלקים בירוק הם סיכום השגיאות. כך הם אמורים להיראות בתכנית תקינה. > valgrind ./bank database1.in database2.txt < input1.txt ==22755== Memcheck, a memory error detector. ==22755== Copyright (C) , and GNU GPL'd, by Julian Seward et al. ==22755== Using LibVEX rev 1575, a library for dynamic binary translation. ==22755== Copyright (C) , and GNU GPL'd, by OpenWorks LLP. ==22755== Using valgrind-3.1.1, a dynamic binary instrumentation framework. ==22755== Copyright (C) , and GNU GPL'd, by Julian Seward et al. ==22755== For more details, rerun with: -v ==22755== ==22755== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 1) ==22755== malloc/free: in use at exit: 0 bytes in 0 blocks. ==22755== malloc/free: 17 allocs, 17 frees, 1,387 bytes allocated. ==22755== For counts of detected errors, rerun with: -v ==22755== All heap blocks were freed -- no leaks are possible. מבוא לתכנות מערכות
29
שגיאות זיכרון כאשר שגיאת זיכרון מתרחשת יודפסו פרטי השגיאה ומצב מחסנית הקריאות באותו רגע. חשוב לזכור לקמפל את הקוד עם דגל -g בכדי שהמידע המסופק לשגיאה יכיל מספרי שורות בקוד. זיכרו לתקן רק את שגיאת הזיכרון הראשונה, בד"כ שאר השגיאות הן תוצאה ישירה שלה. char* duplicateString(char* string) { assert(string); char* result = malloc(strlen(string)); if (!result) { return NULL; } return strcpy(result, string); ==27007== Invalid write of size 1 ==27007== at 0x490656F: strcpy (mac_replace_strmem.c:269) ==27007== by 0x4009A6: duplicateString (account.c:14) ==27007== by 0x400AD2: AccountCreate (account.c:48) ==27007== by 0x400C7F: AccountRead (account.c:79) ==27007== by 0x400E5C: loadDatabase (main.c:14) ==27007== by 0x401303: main (main.c:117) מבוא לתכנות מערכות
30
נזילות זיכרון אם קיימות נזילות זיכרון Valgrind ירשום זאת בסיום הריצה.
יש מספר אפיונים של שגיאות זיכרון. אף אחד מהן לא אמור להתרחש. קוד טוב עובר את Valgrind ללא שגיאות כלל. בשביל לקבל את פירוט השגיאות יש להריץ מחדש עם הדגל --leak-check=full או שאפשר מראש להריץ עם דגל זה. ==27054== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 1) ==27054== malloc/free: in use at exit: 251 bytes in 15 blocks. ==27054== malloc/free: 17 allocs, 2 frees, 1,387 bytes allocated. ==27054== For counts of detected errors, rerun with: -v ==27054== searching for pointers to 15 not-freed blocks. ==27054== checked 100,568 bytes. ==27054== ==27054== LEAK SUMMARY: ==27054== definitely lost: 251 bytes in 15 blocks. ==27054== possibly lost: 0 bytes in 0 blocks. ==27054== still reachable: 0 bytes in 0 blocks. ==27054== suppressed: 0 bytes in 0 blocks. ==27054== Use --leak-check=full to see details of leaked memory. מבוא לתכנות מערכות
31
פירוט נזילות זיכרון הרצה עם הדגל --leak-check=full תגרום להדפסת מידע לכל נזילה בדומה לשגיאות. המידע יודפס רק בסוף הריצה. לכל נזילה יינתן מספר הפעמים שקרתה ומצב מחסנית הקריאות ברגע הקצאת הזיכרון. > valgrind --leak-check=full ./bank database1.in database2.txt < input1.txt … ==29117== 251 (40 direct, 211 indirect) bytes in 1 blocks are definitely lost in loss record 3 of 3 ==29117== at 0x4904A06: malloc (vg_replace_malloc.c:149) ==29117== by 0x400AB1: AccountCreate (account.c:44) ==29117== by 0x401175: parseCommands (main.c:81) ==29117== by 0x401301: main (main.c:117) ==29117== ==29117== LEAK SUMMARY: ==29117== definitely lost: 40 bytes in 1 blocks. ==29117== indirectly lost: 211 bytes in 14 blocks. ==29117== possibly lost: 0 bytes in 0 blocks. ==29117== still reachable: 0 bytes in 0 blocks. ==29117== suppressed: 0 bytes in 0 blocks. ==29117== Reachable blocks (those to which a pointer was found) are not shown. ==29117== To see them, rerun with: --show-reachable=yes מבוא לתכנות מערכות
32
מציאת שגיאות עכשיו כשאנחנו מכירים סוגי שגיאות בסיסיים ושימוש בכלים שונים, נתאים את שיטות השימוש לשגיאות השונות. שגיאות קומפילציה: אינן דורשות הליך "דיבוג" כלל. במקרה הרע דורשות חיפוש באינטרנט. מבוא לתכנות מערכות
33
מציאת שגיאות שגיאות זיכרון: Segmentation Fault:
ע"י הרצה ב"דיבאגר" של התכנית נדע בדיוק את השורה בה מתרחשת השגיאה, המצביע המתאים ומחסנית הקריאות. אם זה אינו מספיק ניתן להריץ את התכנית עם Breakpoint המתרחש קרוב לשגיאה אך לפניה ולעקוב אחרי ערכי המצביע ולמצוא את הנקודה בה הוא מתאפס. קריאה/כתיבה לזיכרון אסור: שגיאות אלו ניתנות למציאה ביעילות ע"י Valgrind המספקת דו"ח מפורט לשגיאות אלו. מבוא לתכנות מערכות
34
מציאת שגיאות שגיאות זיכרון: נזילת זיכרון: שימוש במשתנים לא מאותחלים:
לא צריכים להיות כאלו. הרצה ב-Valgrind תדפיס שגיאות עבור קפיצות (למשל if) התלויות בזיכרון לא מאותחל. נזילת זיכרון: ע"י הרצה ב-Valgrind. קשה למצוא שגיאות אלו באמצעים אחרים. מבוא לתכנות מערכות
35
מציאת שגיאות לולאות אינסופיות: התנהגות לא נכונה:
ע"י הרצה מתוך "דיבאגר" ועצירה זמנית של הקוד כאשר הוא בלולאה. כפתור ה-Suspend ב-Eclipse ( ). Ctrl+z ב-GDB. המשך הרצה מבוקרת יאפשר מציאת הסיבה מדוע תנאי הלולאה ממשיך להתקיים בלי סוף. (רמז: אולי בטעות יצרתם רשימה מקושרת מעגלית?) התנהגות לא נכונה: ע"י חשיבה: הסתכלות בקוד המתאים וחשיבה איפה יכולה להיות הטעות שתגרום תכנית להתנהג דווקא כך. (אפשרי במקרים פשוטים) ע"י דיבאגר: שימוש ב-Breakpoint כדי להגיע לאזור בה התכנית עדיין מתנהגת נכון והתקדמות צעד צעד. ע"י הדפסות זמניות: הדפסת יומן של פעולות וערכי משתנים והסתכלות על הפלט. מה היתרון של הדפסות זמניות על שימוש בדיבאגר? שליטה בצורת הפלט. ניתן ע"י כתיבת פונקציה עזר ליצור פלט נוח וקריא יותר מאשר התסכלות על ערכי המצביעים בדיבאגר. למשל חישבו על פונקציה המדפיסה רשימה מקושרת של חשבונות בנק כך: LIST: [Regular Dani Din ] [Premium Dana Levi ] [Business Danny Cohen ] END ניתן בקלות להדפיס את מאגר הנתונים של התכנית שלנו לאחר ביצוע כל פעולה. ולאחר הרצה של התכנית לראות את בבת אחת בצורה נוחה את מצב מאגר הנתונים בכל שלה של התכנית. שימוש בדיבאגר לא מאפשר בצורה נוחה לראות את כל "ציר הזמן", והרשימה המקושרת תהיה אוסף של מצביעים ולא נוחה לניווט (בכל צומת יש מלבד מצביע לצומת הבא גם מצביע לכל מחרוזת של הצומת וקשה לקורא צומת כאשר הוא מחולק לכמה משתנים במקום להיות מוצג במחרוזת נוחה לעיניים אנושיות). מה החסרונות של הדפסות זמניות? - צריך להוסיף קוד למטרות בדיקה ולהסיר אותו (ולזכור את זה) מבוא לתכנות מערכות
36
סיכום כדי לתקן באג עיברו על השלבים הבאים: שחזרו את הבעיה.
בודדו את הבעיה, ע"י קובץ בדיקה או פונקצית main קצרים. נסו לתקנה. השתמשו בהדפסות זמניות או "דיבאגר" כדי לזהות את הגורם להתנהגות הלא תקינה. נסו לשאול חברים. במקרה הכי גרוע: שאלו מתרגלים. אבל רק אחרי שהצלחתם לשחזר את הבעיה ולבודד דוגמה קצרה הגורמת לה. (למשל פונקצית main של כ-5 שורות) צוות הקורס לא ידבג את התכנית למענכם. מבוא לתכנות מערכות
37
בהצלחה! מבוא לתכנות מערכות
Similar presentations
© 2025 SlidePlayer.com Inc.
All rights reserved.