57
ססססס סס'2 םםםםםםם םםםםם םםםםםם םםםםםםם - םםםםםStructures - םםםםםם םםםםםםData types - םםםםם םםםםםםם םmain םםםםם םםםםםם

תרגול מס' 2

  • Upload
    andren

  • View
    71

  • Download
    4

Embed Size (px)

DESCRIPTION

תרגול מס' 2. מצביעים הקצאת זיכרון דינאמית מבנים - Structures טיפוסי נתונים - Data types העברת פרמטרים ל- main טענות נכונות. מצביעים. שימוש בסיסי אריתמטיקת מצביעים void* מצביע למצביע. מבנה הזיכרון - תזכורת. כתובת. ערך הבית. הזיכרון מורכב מתאים הקרויים בתים: - PowerPoint PPT Presentation

Citation preview

Page 1: תרגול מס' 2

2תרגול מס' מצביעיםהקצאת זיכרון דינאמית - מבניםStructures - טיפוסי נתוניםData types-העברת פרמטרים לmainטענות נכונות

Page 2: תרגול מס' 2

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

מצביעיםשימוש בסיסיאריתמטיקת מצביעיםvoid*מצביע למצביע

Page 3: תרגול מס' 2

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

מבנה הזיכרון - תזכורת:הזיכרון מורכב מתאים הקרויים בתים

מספריכל בית מכיל ערך –פירוש הערכים כערך לא מספרי הוא ע"י •

התכניתלכל בית יש שם – כתובת–

הכתובת היא מיקומו בזיכרון•בד"כ כתובות בזיכרון נרשמות בבסיס •

(16הקסדצימלי )

:משתנים מאוחסנים בבתיםטיפוסים שונים דורשים מספר שונה של בתים, –

למשל:•int בתים8 או 4 צורך •char.צורך בית יחיד

0x0200

0x0201

0x0202

0x0203

0x0204

0x0205

0x0206

0x0207

0x0208

0x0209

0x020A

0x020B

0x020C

0x020D

700000001041011081081110

int התופס בתים4

” hello“מחרוזת המורכבת

ממספר תווים

ערך הבית כתובת

Page 4: תרגול מס' 2

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

מצביעים - תזכורת עבור טיפוסT-נקרא ל T *-מצביע לT

int* הוא מצביע ל-intלמשל –

מצביע מסוגT של משתנה כתובת * הוא משתנה אשר שומר.Tמטיפוס

באופרטור &ניתן לקבל את כתובתו של משתנה ע"י שימושלא ניתן להשתמש ב-& על ביטויים או קבועים )מדוע?(–

אופרטור *ניתן לקרוא את ערכו של המצביע ע"יdereferencingפעולה זו קרויה –

int n = 5;int* ptr = &n; // ptr now points to nprintf("%d",*ptr); // dereferencing ptr

0x20834370820119

0x0204

0x0205

0x0206

0x0207

0x0208

0x020A

0x020B

0x020C

Page 5: תרגול מס' 2

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

0x0 - NULLהכתובת הינה כתובת לא חוקית:0הכתובת

אף עצם אינו יכול להיות בעל כתובת זו–ניתן לעשות שימוש בכתובת זו כדי לציין שמצביע מסוים אינו מצביע לאף עצם כרגע-השתמשו בNULL 0 כאשר אתם מתייחסים לכתובת ולא בקבוע

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

תתקבל ההודעה:UNIXב-– “segmentation fault”

גישה למצביע המכיל "זבל" תגרום לתכניתלהתנהג בצורה לא צפויה

בקוד!מצביעים לא מאותחלים אסור להשאיר –(C99ניתן להכריז על המשתנה מאוחר יותר )•NULLבמקרה ולא ניתן יש לאתחל אותו ל-•

Page 6: תרגול מס' 2

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

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

:חיבור מספרים שלמים–int n = ...;int* ptr2 = ptr + n;

תאים קדימה/אחורהnהתוצאה היא כתובתו של המשתנה מטיפוס מתאים •:חיסור שני מצביעים–

int diff = ptr2 - ptr;( התוצאה היא מספר שלםint)

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

:מדוע לא ניתן לחבר שני מצביעים?שאלה

Page 7: תרגול מס' 2

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

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

למשל כך:int* ptr;... =

int n = *(ptr + 5);

