59
ססססס סס'4 םםםםם םם םםםם םםםםם םםםםם םםםםםםםם Makefile םםםםםם םםםםםם םםםםםםם –Abstract data types

תרגול מס' 4

  • Upload
    paulos

  • View
    90

  • Download
    0

Embed Size (px)

DESCRIPTION

תרגול מס' 4. הידור של מספר קבצים חלוקה למודולים Makefile טיפוסי נתונים מופשטים – Abstract data types. הידור של מספר קבצים. שלבי ההידור עיבוד מקדים הידור קישור פונקציות סטטיות. הידור של מספר קבצים. לא נוח לשמור את כל הקוד בקובץ יחיד עבור ת ו כנה גדולה - PowerPoint PPT Presentation

Citation preview

4תרגול מס' הידור של מספר קבציםחלוקה למודוליםMakefile – טיפוסי נתונים מופשטיםAbstract data types

2מבוא לתכנות מערכות - 234122

הידור של מספר קבצים

שלבי ההידורעיבוד מקדיםהידורקישורפונקציות סטטיות

3מבוא לתכנות מערכות - 234122

הידור של מספר קבצים

לא נוח לשמור את כל הקוד בקובץ יחיד עבור תוכנה גדולה

ניתן לחלק את הקוד למספר קבצים ולקמפל כל קובץ בנפרד ולקשראת כל הקבצים לקובץ הרצה יחיד בסוף התהליך

-כדי להדר מספר קבצים בgcc ניתן פשוט לרשום מספר קבצים :gccכפרמטרים לשורת הפקודה של

> gcc a.c b.c mytest*.c

4מבוא לתכנות מערכות - 234122

Object Files

שלבי הקומפילציה

:את הידור הקוד ניתן לחלק לשלושה שלביםPreprocessing- עיבוד מקדים 1.

Compilationהידור - 2.

Linkingקישור - 3.

Linker

Preprocessor

SourceFiles

Compiler

a.h

a.c

b.c

c.c

a.o

c.o

b.o prog

5מבוא לתכנות מערכות - 234122

Preprocessingעיבוד מקדים – -שלב העיבוד המקדים עובר על קובץ הקוד ומבצע את ההוראות עבור הpreprocessor

הן כל השורות המתחילות ב-#preprocessorהוראות עבור ה-–

פעולות העיבוד המקדים הן כולן פעולות גזירה והדבקה פשוטות–

#define <macro> <value > הגדרת מאקרו חדש. לאחר ההגדרה בכל מקום שיופיע -<macro<-בקוד תבוצע החלפה ל >value>ניתן להגדיר יותר מסתם קבועים בעזרת מאקרו אך חשוב לשים לב להשלכות–

#include <filename >הוסף את תוכן הקובץ לתוך הקוד במיקום הנוכחי -הקוד שמתווסף עובר גם הוא עיבוד מקדים–

ההתייחסות היא לקובץ מן הספריה הסטנדרטית> < אם שם הקובץ מופיע בתוך –

ההתייחסות היא לקובץ שנמצא עם שאר הקוד שכתבנו" " אם שם הקובץ מופיע במרכאות –

#ifdefined <macro >< אם מוגדר מאקרו בשם -macro המשך כרגיל, אחרת מחק את >endifכל הקוד עד להופעת #

6מבוא לתכנות מערכות - 234122

Compilationהידור - שלב ההידור מקבל קבצי קוד מקור שעברו את שלב העיבוד המקדים

c, קבצים אלו "הודבקו" לתוך קבצי ה-hשלב זה אינו מקבל קבצי –הרלוונטיים

לכל קובץ קוד נוצר קובץ בינארי אשר מכיל את הקוד המהודר מקובץהקודgcc עבור oוסיומתו היא . object fileקובץ זה קרוי –

ייתכן וחלק מהפונקציות שהוכרזו לא מומשו ביחידת הקומפילציה יכיל "חורים"objectהנוכחית, במקרה זה קובץ ה-

קובץ זה תלוי מהדר ומערכת הפעלה - לכן קובץ אובייקט הנוצר עבורWindows שונה מקובץ הנוצר עבור מחשב ביתי עם studשרת ה-

7מבוא לתכנות מערכות - 234122

Linkingקישור - שלב הקישור מקבל כקלט קבצי אובייקט ומקשר את כולם לקובץ הרצה

