1 / 63

תרגול 4

תרגול 4. פיתוח מודולרי ב- C makefile ADT-Abstract Data Type דוגמא פשוטה ל ADT- (תאריך (. לי-טל משיח litalma@cs.technion.ac.il נערך והורחב ע''י ודים אייזנברג. הבעיה: קשה לפתח ולתחזק תוכנה גדולה. תוכנה גדולה יכולה להגיע למיליוני שורות קוד למוח האנושי קשה לתפוס כמות כזאת של מידע

buffy-berg
Download Presentation

תרגול 4

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. תרגול 4 פיתוח מודולרי ב-C makefile ADT-Abstract Data Type דוגמא פשוטה לADT- (תאריך( לי-טל משיח litalma@cs.technion.ac.il נערך והורחב ע''י ודים אייזנברג

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

  3. שלבים בחיים של תוכנה • פיתוח • תחזוקה • תוכנה יכולה "לחיות" הרבה שנים אחרי שהגרסה הראשונה פותחה • הוצאותעל תחזוקהמהוות עד 80% מכל ההוצאות על התוכנה • התוכנה משתנהכל הזמן • תיקוני באגים • הוספת אפשרויות/סביבות עבודה נוספות • שינוייםבחוקים/כללים עסקיים/נהלים שמצריכים שינויים בתוכנה

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

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

  6. הפרדה לממשק ומימוש • כל מודול מורכב משני חלקים: • ממשק(interface) • מימוש(implementation) • ממשק מהווה מעין "חוברת הוראות שימוש" או "לוח כניסות" של המודול • מסתירכל פרטי המימוש של המודול הלא נחוצים לעבודה עם המודול

  7. פיתוח תוכנה בלי מודולים

  8. חלוקה למודולים

  9. חלוקה למודולים והסתרת המימוש • הסתרת פרטים שלא נחוצים לעבודה עם המודול ועלולים לבלבל את המשתמשים

  10. חלוקה למודולים והסתרת המימוש • כשרוצים להבין את התמונה הגדולה – מתעלמים מהמימוש של המודולים • כשרוצים להבין מימוש של מודול כלשהו – נכנסים פנימה ומבינים איך המודול מומש

  11. שינוים במימוש של מודול כלשהו • שינויים במודול כלשהו לא משפיעים על שאר המודולים

  12. כל מודול יכול גם להתחלק למודולים

  13. שימוש חוזר במודולים בתוכנה אחרת תוכנה ב תוכנה א

  14. יתרונות נוספים של תכנות מודולארי • כל מודול יכול להיכתבולהיבדקבנפרד • על ידי אנשיםאו צוותים שונים • בזמנים שונים • במקומות שונים

  15. תכנות מודולארי ב-C : הממשק • הממשק נכתב בקובץ header(.h) • הקובץ מכיל • ההצהרותעל הפונקציותאותן מממש המודול • הגדרותשל קבועים והגדרות של טיפוסים אשר המשתמש במודול זה צריך להכיר • למשל ערכי שגיאה אפשריים של הפונקציות • הפונקציות המוצהרות ב header הינן השירותים אותם מספק המודול לשאר התוכנה

  16. תכנות מודולארי ב-C : המימוש • המימושנכתב בקובץ .c • הקובץ מכיל • מימוש הפונקציות אשר הוצהרו ב-header • פונקציות פנימיות למודול אשר לא נועדו לשימוש מחוץ למודול • משתנים סטטיים של הקובץ

  17. דוגמא: מודול תאריך • בתוכנית שנכתוב מתבצעות פעולותהקשורות לתאריכים • נממש את הפעולות במודול נפרד בשם date

  18. קובץ ה-date.h :header #include <stdio.h> /* date module This module defines a date type and implements some date manipulation functions. */ typedefstructDate_t { int day; char month[4]; int year; } Date; typedefenum{DateSuccess,DateFail,DateFailRead, DateBadArgument} DateResult;

  19. המשך קובץ ה-date.h :header /* reads a date from an open file – ‘inputFile’ expects the ‘date’ to point to an allocated Date */ DateResultdateRead (FILE* inputFile, Date* date); /* writes the date to the output file ‘outputFile’ */ DateResultdateWrite (FILE* outputFile, Date* date); /* returns the number of days between dates */ DateResultdateDifference(Date* date1, Date* date2, int* difference);

  20. קובץ המימוש : date.c #include <string.h> #include <assert.h> #include <stdbool.h> #include "date.h" #define MIN_DAY 1 #define MAX_DAY 31 #define MIN_MONTH 1 #define MAX_MONTH 12 #define DAYS_IN_YEAR 365 static char* months[]= {"JAN", "FEB", "MAR", " APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; קבועים שמוגדרים בקובץ .c– מוסתרים משאר התוכנה משתנה סטאטי של קובץ – מוסתר משאר התוכנה

  21. קובץ המימוש : date.c /* returns the date’s month number (1-12) */ staticDateResultdateToMonth(Date* date, int* month); /* returns the number of days passed from 01/01/00 */ staticDateResultdateToDays(Date* date, int* day); פונקציות פנימיות של המודול – מוסתרות משאר התוכנה

  22. קובץ המימוש : date.c static boolisDayValid(int day){ return ((day >= MIN_DAY) && (day <= MAX_DAY)); } static boolisMonthNumberValid(intmonth){ return ((month >= MIN_MONTH) && (month <= MAX_MONTH)); { static boolisMonthStringValid(char*month){ for (inti = MIN_MONTH; i <= MAX_MONTH; i++){ if (strcmp(month, months[i-1]) == 0){ return true; } } return false; } פונקציות פנימיות של המודול – מוסתרות משאר התוכנה

  23. קובץ המימוש : date.c DateResultdateRead(FILE* inputFile, Date* date) { if (inputFile == NULL || date == NULL) returnDateBadArgument; if (fscanf (inputFile, "%d %s %d", &(date->day), date->month, &(date->year)) != 3) returnDateFailRead; if (!isDayValid(date->day) || !isMonthStringValid(date->month)) returnDateFail; returnDateSuccess; }

  24. קובץ המימוש : date.c DateResultdateWrite(FILE* outputFile, Date* date){ if(outputFile == NULL || date == NULL) returnDateBadArgument; if (fprintf (outputFile, "%d %s %d ", date->day, date->month, date->year) < 0) returnDateFail; returnDateSuccess; }

  25. קובץ המימוש : date.c staticDateResultdateToMonth(Date* date, int* month) { int i = 0; if(date== NULL || month == NULL) returnDateBadArgument; for (i = MIN_MONTH; i <= MAX_MONTH; i++){ if (strcmp(date->month, months[i-1]) == 0){ *month = i; returnDateSuccess; } } returnDateFail; }

  26. קובץ המימוש : date.c staticDateResultdateToDays(Date* date, int* days) { int month; if(date == NULL || days == NULL) returnDateBadArgument; if (dateToMonth(date,&month) != DateSuccess) returnDateFail; assert(isMonthNumberValid(month)); *days = date->day + month*(MAX_DAY – MIN_DAY + 1) + DAYS_IN_YEAR *date->year; returnDateSuccess; } אם ה-assert נכשל - יש באג ב-dateToMonth

  27. קובץ המימוש : date.c DateResultdateDifference(Date* date1, Date* date2, int* difference) { int days1, days2; if(date1 == NULL || date2 == NULL || difference == NULL) returnDateBadArgument; if((dateToDays(date1,&days1) != DateSuccess) || (dateToDays(date2,&days2) != DateSuccess)) returnDateFail; *difference = (days1 > days2) ? (days1 – days2) : (days2 - days1); assert(*difference >= 0); returnDateSuccess; }

  28. מודול שמשתמש במודול תאריך: proc.c #include<stdio.h> #include "date.h" intmain() { Date date1,date2; int difference; if (dateRead(stdin,&date1) == DateFail || dateRead(stdin,&date2) == DateFail) return 1; printf ("The difference between "); dateWrite(stdout,&date1); printf(" and "); dateWrite(stdout,&date2); dateDifference(&date1, &date2,&difference); printf("is %d days.\n", difference); return 0; }

  29. תמיכת שפת C בכתיבת מודולים • כתיבת מודולים הוטמעה בשפת C בכך שבניית התוכנה נעשית בשני שלבים: • הידור(compilation) של כל קובץ בנפרד • קישור(linking) של כל הקבצים ביחד

  30. הידור ב-C • כל קובץ מקומפל בנפרד • הבעיה: איך אפשר לקמפלקובץ כלשהו בנפרדאם הוא משתמש בפונקציות חיצוניות שנמצאות בקבצים אחרים ? • הפתרון: הקומפיילר אינו צריך לראות את המימוש של הפונקציות החיצוניות, אלא רק את הצהרותיהן • יש לעשות includeשל קבצי header שמכילים הצהרותשל פונקציותשהמודול משתמשבהן

  31. שימוש בקבציheader • כל קובץ שצריך להשתמשבפריטים כלשהם של מודול מסוים, יבצע includeלקובץ הheader- שלו : #include “date.h” #include <stdio.h> • include של header של התוכנה • ניתן לציין path מלא של הקובץ • include של header סטנדרטי (של מערכת ההפעלה) • כל ה-headers הסטנדרטיים נמצאים במקום מוגדר מראש ע''י מערכת ההפעלה

  32. הוראת include • חייבים לעשות includeרקלדברים שצריכיםאותם במקום שבו עושים include • עושים include ב-headerרק אם יש צורך בהגדרות של הקובץ הנכלל ב-header עצמו • בהצהרה של פונקציות של הheader- • המטרה - לצמצם תלויות • הוראות includeללא צורך או במקום הלא נחוץ - סגנון תכנות רע

  33. מגנון #ifndef בקבצי header • מכיוון שקובץ ה-header עשוי להיכלל במספר קבצים, יש להשתמש במנגנון ifndef#, אשר בודק האם קבוע מסוים הוגדר. • רק בפעם הראשונה בה יתבצע,#include "date.h" הקובץ date.h ייכלל בפועל • חייב להופיע בכל קובץ header #ifndefDATE_H #define DATE_H /* file date.h – the header of module date */ ... #endif

  34. הידור ב-C • תוצאת ההידור היא קובץ object • מכיל קוד מכונה שמתאים לקוד מקור של המודול • במקום קריאות לפונקציות חיצוניות יש "חורים" • ה"חורים" ימולאו בשלב הבא –שלב הקישור 0: 55 push %rbp 1: 48 89 e5 mov %rsp,%rbp 14: e8 00 00 00 00callq 19 <foo+0x19> 19: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffffffffffc(%rbp) 36: eb e8 jmp 20 <foo+0x20> 38: b8 00 00 00 00 mov $0x0,%eax 3d: e8 00 00 00 00callq 42 <foo+0x42> 42: c9 leaveq 43: c3 retq קוד מכונה ניתן פה להמחשה בלבד – אין צורך להבין אותו

  35. קישור (linking) • ה-linkerמחבראת כל קבצי ה-object לתוכנית אחת • ממלאאת כל ה"חורים" בקבצי object במידע אמיתי

  36. הידור וקישור על ידי gcc • כאשר מבצעים • ניתן להפריד את השלבים: • gccprog.cdate.c -o prog • מבוצעים שני השלבים ביחד – נוצר קובץ הרצה prog • gcc -c prog.cdate.c • יוצר את קבצי ה- object • gccprog.odate.o-o prog • מקשר את קבצי ה- objectויוצר קובץ הרצה prog

  37. שינוי במודולים של תוכנה • אם קובץמסוים משתנה– אילו קבצים צריך לקמפל/לקשר מחדש? • אם השינוי נעשה בקובץ .cניתן לקמפל רק אותו • אם השינוי נעשה בקובץ header, חייבים לקמפל את כל הקבצים שתלויים בקובץ ששונה • ואחרי שקימפלנו קובץ כלשהוא חייבים לבצע קישור מחדש של כל קבצי ה-object

  38. בנייה יעילה של תוכנה ממספר קבצים • דוגמא: תוכנית המורכבת מהקבצים הבאים: • calc.c • control.c • main_prog.c • בכל פעם שמשנים קובץ .c מסוים, מספיק להדר שוב רק אותו ולבצע קישור מחדש. • לדוגמא, אם שונה control.c, מספיק לבצע: gcc -c calc.c gcc -c control.c gcc -c main_prog.c gcccalc.ocontrol.omain_prog.o -o prog • gcc -c control.c • gcccalc.ocontrol.omain_prog.o -o prog

  39. אוטומציה של בניית התוכנה • תהליך בניית תוכנה גדולה יכול להיות מורכב • אלפי קבצים (מודולים) • עלול לקחת כמה שעות • כל קובץ יכול לדרוש הידור בצורה שונה • דגלי קומפילציה שונים • שפות תכנות שונות • קומפיילרים שונים • תלויות מורכבות בין הקבצים • כשקובץ מסוים משתנה – אילו קבצים צריך לקמפל מחדש? • קיימת סכנה בפספוס חלק מהתלויות ובניית תוכנה פגומה כתוצאה מכך • לא יעיל לקמפל את כל הקבצים בשביל כל שינוי

  40. אוטומציה של בניית התוכנה - make • ניתן להפוך את תהליךבניית התוכנה לאוטומטי ע"י שימוש בתוכנה make • מקבלת כקלט קובץ Makefileשבו מקודדים חוקי בניית התוכנה • אילו קבצים יש לקמפל ולקשר, מתי ובאילו תנאים • איךלקמפל/לקשר את הקבצים • תלויותבין הקבצים • כאשר משניםקובץ כלשהו ומריצים ,makeהפקודה מחשבת אילו צעדים בבניית התוכנה חייבים לבצע ומבצעת רק אותם • חוסכת קימפול מחדש של כל הקבצים

  41. קבצי Makefile • קובץ ה-Makefileהמתאים לדוגמא הנ"ל הוא: • prog:main_prog.ocalc.ocontrol.o • gcccalc.ocontrol.omain_prog.o -o prog • calc.o:calc.c • gcc -c calc.c • control.o:control.c • gcc -c control.c • main_prog.o:main_prog.c • gcc -c main_prog.c

  42. Makefile – דוגמא נוספת • תוכנה שמורכבת מהקבצים הבאים :

  43. make • קובץ ה-Makefileהמתאים לתוכנה הזאת: • הפעלת makeעל התוכנה prog:a.ob.oc.o gcca.ob.oc.o-o prog a.o:a.ca.hb.h gcc -c a.c b.o:b.cb.h gcc -c b.c c.o:c.cc.hb.h gcc -c c.c > make > make prog

  44. מבנה קובץ Makefile • קובץ Makefile מורכב ממספר כניסות • מבנה הכניסות : <target>: <other targets or files, the target depends on> <TAB> <build command for the target> • כל targetבדרך כלל מציין קובץ (קובץ מקור, header, object או הרצה) • #מסמן הערה עד סוף השורה • נקודות נוספות: • בלי TABבתחילת שורת הפקודה, הפקודה לא תתבצע. • כדי לשבור שורה ארוכה לכמה שורות יש להוסיף \ בסוף כל שורה פרט לאחרונה (ועדיין יש לשים TAB בתחילת כל שורה) • אין לשים רווחים לא בתחילת שורה ולא בסופה • סיום שורות ב-Enter

  45. שימוש ב-make make [ -f filename ] [ target ] • makeמחפשאת קובץ ה-Makefile באופן הבא: • אם לא משתמשים באופציה -f הוא מחפש קובץ בשם makefile או Makefile בתיקיה הנוכחית. • אם משתמשים באופציה -f הוא מחפש את הקובץ filename • אםtargetמופיעבהפעלת make,הפקודה מבצעת את רשימת הפקודות המופיעות ב-target ב-Makefile • אם targetלא מופיע בהפעלת make הפקודה מבצעת את ה-target הראשון ב-Makefile

  46. אופן פעולת make • בודקת את התלויות ב-target אותו הוא הולך לבצע • אם יש קובץ ברשימת התלויות שהוא חדש יותר מקובץ ה- target יש לבצע את הפקודה של ה-target על מנת לעדכנו • בדיקת התלויות נעשית באופן רקורסיבי, כך שיבוצעו כל הפקודות של הקבצים שבהם ה-target תלוי, בסדר הנכון

  47. הגדרת מאקרו ב-Makefile • ב-Makefile כדאי להגדיר כמאקרו מחרוזת • שחוזרת כמה פעמים • שעשויה להשתנות בעתיד • ניתן להגדיר מאקרו בצורה הבאה: EXEC = prog • אחרי שמאקרו כלשהו מוגדר, ניתן להתיחס אליו על ידי $ושם המאקרו בסוגריים: ($(EXEC • קיימים מאקרו מוגדרים מראש: • $@ הוא ה-target הנוכחי • $* הוא ה-target הנוכחי ללא סיומת

  48. דוגמא לשימוש במאקרו ב-Makefile CC = gcc OBJS = a.ob.oc.o EXEC = prog DEBUG_FLAG = # now empty, assign -g for debug COMP_FLAG = -c -Wall $(EXEC) : $(OBJS) $(CC) $(DEBUG_FLAG) $(OBJS) -o $@ a.o : a.ca.hb.h $(CC) $(DEBUG_FLAG) $(COMP_FLAG) $*.c b.o : b.cb.h $(CC) $(DEBUG_FLAG) $(COMP_FLAG) $*.c c.o : c.cc.hb.h $(CC) $(DEBUG_FLAG) $(COMP_FLAG) $*.c clean: rm -f $(OBJS) $(EXEC) > make clean rm -f a.o b.o c.o prog > make gcc -c -Wall a.c gcc -c -Wall b.c gcc -c -Wall c.c gcc a.o b.o c.o -o prog

  49. יצירת תלויות ל-Makefile באופן אוטומטי • אפשר למצוא תלויות בין קבצים ע"י שימוש בפקודה הבאה: gcc -MM <c files> • למשל: • ניתן לשמורפלט זה בקובץ ולהשתמש בו כשלד ל- Makefile > gcc -MM *.c a.o : a.c a.h b.h b.o : b.c b.h c.o : c.c c.h b.h

  50. ADT Abstract Data Type

More Related