:האופרטור ] [ משמש כקיצור לפעולה זוint n = ptr[5];

:כלומר הפעולות הבאות שקולות*(ptr + n) ≡ ptr[n]

Page 8: תרגול מס' 2

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

מצביעים ומערכיםמערכים ומצביעים מתנהגים בצורה דומה

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

void sort(int* array, int size);עבור מערךאיטרטורמצביע יכול לשמש כ

int array[N];

...//

for(int* ptr = array; ptr < array+N; ptr++){

printf("%d ",*ptr);

}

:הבדליםהכרזה על מצביע אינה מקצה זיכרון הכרזה על מערך מקצה זיכרון כגודל המערך, –

!לאחסון המשתניםניתן לשנות את ערכו של מצביע, אך לא ניתן לשנות את "ערכו" של תחילת המערך–

ניתן C99ב-להכריז על

משתנה בתוך forלולאת

Page 9: תרגול מס' 2

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

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

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

int n = 5;double d = 3.14;

void* ptr = &n;ptr = &d;

double d2 = *ptr; // Error: cannot dereference void*double d3 = *(double*)ptr; // O.K. – option 1double* dptr = ptr; // Implicit cast from void* to double*double d4 = *dptr; // O.K. – option 2

Page 10: תרגול מס' 2

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

מצביע למצביע ניתן ליצור מצביע לכל טיפוס, בפרט עבור טיפוסT ניתן ליצור מצביע מטיפוס *T**

Tמצביע למצביע של מתקבל –אפשר להמשיך לכל מספר של *–

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

void sort_pointers(int** array, int size);

עבור מחרוזות:swapכתיבת פונקצית –void swap_strings(char** str1, char** str2) {

char* temp = *str1;*str1 = *str2;*str2 = temp;

}מדוע יש כאן צורך במצביע למצביע?•

Page 11: תרגול מס' 2

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

מצביעים - סיכוםמצביעים משמשים להתייחסות לתאי זיכרון ניתן לקבל את כתובתו של משתנה ע"י אופרטור& ניתן לקרוא ממצביע ולקבל את הערך המוצבע ע"י* הערךNULL מציין שאין עצם מוצבע ואסור לקרוא אותו על מצביעיםפעולות חשבוניות ניתן לבצע

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

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

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

גנרי

Page 12: תרגול מס' 2

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

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

Page 13: תרגול מס' 2

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

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

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

: משתנים אשר מוגדרים לכל אורך התכנית וניתן לגשת משתנים גלובליים–אליהם מכל מקום. המשתנים מוקצים כאשר התכנית מתחילה ונשמרים לכל

אורך זמן הריצה: משתנים פנימיים של פונקציה. משתנים אלו משתנים סטטיים של פונקציה–

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

: מוקצים ומשוחררים ע"י קריאה מפורשת לפונקציהמשתנים דינאמיים–

Page 14: תרגול מס' 2

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

הרוע של משתנים גלובליים משתנים גלובליים, משתנים סטטיים של קובץ ומשתנים סטטיים של

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

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

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

בקורסים מתקדמים בהמשך התואר תראו מקרים בהם חובה או מומלץ –להשתמש במשתנים כאלו

Page 15: תרגול מס' 2

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

משתנים דינאמיים הם משתנים שזמן החיים שלהם הוא בשליטת משתנים דינאמיים

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

stackבניגוד למשתנים מקומיים המוקצים על מחסנית הקריאות, ה-•

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

הגישה למשתנים אלו נעשית תמיד בעזרת מצביעים

Page 16: תרגול מס' 2

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

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

void* malloc(size_t bytes);–mallocמקבלת גודל בבתים של זיכרון אותו עליה להקצות ערך החזרה מכיל מצביע לתחילת גוש הזיכרון שהוקצה–התוצאה היא תמיד גוש זיכרון רציף–NULLבמקרה של כשלון מוחזר –

:לאחר מכן ניתן להתייחס לשטח המוצבע כאל משתנה או מערךint* my_array = malloc(sizeof(int) * n);for (int i=0; i<n; i++) {

my_array[i] = i;}

Page 17: תרגול מס' 2

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

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

int* ptr = malloc(4);4 1 או 0- מספר לא ברור המופיע בקוד שאינו מספר קסם הוא הרגל תכנותי רעמספרי קסם הם:

פוגעים בקריאות הקוד•מקשים על שינויים עתידיים בקוד•

8 הוא intלמשל מעבר לסביבה בה גודלו של –ככל שצריך יותר שינויים הסיכוי לפספס אחד מהם גדל–

:יש להימנע ממספרי קסם שיקלו על שינויים ועל קריאת הקודdefine בעזרת #קבועיםע"י הגדרת –ע"י שמירת ערכם במשתנה קבוע בעל שם ברור–

Page 18: תרגול מס' 2

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

קביעת הגודל - נסיון שני נסיון שני - נגדיר את הגודל שלint:כקבוע

#define SIZE_OF_INT 4

int* ptr = malloc(SIZE_OF_INT);

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

non-portableקוד שדורש שינויים במעבר בין סביבות שונות נקרא –

Page 19: תרגול מס' 2

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

sizeofאופרטור נשתמש באופרטורsizeof :אשר מחזיר את הגודל המתאים

int* ptr = malloc(sizeof(int));

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

int* ptr = malloc(sizeof(int));

בבתים:הטיפוס של המשתנה עבור הפעלה על משתנה יוחזר הגודל שלint* ptr = malloc(sizeof(*ptr)); // = sizeof(int)

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

char* str = "This is a string";char* copy = malloc(sizeof(char)*(strlen(str)+1));

למה השיטה הזו עדיפה?

, sizeof(char)אפשר להוריד את 1מובטח שהוא תמיד

למה צריך 1?+

Page 20: תרגול מס' 2

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

בדיקת ערכי חזרהmalloc עלולה להיכשל בהקצאת הזיכרון - במקרה זה מוחזר NULL מה קורה במקרה זה אםmalloc?נכשלת

int* my_array = malloc (sizeof(int) * n);for (int i=0; i<n; i++) {

my_array[i] = i;}

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

int* my_array = malloc(sizeof(int) * n);if (my_array == NULL) { // or !my_array

handle_memory_error();}

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

Page 21: תרגול מס' 2

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

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

void free(void* ptr); חייב להצביע לתחילת גוש הזיכרון )אותו ערך שהתקבל freeהמצביע שנשלח ל-–