יחיד

בשלב זה לכל "חור" שהושאר בקובץ אובייקט מקושרת הפונקציההמתאימה

אם אכן קיים לה מימוש באחד מקבצי האובייקט–

:ייתכנו שגיאות בשלב זה הנקראות שגיאות קישורלא נמצא מימוש עבור פונקציה מסוימת–

a.c:11: undefined reference to `my_function'

נמצא יותר ממימוש אחד עבור פונקציה מסוימת–

a.c:10: multiple definition of `main'

b.c:10: first defined here

8מבוא לתכנות מערכות - 234122

פונקציות סטטיות

:ניתן להכריז על פונקציה כסטטית

static <function declaration>

במקרה זה הפונקציה אינה נראית מחוץ ליחידת הקומפילציה שלה לא ינסה לקשר אותה לקריאות לפונקציה מיחידות קומפילציה linkerוה-

אחרות

:דוגמהstatic int square(int n) {

return n*n;

}

מיחידת קומפילציה אחרת squareבמקרה זה אם תהיה הכרזה וקריאה ל-–הפונקציה הסטטית לא תקושר לקריאה זו.

9מבוא לתכנות מערכות - 234122

הידור של מספר קבצים - סיכום

ניתן לקמפל מספר קבצים ביחד לקובץ הרצה יחיד:עיבוד-מקדים, הידור וקישורההידור ניתן לחלוקה לשלושה שלבים בשלב העיבוד המקדים מוחלפים מאקרוים ומבוצעות פעולותinclude בשלב ההידור נוצר מכל קובץcקובץ אובייקט בשלב הקישור מחוברים קבצי האובייקט לקובץ הרצה יחידכדי להסתיר פונקציות מיחידות קומפילציה אחרות ניתן להגדירן כסטטיות

10מבוא לתכנות מערכות - 234122

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

תכנות מודולארי-מודולים בCדוגמה: מודול תאריך

11מבוא לתכנות מערכות - 234122

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

אורך החיים של קוד יכול להיות עשרות שנים, לאורך תקופה זו יש לתחזקאת הקוד

תיקון באגים–

הכנסת תכונות חדשות–

התאמה להתקנים חדשים–

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

:תכנות מודולאריהפתרוןחלוקת התוכנה למודולים לפי תפקידם–

מאפשר שימוש חוזר במודולים עבור תוכנות אחרות–

מסתיר פרטי מימוש ומקטין את התלויות בקוד–

12מבוא לתכנות מערכות - 234122

מודול

מודול תוכנה מחולק לשני חלקים: המנשק והמימוש

מנשק(interface):מגדיר את הפעולות שניתן לעשות בעזרת המודול–

חלק זה חשוף למשתמש במודול–

( מימושimplementation):מספק את המימוש לפעולות שניתן לבצע דרך מנשק המודול–

חלק זה אינו חשוף למשתמש–

למשל, עבור מכונית הגה הוא חלק מהמנשק ואילו המנוע הוא חלקמהמימוש

13מבוא לתכנות מערכות - 234122

Cמודולים ב--כדי לכתוב מודולים בC ניצור לכל מודל קובץ c וקובץ h

-קובץ הh:יכיל את מנשק המודול הכרזות על הפונקציות–

הגדרות טיפוסים–

הכרזות על קבועים שיש בהם עניין למשתמש )למשל קודי שגיאה(–

-קובץ הc :יכיל את מימוש המודולמימושי פונקציות המנשק ופונקציות פנימיות נוספות–

הגדרות טיפוסים לשימוש פנימי–

הכרזות על קבועים שאין בהם עניין למשתמש–

2נראה כעת כיצד יוצרים מודול מטיפוס הנתונים עבור תאריך מתרגול

14מבוא לתכנות מערכות - 234122

date.hמנשק המודול - #ifndef DATE_H_#define DATE_H_ #include <stdbool.h>#include <stdio.h> #define MIN_DAY 1#define MAX_DAY 31#define MIN_MONTH 1#define MAX_MONTH 12#define DAYS_IN_YEAR 365 /** * A module for a date datatype */typedef struct Date_t {

int day;char month[4];int year;

} Date;

includeהגנה נגד כפול,

מה קורה בלעדיה?

15מבוא לתכנות מערכות - 234122

date.hמנשק המודול - /** Possible error codes */typedef enum {

DATE_SUCCESS, DATE_NULL_ARG, DATE_FAIL, DATE_INVALID} DateResult;

/** writes the date to the stream fd */DateResult datePrint(Date date, FILE* fd); 

/** Reads a date from the stream fd */DateResult dateRead(Date* date, FILE* fd); 

/** Returns true if both dates are identical */bool dateEquals(Date date1, Date date2); 

/** Returns the number of days between the dates */int dateDifference(Date date1, Date date2); 

/** Checks if the date has valid values */bool dateIsValid(Date date); #endif /* DATE_H_ */

כדי לאפשר למשתמש שימוש נוח במודול עלינו לספק לו קודי

שגיאה מפורטים, לכן נגדיר טיפוס מיוחד למטרה זו ונשתמש

בו

יש לספק תיעוד מפורט יותר של ערכי החזרה

והשגיאה. תיעוד זה אינו מסופק כאן מפאת חוסר

המקום בשקף

16מבוא לתכנות מערכות - 234122

date.cמימוש המודול - #include "date.h"#include <string.h> #define INVALID_MONTH 0static const char* const months[] = { "JAN", "FEB", "MAR", " APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; static int monthToInt(char* month) {

for (int i = MIN_MONTH; i <= MAX_MONTH; i++) {if (strcmp(month, months[i - 1]) == 0) {return i;}}return INVALID_MONTH;

} static int dateToDays(Date date) {

int month = monthToInt(date.month);return date.day + month*(MAX_DAY - MIN_DAY + 1) + DAYS_IN_YEAR * date.year;

}

פונקציות פנימיות אינן מעניינות את המשתמש ולכן יש להכריז עליהן

כסטטיות

אין צורך להכריז מחדש על הפונקציות, פשוט כוללים את

המנשק בקובץ

17מבוא לתכנות מערכות - 234122

date.cמימוש המודול - DateResult datePrint(Date date, FILE* fd) {

if (!fd) {return DATE_NULL_ARG;

}fprintf(fd, "%d %s %d\n", date.day, date.month, date.year);return DATE_SUCCESS;

} DateResult dateRead(Date* date, FILE* fd) {

if (!date || !fd) {return DATE_NULL_ARG;

}if (fscanf(fd, "%d %s %d", &(date->day), date->month, &(date-

>year)) != 3) {return DATE_FAIL;

}return dateIsValid(*date) ? DATE_SUCCESS : DATE_INVALID;

}

18מבוא לתכנות מערכות - 234122

date.cמימוש המודול -

bool dateEquals(Date date1, Date date2) {return date1.day == date2.day &&

strcmp(date1.month,date2.month) == 0 &&date1.year == date2.year;

} int dateDifference(Date date1, Date date2) {

int days1 = dateToDays(date1);int days2 = dateToDays(date2);return days1 - days2;

}

19מבוא לתכנות מערכות - 234122

שימוש במודול התאריך#include "date.h"