(mallocמ-לאחר שחרור הזיכרון אסור לגשת יותר לערכים בזיכרון ששוחרר– לא מתבצע כלוםfree ל-NULLאם שולחים –

NULLכלומר אין צורך לבדוק את הפרמטר הנשלח ולוודא שאינו •למה זה טוב?•

מצביע שאינו מצביע freeאסור לשחרר את אותו זיכרון פעמיים או לשלוח ל-–(NULLלתחילת גוש זיכרון שהוקצה דינאמית )או

int* my_array = malloc(sizeof(int) * n);// ... using my_array ...free(my_array);

Page 22: תרגול מס' 2

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

מקרי קצה במקרה ונשלחNULL-ל freeלא מתבצע כלום

if (ptr != NULL) {free(ptr);

}:ניתן להחליף את הקוד הקודם בזה

free(ptr);NULL עבור מקרה קצה הואfree

לא היתה מתמודדת עם מקרה הקצה הזה?freeמה היה קורה אם –

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

Page 23: תרגול מס' 2

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

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

הערכים הצפויים.נותן תוצאות שאינן צפויות–

בחלק מהמקרים התוצאה שתוחזר אכן מתאימה לציפיות•

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

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

Page 24: תרגול מס' 2

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

התנהגות לא מוגדרת - דוגמה?האם שתי התכניות הבאות מתנהגות בצורה זהה

#include <stdio.h>#define N 7 int main() {

int i;int a[N] = {0};for (i=0; i < N; i+

+) {printf("%d\n", i);a[N-1-(i+1)] = a[i];}return 0;

}

#include <stdio.h>#define N 7 int main() {

int a[N] = {0};int i;for (i=0; i < N; i+

+) {printf("%d\n", i);a[N-1-(i+1)] = a[i];}return 0;

}

Page 25: תרגול מס' 2

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

דליפות זיכרון מתרחשת כאשר שוכחים לשחרר זיכרון שהוקצה:דליפת זיכרון

void sort(int* array, int n) {int* copy = malloc(sizeof(int) * n);// ... some code without free(copy)return;

}דליפת זיכרון אינה גורמת ישירות לשגיאות בהתנהגות התוכנה

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

תחתUNIX ניתן להשתמש בכלי valgrind לאיתור דליפות זיכרון–valgrindמריץ את התכנית שלכם ומחפש גושי זיכרון שהוקצו אך לא שוחררו 3 בתרגול עזר valgrindניתן למצוא מידע נוסף על השימוש ב-–