int main() {Date date1 = { 21, "NOV", 1970 };Date date2;DateResult result = dateRead(&date2, stdin);if (result == DATE_FAIL) {

fprintf(stderr,"Bad date format\n");return 0;

}if (result == DATE_INVALID) {

fprintf(stderr,"Invalid date\n");return 0;

}datePrint(date1, stdout);datePrint(date2, stdout);if (!dateEquals(date1,date2)) {

int diff = dateDifference(date1,date2);int absoluteDiff = diff < 0 ? -diff : diff; printf("The dates are %d days apart\n", absoluteDiff );

}return 0;

כעת ניתן להשתמש במודול התאריך בתכניות שונות וגם לעדכן את מודול התאריך

בקלות

20מבוא לתכנות מערכות - 234122

חלוקה למודולים - סיכום

נהוג לחלק את הקוד למודולים כדי לאפשר שימוש חוזר ותחזוקה נוחהמודול מוגדר משני חלקים - מנשק ומימושהמנשק מכיל רק את מה שהמשתמש במודול זקוק לו-בC מגדירים את מנשק המודול בקובץ h ואת המימוש בקובץ c יש להגן על קבציh מפני includeכפול -פונקציות פנימיות של המודול יש להגדיר כstatic

21מבוא לתכנות מערכות - 234122

Makefile

בנייה הדרגתית של תכונה-שימוש בmakeלבנייה אוטומטית

22מבוא לתכנות מערכות - 234122

בנייה של תוכנה גדולה

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

נרצה להימנע מקומפילציה מחדש של כל הקוד לאחר שינוי קטן

נניח שבתוכנה שלנו קיימים הקבציםa.c, b.c-ו c.cכיצד נבנה את התוכנה כך שנוכל לקמפל מחדש רק חלקים שישתנו?–

?a.cכיצד נקמפל את התוכנה מחדש לאחר שינוי ב-–

23מבוא לתכנות מערכות - 234122

בנייה הדרגתית של תוכנה

:לדוגמה, התכנית שלנו מורכבת מהקבצים הבאים–calc.c–control.c–main_prog.c

שוב ולקשר מחדשלהדר רק אותו בכל פעם שנשנה קובץ יחיד מספיק:control.cלדוגמה, אם נשנה את –

?בעיה: מה נעשה כשמספר הקבצים והתלויות ביניהם גדלים

> gcc -c calc.c> gcc -c control.c> gcc -c main_prog.c> gcc calc.o control.o main_prog.o -o prog

> gcc -c control.c> gcc calc.o control.o main_prog.o -o prog

24מבוא לתכנות מערכות - 234122

makeMake היא כלי פשוט ויעיל המאפשר בנייה הדרגתית של תוכנה בלי חזרה

על חלקים שלא עודכנו–make מאפשרת יצירת קובץ Makefile אשר יכיל הוראות לבניית התוכנה

תשתמש בהוראות כדי makeבכל פעם שנרצה לבנות מחדש את התוכנה –לבנות מחדש רק את החלקים שהשתנו

prog: calc.o control.o main_prog.o gcc calc.o control.o main_prog.o -

o prog

calc.o: calc.c calc.hgcc -c calc.c

control.o: control.c calc.h control.hgcc -c control.c

main_prog.o: calc.h control.hgcc -c main_prog.c

> make

25מבוא לתכנות מערכות - 234122

makeשימוש ב--ה( לאחר כתיבת קובץ ההוראותmakefile-השימוש ב ,)make מתבצע

בעזרת הפקודה הבאה:

> make [-f filename] [target]

Makefile או makefile תחפש קובץ בשם makeאם לא מוגדר שם קובץ, –בתיקיה הנוכחית

מאפשר לבצע רק חלק מתהליך הבנייה או לטפל במקרים targetהארגומנט –makefileמיוחדים. אם הוא אינו מופיע יבוצע הכלל הראשון ב-

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

26מבוא לתכנות מערכות - 234122

makefile-הmakefileמכיל את רשימת הקבצים הדרושים לתוכנה והתלויות ביניהם -הmakefile( מורכב מכללים rules)

כל כלל מתחיל בשורה מהצורה:–

a.o: a.c a.h b.h הוא שם הקובץ שנוצר מכלל זהtargetכאשר שם הכלל, הקרוי גם •

targetהתלויות הם הקבצים ששינוי בהם משפיע על שינוי ה-•השורות הבאות בכלל מכילות את רשימת הפקודות שיש לבצע עבור הכלל–

TABכל שורת פקודה חייבת להתחיל ב-•:a.oלדוגמה, כלל עבור יצירת קובץ האובייקט –

a.o: a.c a.h b.h

gcc -c a.c

מסמן הערה עד סוף השורה#התו בדומה למאקרו ב-\ניתן להשתמש בתו( כדי לשבור שורה ארוכה C)

27מבוא לתכנות מערכות - 234122

דוגמה:נסתכל על התכנית הבאה-קובץ הmake המתאים ייראה

כך:

הפעלתmake:תיראה כך

a.c:#include "a.h"

a.h:#include "b.h"

b.c:#include "b.h"

b.h:

c.c:#include "b.h"#include "c.h"

c.h:

a.o b.o c.o

prog

prog: a.o b.o c.ogcc a.o b.o c.o -o

prog

a.o: a.c a.h b.hgcc -c a.c

b.o: b.c b.hgcc -c b.c

c.o: c.c c.h b.hgcc -c c.c

> make

> make prog

28מבוא לתכנות מערכות - 234122

makeשיטת העבודה של כאשרmake< מקבלת את הכלל name>: <dependencies לביצוע היא >

משתמשת באלגוריתם הבא:< יש לבצע בדיקה:dependenciesלכל שם ב->–

אם קיים כלל מתאים לתלות הזו נבצע אותו תחילה בצורה רקורסיבית•

אם לא קיים כלל נבדוק אם הקובץ עודכן מאז הפעם האחרונה שביצענו •את הכלל )לפי חתימת הזמן על הקובץ(

<nameאם נמצאו תלויות שהשתנו יש לבצע את הפקודות עבור הכלל >–

אם לא היה שינוי באף אחד מהתלויות של הכלל אין צורך לבצע כלום–

29מבוא לתכנות מערכות - 234122

מציאת תלויות

-ניתן למצוא את התלויות בתוכנה ולקבל שלד עבור הmakefile בעזרת הפקודה הבאה

> gcc -MM *.c

:לדוגמה, הפלט עבור הדוגמה הקודמת

> 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

30מבוא לתכנות מערכות - 234122

makefileאפשרויות נוספות ב-

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

עבור מחרוזות שצפויות להשתנות בעתיד•

מאקרו מוסיפים בקובץ שורה מהצורה הבאה:להגדירכדי •

<MACRO NAME> = <string>

למאקרו משתמשים ב-$ ושם המאקרו בסוגריים:להתייחסכדי •

$(<MACRO NAME>) :קיימים מספר מאקרוים מוגדרים מראש

הנוכחיtarget הוא ה-$@–

הנוכחי ללא סיומתtargetהוא ה-$* –

31מבוא לתכנות מערכות - 234122

מתקדם יותרmakefileדוגמה ל-

CC = gccOBJS = a.o b.o c.oEXEC = progDEBUG_FLAG = # now empty, assign -g for debugCOMP_FLAG = -std=c99 -Wall -Werror

$(EXEC) : $(OBJS)$(CC) $(DEBUG_FLAG) $(OBJS) -o $@

a.o : a.c a.h b.h$(CC) -c $(DEBUG_FLAG) $

(COMP_FLAG) $*.cb.o : b.c b.h

$(CC) -c $(DEBUG_FLAG) $(COMP_FLAG) $*.cc.o : c.c c.h b.h

$(CC) -c $(DEBUG_FLAG) $(COMP_FLAG) $*.cclean:

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

32מבוא לתכנות מערכות - 234122

להרבה מהקבצים שיש ליצור בבניית התוכנה יש כללים קבועים בבנייתם בעל אותו שם c נוצרים על בעזרת הידור של קובץ oלמשל כל קבצי ה-–

באותה צורה

-כדי למנוע שכפולי קוד בקובץ הMakeקיימים כללי מובנים ומשתנים מוגדרים

מראש לסוגי קבצים מסוימים יודעתo, makeלמשל עבור קבצי –

להשתמש בפקודה הבאה ליצירתם כברירת מחדל:cמקובץ

כללים מובנים

CC=gccOBJS=a.o b.o c.oEXEC=progDEBUG=# now empty, assign -g for debugCFLAGS=-std=c99 -Wall -Werror $(DEBUG)

$(EXEC) : $(OBJS)

a.o : a.c a.h b.hb.o : b.c b.hc.o : c.c c.h b.h

clean:rm -f $(OBJS) $(EXEC)

$(CC) $(CFLAGS) -c

makeקובץ ה-החדש המנצל את החוקים המובנים

33מבוא לתכנות מערכות - 234122

Makefileסיכום -

ניתן להגדיר קבציmakefileכדי להקל ולזרז את בניית התוכנה הפקודהmake-משתמשת ב makefile כדי לבנות את התוכנה בצורה

אוטומטית ניתן להגדיר מאקרו בתוךmakefileכדי להקל על שינויו בעתיד -ניתן להשתמש בgcc כדי ליצור שלד של makefile

34מבוא לתכנות מערכות - 234122

טיפוסי נתונים מופשטים Abstract data

typesהסתרהADTעבור תאריך

35מבוא לתכנות מערכות - 234122

בעיה

:משתמש במודול התאריך שלנו כתב את הקוד הבאint main() {

Date date1 = { 21, "NOV", 1970 };

Date date2;

printf("Enter a day number:");

scanf("%d", &date2.day);

// ... more code ...

return 0;

}

?מה הבעיה? מה גורם לה

36מבוא לתכנות מערכות - 234122

Encapsulationהסתרה - הבעיה בטיפוסי הנתונים שלנו - המימוש חשוף

קוד במקום להשתמש בקוד קייםלשכפלהמשתמש עלול –

שינויים עתידיים במודול "ישברו" את הקוד של המשתמש בו–

הפתרון: נסתיר את המימוש של טיפוס הנתונים מהמשתמש כך שקודלא יתקמפלאשר אינו משתמש במנשק

חשוב לציין - המשתמש יכול להיות כותב המודול בעצמו

37מבוא לתכנות מערכות - 234122

טיפוס נתונים מופשט

-כדי להסתיר את המימוש מהמשתמש בC:נשתמש בשיטה הבאה טיפוס מצביע למבנה נכריז על hבקובץ ה-–

typedef struct datatype_t* Datetype;

את המבנה נממש cבקובץ ה-–

struct datatype_t {

// fields ...

};

משתמשים במודול לא יוכלו לבצעdereferenceלמצביע להשתמש בו רק דרך המנשק המוגדרמשתמשים במודול חייבים

38מבוא לתכנות מערכות - 234122

ADT - עבור תאריך date.h#ifndef DATE_H_#define DATE_H_ #include <stdbool.h>#include <stdio.h> #define MIN_DAY 1#define MAX_DAY 31#define MIN_MONTH 1#define MAX_MONTH 12#define DAYS_IN_YEAR 365 /** * A module for a date */typedef struct Date_t* Date; typedef enum {

DATE_SUCCESS, DATE_NULL_ARG, DATE_FAIL, DATE_INVALID} DateResult;

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

dereference-ל Date

39מבוא לתכנות מערכות - 234122

ADT - עבור תאריך date.h/** Allocates a new date */Date dateCreate(int day, int month, int year); /** Allocates a new date which is a copy of the argument */Date dateCopy(Date date); /** Frees an existing date object */void dateDestroy(Date date); DateResult datePrint(Date date, FILE* fd);DateResult dateRead(Date date, FILE* fd);bool dateEquals(Date date1, Date date2);int dateDifference(Date date1, Date date2);bool dateIsValid(Date date); #endif /* DATE_H_ */

מאחר והמשתמש לא יכול ליצור עצמים מטיפוס התאריך עלינו

לספק לו מנשק לביצוע פעולות אלו

40מבוא לתכנות מערכות - 234122

ADT - עבור תאריך date.c#include "date.h"#include <stdlib.h>#include <string.h>#define INVALID_MONTH 0

struct Date_t {int day;char month[4];int year;

};

static const char* const months[] = {"JAN", "FEB", "MAR", " APR", "MAY", "JUN",

"JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };

static int monthToInt(char* month) {for (int i = MIN_MONTH; i <= MAX_MONTH; i++) {

if (strcmp(month, months[i - 1]) == 0) {

return i;}

}return INVALID_MONTH;

}

Cהמבנה מוגדר בקובץ ה-ולכן גישה לשדותיו אפשרית רק מיחידת הקומפילציה הזו

41מבוא לתכנות מערכות - 234122

ADT - עבור תאריך date.cstatic bool isDayValid(int day) {

return day >= MIN_DAY && day <= MAX_DAY;} static bool isMonthNumberValid(int month) {

return month >= MIN_MONTH && month <= MAX_MONTH;} static int dateToDays(Date date) {

int month = monthToInt(date->month);return date->day + month*(MAX_DAY - MIN_DAY + 1) +

DAYS_IN_YEAR * date->year;}

42מבוא לתכנות מערכות - 234122

ADT - עבור תאריך date.cDate dateCreate(int day, int month, int year) {

if (!isDayValid(day) || !isMonthNumberValid(month)) {

return NULL;}Date date = malloc(sizeof(*date));if (!date) {

return NULL;}date->day = day;strcpy(date->month, months[month-1]);date->year = year;return date;

}

43מבוא לתכנות מערכות - 234122

ADT - עבור תאריך date.cDate dateCopy(Date date) {

if (!date) {return NULL;

}return dateCreate(date->day, monthToInt(date->month),

date->year);} void dateDestroy(Date date) {

free(date);} bool dateIsValid(Date date) {

return isDayValid(date->day) &&monthToInt(date->month) != INVALID_MONTH;

}

למה בכלל לטרוח?

44מבוא לתכנות מערכות - 234122

ADT - עבור תאריך date.cDateResult datePrint(Date date, FILE* fd) {

if(!fd) {return DATE_NULL_ARG;

}fprintf(fd, "%d %s %d\n", date->day, date->month, date->year);return DATE_SUCCESS;

} DateResult dateRead(Date date, FILE* fd) {

if(!date || !fd) {return DATE_NULL_ARG;

}if(fscanf(fd,"%d %s %d",&(date->day),date->month,&(date->year)) !=

3) {return DATE_FAIL;

}return dateIsValid(date) ? DATE_SUCCESS : DATE_INVALID;

45מבוא לתכנות מערכות - 234122

ADT - עבור תאריך date.cbool dateEquals(Date date1, Date date2) {

return date1->day == date2->day &&strcmp(date1->month,date2->month) == 0 &&date1->year == date2->year;

} int dateDifference(Date date1, Date date2) {

int days1 = dateToDays(date1);int days2 = dateToDays(date2);return days1 - days2;

}

 

46מבוא לתכנות מערכות - 234122

עבור תאריךADTשימוש ב-#include "date.h" int main() {

Date date1 = dateCreate(21, 11, 1970);Date date2 = dateCreate(1,1,0);DateResult result = dateRead(date2, stdin);if (result != DATE_SUCCESS) {fprintf(stderr,"Bad input\n");return 0;}if (!dateEquals(date1,date2)) {int diff = dateDifference(date1,date2);int absoluteDiff = diff < 0 ? -diff : diff; printf("The dates are %d days apart\n", absoluteDiff ); }dateDestroy(date1);dateDestroy(date2);return 0;

}

לא לשכוח לשחרר!ההקצאות הן

דינאמיות!

מה הבעיה?

47מבוא לתכנות מערכות - 234122

טיפוסי נתונים מופשטים - סיכום

טיפוסי נתונים מופשטים נבדלים מטיפוסי נתונים רגילים בניתוק המימושמהמנשק

כדי להסתיר את המימוש שלstruct-ב Cנשתמש במצביעים למבנה את הגדרת המבנה נשים בקובץC

48מבוא לתכנות מערכות - 234122

מצביעים לפונקציות

תחבירדוגמאותקוד גנרי

49מבוא לתכנות מערכות - 234122

מצביעים לפונקציות

?מה משותף לשתי הפונקציות הבאות

bool bigger(int a, int b);

bool abs_bigger(int a, int b);

ביצוע קריאה לפונקציות בעלות אותה חתימה נעשה באותה צורה בקודמכונה

השמת המשתנים במקום מוגדר )בד"כ על המחסנית(1.