Page 26: תרגול מס' 2

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

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

עבודה מסודרתקיים רק פתרון אחד יעיל -

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

קוד מסובך מקל על הכנסת באגים בטעות

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

Page 27: תרגול מס' 2

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

הקצאת זיכרון דינאמית - סיכוםמומלץ לא להשתמש במשתנים גלובליים וסטטיים-ניתן להשתמש בmalloc-ו free כדי להקצות ולשחרר זיכרון בצורה

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

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

Page 28: תרגול מס' 2

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

מבניםהגדרת מבנהפעולות על מבניםtypedef

Page 29: תרגול מס' 2

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

הטיפוסים הקיימים אינם מספיקים נניח שברצוננו לכתוב תוכנה לניהול אנשי קשר, לכל איש קשר נשמור: שם

וכתובת מגורים.e-mailפרטי, שם משפחה, מספר טלפון, כתובת מערכים שונים5 לשם כך נצטרך לשמור ! 5כל פונקציה שתצטרך לקבל את פרטיו של איש קשר כלשהו תצטרך לקבל

!פרמטרים שונים לפחות

void someFunction(char* firstname, char* lastname, char* address, char* email, int number, ... more?);

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

void someFunction(Contact contact, ...);

Page 30: תרגול מס' 2

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

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

:structהמילה השמורה struct <name> {

<typename 1> <field name 1>;<typename 2> <field name 2>;...<typename n> <field name n>;

} <declarations>;שדותהטיפוס החדש מורכב מ:

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

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

Page 31: תרגול מס' 2

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

מבנים - דוגמאותstruct point {

double x;double y;

}; struct date {

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

}; struct person {

char* name;struct Date birth;

};

x=3.0

y=2.5

point

day=31month="NOV

"

date

year=1971

name=0x0ffef6

birth

person

"Ehud Banai"day=31

month="MAR"

year=1953

?4למה

כל המערך נשמר בתוך

המבנה

המחרוזת נשמרת מחוץ

למבנה

Page 32: תרגול מס' 2

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