קפיצה לכתובת תחילת הפונקציה בזיכרון2.

כתיבת ערך החזרה למקום מוסכם3.

קפיצה לכתובת ממנה נקראה הפונקציה4.

זהים לכל שתי פונקציות בעלות אותה חתימה4 ו-3, 1שלבים לכן ניתן לקרוא לפונקציות שונות בעזרת קוד דומה–

50מבוא לתכנות מערכות - 234122

מצביעים לפונקציות:ניתן להכריז על מצביע לפונקציה בעלת חתימה מסוימת

<return type> (*<name>)(<parameters>) = <initial value>;

ומחזירה intלמשל הכרזה על מצביע עבור פונקציה המקבלת משתנה יחיד מטיפוס –int-המאותחל ל NULL:תיראה כך

int (*ptr)(int) = NULL;

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

int square(int n);

...

ptr = square; // &square also works

:ניתן לקרוא לפונקציה דרך המצביעprintf("%d", ptr(5)); // (*ptr)(5) also works

לא נהוג להשתמש ב-& ו-* עבור מצביעים

לפונקציות

מה קורה בלי הסוגריים?

51מבוא לתכנות מערכות - 234122

דוגמה בסיסית:ברשותנו שתי הפונקציות הבאות

נכתוב את הפונקציהmax המקבלת מצביע לפונקציה ומחזירה את האיבר הגדולמביניהם בהתאם לקריטריון שמועבר לה