שימוש במבנים הטיפוס החדש מוגדר בשםstruct <name> נקודה(.כדי לגשת לשדות של משתנה מטיפוס המבנה נשתמש באופרטור(

struct point p;p.x = 3.0;p.y = 2.5;double distance = sqrt(p.x * p.x + p.y * p.y);

עבור מצביע למבנה ניתן להשתמש באופרטור החץ-<struct point* p = malloc(sizeof(*p));(*p).x = 3.0; // Must use parentheses, annoyingp->y = 2.5; // Same thing, only clearerdouble distance = sqrt(p->x * p->x + p->y * p->y);

מה חסר?

Page 33: תרגול מס' 2

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

פעולות על מבנים:ניתן לאתחל מבנים בעזרת התחביר הבא

struct date d = { 31, "NOV", 1970 };

:ניתן לבצע השמה בין מבנים מאותו הטיפוסstruct date d1,d2;// ...d1 = d2;

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

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

מצביעים(

Page 34: תרגול מס' 2

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

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

נשתמש מסיבה זו וכדי למנוע העתקות כבדות ומיותרות של מבנים בדרך כללבמבנים ע"י מצביעים

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

name=0x0ffef6

birth

person1"Ehud Banai"

day=31

month="MAR"

year=1953

name=0x0ffed0

birth

person2"Yuval Banai"

day=9

month="JUN"

year=1962

Page 35: תרגול מס' 2

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

typedefהגדרת טיפוסים בעזרת המילה השמורהtypedef משמשת להגדרת טיפוסים חדשים ע"י נתינת שם חדש

לטיפוס קייםtypedef int length;

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

נשתמש בפקודתtypedef:כדי לתת שמות נוחים לטיפוסים typedef struct point Point;

(struct )ללא המילה השמורה Pointבמקרה זה נוכל להתייחס למבנה מעכשיו כ-–נוח לתת שם גם לטיפוס המצביע למבנה:–

typedef struct date Date, *pDate;עבור מבנים מסובכים נשתמש תמיד במצביעים ולכן במקרים האלו נשמור את השם –

ה"נוח" לטיפוס המצביע:typedef struct person *Person;

Page 36: תרגול מס' 2

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

typedefהגדרת טיפוסים בעזרת ניתן להוסיףtypedef:ישירות על הגדרת המבנה

typedef struct point {double x;double y;

} Point;

:ניתן להשמיט את שם הטיפוס בהגדרה ולהשאיר רק את השם החדשtypedef enum { RED, GREEN, BLUE } Color;typedef struct {

double x;double y;

} Point;

Page 37: תרגול מס' 2

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

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

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

-<.ניתן לגשת לשדות ע"י האופרטורים . ו- –העתקה והשמה של מבנים בטוחה כל עוד אין בהם מצביעים-מומלץ להשתמש בtypedefכדי לתת שם נוח לטיפוס החדש

Page 38: תרגול מס' 2

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

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

Page 39: תרגול מס' 2

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

Data typesטיפוסי נתונים – typedef struct date_t {

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

} Date; int main() {

Date d1 = {21, "NOV", 1970};Date d2;scanf("%d %3s %d", &d2.day, d2.month, &d2.year);printf("%d %s %d\n", d1.day, d1.month, d1.year);printf("%d %s %d\n", d2.day, d2.month, d2.year); // deja-vuif (d1.day == d2.day && strcmp(d1.month,d2.month) == 0 &&d1.year == d2.year) {printf("The dates are equal\n");}return 0;

}

אלו בעיות יש בקוד הזה?

Page 40: תרגול מס' 2

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

Data typesטיפוסי נתונים - מהרכבה של שני מספרים שלמים וארבעה תווים יותרתאריך הוא

לא כל צירוף של ערכים עבור המבנהDate תאריך חוקי הוא אכן–5 BLA 2010“-אין חודש מתאים ל - BLA”–31 SEP 1978 ימים30 - ב-ספטמבר יש רק –29 FEB 2010 ימים28 יש רק 2010 - בפברואר

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

Page 41: תרגול מס' 2

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

Data typesטיפוסי נתונים - כדי לוודא את נכונות השימוש בתאריכים ולמנוע את שכפולי הקוד

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

טיפוס נתונים -לצירוף של טיפוס והפעולות האפשריות עליו קוראיםData type

טיפוסי הנתונים המובנים בשפה נקראים טיפוסי נתונים פרימטיביים– ומצביעים )לכל אחד מהם פעולות שונות אפשריות(int, floatלמשל •

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

Page 42: תרגול מס' 2

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

טיפוס נתונים לתאריך#include <stdio.h>#include <string.h>#include <stdbool.h> typedef struct Date_t {

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

} Date;

const int MIN_DAY = 1; const int MAX_DAY = 31;const int INVALID_MONTH = 0;const int MIN_MONTH = 1;const int MAX_MONTH = 12;const int DAYS_IN_YEAR = 365;

const char* const months[] = { "JAN", "FEB", "MAR", " APR", "MAY", "JUN","JUL", "AUG", "SEP", "OCT", "NOV", "DEC" };

הגדרת קבועים

רק לקבצים includeמבצעים שהכרחיים לקמפול הקוד:

stdio.h עבור - printf-ו scanfstring.h עבור - strcmp

stdbool.h עבור הגדרת הטיפוס - bool

Page 43: תרגול מס' 2

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

טיפוס נתונים לתאריך/** writes the date to the standard output */void datePrint(Date date);

/** Reads a date from the standard input. * Returns true if valid, false otherwise */bool dateRead(Date* date); /** 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); /** Translates a month string to an integer */int monthToInt(char* month); /** Calculates the number of days since 01/01/0000 */int dateToDays(Date date); /** Checks if the date has valid values */bool dateIsValid(Date date);

מומלץ לתעד לפחות בקצרה את משמעות

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

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

הערות באמצע הקוד בד"כ מיותרות או מסבירות קוד שהיה צריך להיכתב ברור

יותר

Page 44: תרגול מס' 2

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

טיפוס נתונים לתאריך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;

} int dateToDays(Date date) {

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

} bool dateIsValid(Date date) {

return date.day >= MIN_DAY && date.day <= MAX_DAY &&monthToInt(date.month) != INVALID_MONTH;

}

Page 45: תרגול מס' 2

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

טיפוס נתונים לתאריךvoid datePrint(Date date) {

printf("%d %s %d\n", date.day, date.month, date.year);} bool dateRead(Date* date) {

if (date == NULL) {return false;

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

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

}return dateIsValid(*date);

}

יש לבדוק את תקינות הקלט בכניסה לפונקציה

במיוחד מצביעים!

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

לכתוב אותו ולא המבצעת אותומחדש!

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

לה!

Page 46: תרגול מס' 2

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

טיפוס נתונים לתאריך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;

}

Page 47: תרגול מס' 2

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

המעודכנתmainפונקצית ה-int main() {

Date date1 = { 21, "NOV", 1970 };Date date2;if(!dateRead(&date2)) {

printf("Invalid date\n");return 0;

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

printf("The dates are equal\n");} else {

int diff = dateDifference(date1,date2);printf("The dates are %d days apart\n", abs(diff));

}return 0;

}

Page 48: תרגול מס' 2

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

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

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

נפרדים

Page 49: תרגול מס' 2

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

mainהעברת פרמטרים ל- הפרמטריםargc-ו argvתכנית לדוגמה

Page 50: תרגול מס' 2

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

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

int main(int argc, char** argv)

במקרה זה יילקחו הארגומנטים משורת ההרצה של התכנית ויושמו לתוךע"י מערכת ההפעלה argv ו-argcהמשתנים

–argc )יאותחל למספר הארגומנטים בשורת הפקודה )כולל שם הפקודה–argv -הוא מערך של מחרוזות כאשר התא ה- בו יכיל את הארגומנט ה

בשורת הפקודהNULLבנוסף, קיים איבר אחרון נוסף במערך המאותחל ל-–

Page 51: תרגול מס' 2

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

echoדוגמה - תכנית #include <stdio.h> int main(int argc, char** argv) {

for(int i = 1; i < argc; i++) {printf("%s ", argv[i]);}return 0;

}

> ./echo Hello worldHello world> ./echo Hello > world> cat worldHello

כיצד ניתן לכתוב את הקוד הזה ללא שימוש במשתנה

argc?

לאן נעלמה ?worldהמילה

argv[0] /."echo"

"Hello"

"world"

argv[1]argv[2]argv[3]

argv3

argc

Page 52: תרגול מס' 2

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

טענות נכונותהערות התוך הקוד שימוש במאקרוassertכיבוי המאקרו-מתי משתמשים בassert

Page 53: תרגול מס' 2

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

הערות בתוך הקוד?מה הבעיה בקוד הזה

int main(int argc, char** argv) {if (argc > 3) {

...} else if ( argc < 2) {

...} else {

// if we are here argc is 2...

}}

Page 54: תרגול מס' 2

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

assertהמאקרו המאקרוassert:משמש לוידוא טענות

assert(<expression>); המאקרו מוגדר בקובץ המנשקassert.h ועל מנת להשתמש בו יש לעשות #include.בזמן ריצת הקוד הביטוי מוערך ונבדק

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

-נשתמש בassert.כדי להגן על הקוד מפני הכנסת באגים // if we are here argc is 2 assert(argc == 2);

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

> ./prog a> ./prog a bprog: main.c:12: main: Assertion `n==3' failed.Abort

Page 55: תרגול מס' 2

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

כיבוי המאקרו ניתן לכבות את המאקרוassert ע"י הגדרת הקבוע NDEBUG

#define NDEBUG מוגדר המאקרו יוחלף בקוד שאינו עושה כלוםNDEBUGאם –כך ניתן לשחרר גרסה סופית של הקוד שאינה מואטת ע"י הבדיקות ללא הסרתן –

ידניתDNDEBUG- ישירות משורת ההידור ע"י הוספת הדגל NDEBUGניתן להגדיר את –

< מוסיף בתחילת כל קובץ הגדרה של המאקרו בשם D<stringהדגל -•<string>

:קוד שבתוך המאקרו לא יורץ כלל אם המאקרו כבוישימו לב.assertאסור לשים חישוביים הכרחיים לקוד בתוך –

?מה הבעיה כאן? מה הפתרוןassert(doSomethingImportant() != FAILED);

Page 56: תרגול מס' 2

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

assertמתי משתמשים ב--בassert הנעשות בקודנכונות של הנחות משתמשים לוידוא

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

נתונים

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

int getInput() {int input;printf("Enter a positive number:");scanf("%d",&input);assert(input > 0);return input;

}

כאן?assertמה הבעיה ב-מה צריך לעשות במקום?

Page 57: תרגול מס' 2

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

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

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

מתאים עבור שגיאות אחרות