int max(int a, int b, bool (*compare)(int,int)) {

return compare(a,b) ? a : b;

}

מה יהיו תוצאות כל אחת מההרצות הבאות שלmax?max(-7, 5, isBigger);

max(-7, 5, isBiggerAbs);

bool isBiggerAbs(int a, int b) {

int abs_a = a > 0 ? a : -a;

int abs_b = b > 0 ? b : -b;

return abs_a > abs_b;}

bool isBigger(int a, int b) {

return a > b;}

52מבוא לתכנות מערכות - 234122

עוד דוגמה

בעזרת מצביעים לפונקציות ניתןליצור קוד שבכל הרצה שלו יתנהג

אחרת:

int main() {bool (*function)(int, int);

 if (getchar() == '1') {

function = isBigger;} else {

function = isBiggerAbs;}

 int a = -5, b = 3;bool b = function(a, b);

 if (b) {

printf("%d",a);} else {

printf("%d",b);}return 0;

}

53מבוא לתכנות מערכות - 234122

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

typedef bool (*CmpFunction)(int, int);

 

void sort(int* array, int n, CmpFunction compare) {

assert(array != NULL && compare != NULL);

for (int i = 0; i < n; i++) {

for (int j = i + 1; j < n; j++) {

if (compare(arr[i], arr[j])) {

int tmp = array[i];

array[i] = array[j];

array[j] = tmp;

}

}

}

}

כדי להימנע מהתחביר הלא נוח של מצביעים לפונקציות

typedefניתן להשתמש ב-

54מבוא לתכנות מערכות - 234122

דוגמה - מיון

:דוגמה לשימושint main(){ int arr[] = { 1, -3, 9, -10, -5 }; sort(arr, 5, isBigger); // -10 -5 -3 1 9 sort(arr, 5, isBiggerAbs); // 1 -3 -5 9 -10 return 0;}

55מבוא לתכנות מערכות - 234122

קוד גנרי

קוד גנרי הוא קוד המסוגל לעבוד על עצמים מטיפוסים שונים

-בC:ניתן לכתוב קוד גנרי בעזרת כדי לייצג עצמים כללייםvoidמצביעים ל-–

מצביעים לפוקציות כדי לייצג את הפעולות על העצמים–

בשיטה זו נוכל לממש אלגוריתמים פעם אחת בלבד ולמנוע שכפול קוד

56מבוא לתכנות מערכות - 234122

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

typedef bool (*CmpFunction)(void*, void*);

 

void sort(void** array, int n, CmpFunction compare) {

assert(array != NULL && compare != NULL);

for (int i = 0; i < n; i++) {

for (int j = i + 1; j < n; j++) {

if (compare(arr[i], arr[j])) {

void* tmp = array[i];

array[i] = array[j];

array[j] = tmp;

}

}

}

}

57מבוא לתכנות מערכות - 234122

שימוש בפונקצית המיון הגנרי

כדי למיין באמצעות הפונקציה החדשה עלינו ליצור פונקציות השוואהמתאימות:

bool intIsBigger(void* a, void* b) {

return isBigger(*(int*) a, *(int*) b);

}

 

bool dateIsBigger (void* date1, void* date2) {

assert(date1 && date2);

int difference = dateDifference(date1,date2);

return intIsBigger(difference, 0);

}

למה לא חייבים המרה

מפורשת?

58מבוא לתכנות מערכות - 234122

שימוש בפונקצית המיון הגנריint main() {

void* dates[3];dates[0] = dateCreate(20, 5, 2010);dates[1] = dateCreate(1, 1, 2000);dates[2] = dateCreate(2, 2, 2001);

  int* numbers[3];numbers[0] = malloc(sizeof(int)); *numbers[0] = 17;numbers[1] = malloc(sizeof(int)); *numbers[1] = 1;numbers[2] = malloc(sizeof(int)); *numbers[2] = 7;

 sort(dates, 3, dateIsBigger);sort((void**)numbers, 3, intIsBigger);

 for (int i = 0; i < 3; i++) {

datePrint(dates[i], stdout); printf("\n");}

for (int i=0; i<3; i++) {printf("%d\n",*numbers[i]);

}return 0;

}

1 JAN 20002 FEB 200120 MAY 20101717

למה דרושה המרה

מפורשת?

מה חסר?

59מבוא לתכנות מערכות - 234122

מצביעים לפונקציות - סיכום

-ניתן להגדיר בCמצביעים אשר שומרים כתובת של פונקציה ניתן להריץ את הפונקציה שכתובתה שמורה במצביענשתמש במצביעים לפונקציות כדי להעביר "הוראות" לחלקי קוד אחריםבעזרת מצביעים לפונקציות ניתן לכתוב קוד גנרי ולחסוך שכפול קוד