תרגול מס' 2
description
Transcript of תרגול מס' 2
2תרגול מס' מצביעיםהקצאת זיכרון דינאמית - מבניםStructures - טיפוסי נתוניםData types-העברת פרמטרים לmainטענות נכונות
2מבוא לתכנות מערכות - 234122
מצביעיםשימוש בסיסיאריתמטיקת מצביעיםvoid*מצביע למצביע
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“מחרוזת המורכבת
ממספר תווים
ערך הבית כתובת
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
5מבוא לתכנות מערכות - 234122
0x0 - NULLהכתובת הינה כתובת לא חוקית:0הכתובת
אף עצם אינו יכול להיות בעל כתובת זו–ניתן לעשות שימוש בכתובת זו כדי לציין שמצביע מסוים אינו מצביע לאף עצם כרגע-השתמשו בNULL 0 כאשר אתם מתייחסים לכתובת ולא בקבוע
שימוש בקבועים כאלו משפר את קריאות הקוד– נסיון לקרוא מצביע המכיל את הכתובתNULL לקריסת התוכנה יגרום.
תתקבל ההודעה:UNIXב-– “segmentation fault”
גישה למצביע המכיל "זבל" תגרום לתכניתלהתנהג בצורה לא צפויה
בקוד!מצביעים לא מאותחלים אסור להשאיר –(C99ניתן להכריז על המשתנה מאוחר יותר )•NULLבמקרה ולא ניתן יש לאתחל אותו ל-•
6מבוא לתכנות מערכות - 234122
אריתמטיקת מצביעים על מצביעיםפעולות חשבוניות ניתן בנוסף לבצע
:חיבור מספרים שלמים–int n = ...;int* ptr2 = ptr + n;
תאים קדימה/אחורהnהתוצאה היא כתובתו של המשתנה מטיפוס מתאים •:חיסור שני מצביעים–
int diff = ptr2 - ptr;( התוצאה היא מספר שלםint)
פעולות אלו מאפשרות להסתכל על המשתנה הבא/הקודם בזיכרוןהכרחי לשימוש במערכים ומחרוזות– – טעויות חשבוניות עלולות לגרום לקריאת "זבל" מהזיכרוןמסוכן–
:מדוע לא ניתן לחבר שני מצביעים?שאלה
7מבוא לתכנות מערכות - 234122
מצביעים – גישה למערך ,ניתן להשתמש במצביע כדי לגשת למשתנים הנמצאים בהמשך בזיכרון
למשל כך:int* ptr;... =
int n = *(ptr + 5);
:האופרטור ] [ משמש כקיצור לפעולה זוint n = ptr[5];
:כלומר הפעולות הבאות שקולות*(ptr + n) ≡ ptr[n]
8מבוא לתכנות מערכות - 234122
מצביעים ומערכיםמערכים ומצביעים מתנהגים בצורה דומה
ניתן להשתמש בשם המערך כמצביע לאיבר הראשון בו–כאשר שולחים מערך לפונקציה ניתן לשלוח אותו כמצביע:–
void sort(int* array, int size);עבור מערךאיטרטורמצביע יכול לשמש כ
int array[N];
...//
for(int* ptr = array; ptr < array+N; ptr++){
printf("%d ",*ptr);
}
:הבדליםהכרזה על מצביע אינה מקצה זיכרון הכרזה על מערך מקצה זיכרון כגודל המערך, –
!לאחסון המשתניםניתן לשנות את ערכו של מצביע, אך לא ניתן לשנות את "ערכו" של תחילת המערך–
ניתן C99ב-להכריז על
משתנה בתוך forלולאת
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
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;
}מדוע יש כאן צורך במצביע למצביע?•
11מבוא לתכנות מערכות - 234122
מצביעים - סיכוםמצביעים משמשים להתייחסות לתאי זיכרון ניתן לקבל את כתובתו של משתנה ע"י אופרטור& ניתן לקרוא ממצביע ולקבל את הערך המוצבע ע"י* הערךNULL מציין שאין עצם מוצבע ואסור לקרוא אותו על מצביעיםפעולות חשבוניות ניתן לבצע
מאפשר התייחסות למצביעים בדומה למערכים– מצביעיםלאתחל חשוב
הרצת קוד הניגש למצביעים המכילים ערך לא תקין תגרום להתנהגות לא –מוגדרת
הכרזה על מצביע אינה מאתחלת זיכרון עבור המשתנה המוצבע!– מצביע מטיפוסvoid * יכול להצביע לעצם מכל סוג ומשמש לכתיבת קוד
גנרי
12מבוא לתכנות מערכות - 234122
הקצאת זיכרון דינאמיתסוגי משתניםהקצאת זיכרוןשחרור זיכרוןנזילות זיכרון
13מבוא לתכנות מערכות - 234122
סוגי משתנים:את המשתנים השונים בקוד ניתן לסווג לפי טווח ההכרה ואורך חייהם
: משתנים פנימיים של פונקציות. נגישים רק בבלוק בו הם משתנים מקומיים–הוגדרו. משתנים אלו מוקצים בכל פעם שהבלוק מורץ ומשוחררים בסופו.
: משתנים אשר מוגדרים לכל אורך התכנית וניתן לגשת משתנים גלובליים–אליהם מכל מקום. המשתנים מוקצים כאשר התכנית מתחילה ונשמרים לכל
אורך זמן הריצה: משתנים פנימיים של פונקציה. משתנים אלו משתנים סטטיים של פונקציה–
שומרים על ערכם בין הקריאות השונות לפונקציה. מאותחלים בריצה הראשונה של הפונקציה, משוחררים בסוף ריצת התכנית
: מוקצים ומשוחררים ע"י קריאה מפורשת לפונקציהמשתנים דינאמיים–
14מבוא לתכנות מערכות - 234122
הרוע של משתנים גלובליים משתנים גלובליים, משתנים סטטיים של קובץ ומשתנים סטטיים של
תכנות רעפונקציה נחשבים ל הסיבה העיקרית לכך - שימוש במשתנים אלו מקשה על הבנת ודיבוג
הקוד:כדי להבין פונקציה המשתמשת במשתנה גלובלי יש להסתכל בקוד נוסף–קשה לצפות את תוצאת הפונקציה כי היא אינה תלויה רק בפרמטרים שלה–קשה לצפות השלכות של שינויים על ערך המשתנה–בשימוש במשתנה סטטי של פונקציה - בשביל לצפות את תוצאת הפונקציה –
צריך לדעת מה קרה בהרצות קודמותאין להשתמש במשתנים גלובליים במת"מ
בקורסים מתקדמים בהמשך התואר תראו מקרים בהם חובה או מומלץ –להשתמש במשתנים כאלו
15מבוא לתכנות מערכות - 234122
משתנים דינאמיים הם משתנים שזמן החיים שלהם הוא בשליטת משתנים דינאמיים
המתכנתקוד מפורש מקצה אותם וקוד מפורש דרוש לשחרורם–heapהמשתנים מוקצים באזור זיכרון שקרוי ה-–
stackבניגוד למשתנים מקומיים המוקצים על מחסנית הקריאות, ה-•
:משתמשים בהם כאשרצריך ליצור מערך שגודלו אינו ידוע מראש–יש צורך לשמור נתונים בזיכרון גם לאחר יציאה מהפונקציה–
הגישה למשתנים אלו נעשית תמיד בעזרת מצביעים
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;}
17מבוא לתכנות מערכות - 234122
קביעת הגודל אותו מקצים כיצר נדע כמה בתים עלינו להקצות עבור משתנה מסוגint? :נסיון ראשון
int* ptr = malloc(4);4 1 או 0- מספר לא ברור המופיע בקוד שאינו מספר קסם הוא הרגל תכנותי רעמספרי קסם הם:
פוגעים בקריאות הקוד•מקשים על שינויים עתידיים בקוד•
8 הוא intלמשל מעבר לסביבה בה גודלו של –ככל שצריך יותר שינויים הסיכוי לפספס אחד מהם גדל–
:יש להימנע ממספרי קסם שיקלו על שינויים ועל קריאת הקודdefine בעזרת #קבועיםע"י הגדרת –ע"י שמירת ערכם במשתנה קבוע בעל שם ברור–
18מבוא לתכנות מערכות - 234122
קביעת הגודל - נסיון שני נסיון שני - נגדיר את הגודל שלint:כקבוע
#define SIZE_OF_INT 4
int* ptr = malloc(SIZE_OF_INT);
עכשיו הקוד קריא וקל לשנות את הערךאבל אם נעביר את הקוד לסביבה אחרת עדיין נצטרך לעדכן את הערך
non-portableקוד שדורש שינויים במעבר בין סביבות שונות נקרא –
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?+
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();}
בהמשך נראה מקרים נוספים של שגיאות יותר שכיחות ופשוטות להתמודדות
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);
22מבוא לתכנות מערכות - 234122
מקרי קצה במקרה ונשלחNULL-ל freeלא מתבצע כלום
if (ptr != NULL) {free(ptr);
}:ניתן להחליף את הקוד הקודם בזה
free(ptr);NULL עבור מקרה קצה הואfree
לא היתה מתמודדת עם מקרה הקצה הזה?freeמה היה קורה אם –
לטפל במקרי קצה בתוך הפונקציהעדיףבאגים ושכפולי קודמונע מהמשתמש בה ליצור –
23מבוא לתכנות מערכות - 234122
גישה לזיכרון אחרי ששוחרר )אינה מוגדרתגישה לכתובת זיכרון שאינה מוקצה )או הוקצתה ושוחררה אינו מוגדרשחרור כפול של כתובת זיכרון קוד שתוצאתו אינה מוגדרת הוא קוד שמתקמפל ורץ אך אינו מחשב את
הערכים הצפויים.נותן תוצאות שאינן צפויות–
בחלק מהמקרים התוצאה שתוחזר אכן מתאימה לציפיות•
קוד שאינו מוגדר הוא באג קשה לטיפולקשה לצפות את התנהגותו והשלכותיו –יכול להשפיע על משתנים באזור אחד בקוד–
חשוב להקפיד על שימוש נכון בשפה כדי להימנע ממקרים אלו
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;
}
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ניתן למצוא מידע נוסף על השימוש ב-–
26מבוא לתכנות מערכות - 234122
איך מתמודדים עם כל הקשיים? כדי להימנע מכל הבעיות שתוארו כאשר עובדים עם הקצאות דינאמיות
עבודה מסודרתקיים רק פתרון אחד יעיל -
בעזרת עבודה מסודרת ניתן לשמור על הקוד פשוט יותר
קוד מסובך מקל על הכנסת באגים בטעות
הטיפול בבאגים קשה יותר אם הקוד מסובך
27מבוא לתכנות מערכות - 234122
הקצאת זיכרון דינאמית - סיכוםמומלץ לא להשתמש במשתנים גלובליים וסטטיים-ניתן להשתמש בmalloc-ו free כדי להקצות ולשחרר זיכרון בצורה
מפורשתעבור יצירת מערכים בגודל לא ידוע–עבור שמירת ערכים לאורך התכנית–
ניהול הזיכרון מתבצע ע"י מצביעים לתחילת גוש הזיכרון שהוקצהיש לבדוק הצלחת הקצאת זיכרוןיש לזכור לשחרר את הזיכרון המוקצה כאשר אין בו צורך יותר-ניתן להשתמש בvalgrindכדי למצוא בקלות גישות לא מוגדרות לזיכרון
28מבוא לתכנות מערכות - 234122
מבניםהגדרת מבנהפעולות על מבניםtypedef
29מבוא לתכנות מערכות - 234122
הטיפוסים הקיימים אינם מספיקים נניח שברצוננו לכתוב תוכנה לניהול אנשי קשר, לכל איש קשר נשמור: שם
וכתובת מגורים.e-mailפרטי, שם משפחה, מספר טלפון, כתובת מערכים שונים5 לשם כך נצטרך לשמור ! 5כל פונקציה שתצטרך לקבל את פרטיו של איש קשר כלשהו תצטרך לקבל
!פרמטרים שונים לפחות
void someFunction(char* firstname, char* lastname, char* address, char* email, int number, ... more?);
המהווים הרכבה של טיפוסים חדשים כדי להימנע מריבוי משתנים ניתן להגדירמספר טיפוסים קיימים
void someFunction(Contact contact, ...);
30מבוא לתכנות מערכות - 234122
Structuresמבנים - ניתן להגדיר טיפוסים חדשים המהווים הרכבה של מספר טיפוסים קיימים בעזרת
:structהמילה השמורה struct <name> {
<typename 1> <field name 1>;<typename 2> <field name 2>;...<typename n> <field name n>;
} <declarations>;שדותהטיפוס החדש מורכב מ:
לכל שדה יש שם–טיפוס השדה נקבע לפי הגדרת המבנה–
המבנים נשמרים בזיכרון ברצףניתן להשתמש במערכים בעלי גודל קבוע כשדות - כל המערך נשמר במבנהניתן להשתמש במצביעים כשדות - במקרה זה הערך המוצבע אינו חלק מהמבנה
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למה
כל המערך נשמר בתוך
המבנה
המחרוזת נשמרת מחוץ
למבנה
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);
מה חסר?
33מבוא לתכנות מערכות - 234122
פעולות על מבנים:ניתן לאתחל מבנים בעזרת התחביר הבא
struct date d = { 31, "NOV", 1970 };
:ניתן לבצע השמה בין מבנים מאותו הטיפוסstruct date d1,d2;// ...d1 = d2;
במקרה זה מתבצעת השמה בין כל שני שדות תואמים– מבנים מועברים ומוחזרים מפונקציותby valueכלומר מועתקים –
גם במקרה זה מתבצעת ההעתקה שדה-שדה– הפעולות האלו אינן מתאימות למבנים מסובכים יותר )בד"כ בגלל
מצביעים(
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
35מבוא לתכנות מערכות - 234122
typedefהגדרת טיפוסים בעזרת המילה השמורהtypedef משמשת להגדרת טיפוסים חדשים ע"י נתינת שם חדש
לטיפוס קייםtypedef int length;
פקודתtypedef עובדת על שורת הכרזה של משתנה – אך מגדירה טיפוס חדש במקום משתנה.
נשתמש בפקודתtypedef:כדי לתת שמות נוחים לטיפוסים typedef struct point Point;
(struct )ללא המילה השמורה Pointבמקרה זה נוכל להתייחס למבנה מעכשיו כ-–נוח לתת שם גם לטיפוס המצביע למבנה:–
typedef struct date Date, *pDate;עבור מבנים מסובכים נשתמש תמיד במצביעים ולכן במקרים האלו נשמור את השם –
ה"נוח" לטיפוס המצביע:typedef struct person *Person;
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;
37מבוא לתכנות מערכות - 234122
מבנים - סיכום מבנים מאפשרים הרכבה של מספר טיפוסים קיימים כדי להקל על
קריאות הקודמבנה מורכב משדות בעלי שם
-<.ניתן לגשת לשדות ע"י האופרטורים . ו- –העתקה והשמה של מבנים בטוחה כל עוד אין בהם מצביעים-מומלץ להשתמש בtypedefכדי לתת שם נוח לטיפוס החדש
38מבוא לתכנות מערכות - 234122
טיפוסי נתונים
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;
}
אלו בעיות יש בקוד הזה?
40מבוא לתכנות מערכות - 234122
Data typesטיפוסי נתונים - מהרכבה של שני מספרים שלמים וארבעה תווים יותרתאריך הוא
לא כל צירוף של ערכים עבור המבנהDate תאריך חוקי הוא אכן–5 BLA 2010“-אין חודש מתאים ל - BLA”–31 SEP 1978 ימים30 - ב-ספטמבר יש רק –29 FEB 2010 ימים28 יש רק 2010 - בפברואר
בצורות מסוימותמי שמשתמש במבנה התאריך צפוי להשתמש בוהדפסת תאריך–מציאת התאריך המוקדם יותר מבין שני תאריכים–מציאת מספר הימים בין שני תאריכים–
41מבוא לתכנות מערכות - 234122
Data typesטיפוסי נתונים - כדי לוודא את נכונות השימוש בתאריכים ולמנוע את שכפולי הקוד
בשימוש בתאריכים עלינו לכתוב פונקציות מתאימות לטיפול בתאריכים
טיפוס נתונים -לצירוף של טיפוס והפעולות האפשריות עליו קוראיםData type
טיפוסי הנתונים המובנים בשפה נקראים טיפוסי נתונים פרימטיביים– ומצביעים )לכל אחד מהם פעולות שונות אפשריות(int, floatלמשל •
יצירת טיפוסי נתונים מהווה את הבסיס לכתיבת תוכנה גדולה בצורה מסודרת –ופשוטה
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
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);
מומלץ לתעד לפחות בקצרה את משמעות
הפונקציות מעל הכרזתן
תיעוד צריך להופיע מעל הפונקציה ולא בתוכה
הערות באמצע הקוד בד"כ מיותרות או מסבירות קוד שהיה צריך להיכתב ברור
יותר
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;
}
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);
}
יש לבדוק את תקינות הקלט בכניסה לפונקציה
במיוחד מצביעים!
, אם קוד כלשהו משכפול קודהמנעו לקרוא לפונקציה כבר נכתב הקפידו
לכתוב אותו ולא המבצעת אותומחדש!
וקוד חוזר על אם אין פונקציה מתאימה ולקרוא לכתוב פונקצית עזר - יש עצמו
לה!
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;
}
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;
}
48מבוא לתכנות מערכות - 234122
טיפוסי נתונים - סיכוםכאשר מגדירים טיפוס חדש יש להגדיר גם פונקציות מתאימות עבורויש להגדיר פונקציות עבור הפעולות הבסיסיות שיצטרך המשתמש בטיפוס יש להגדיר פונקציות כך שתשמורנה על ערכים חוקיים של הטיפוס
ותמנענה באגים יצירת טיפוסי נתונים מאפשרת דרך נוחה לחלוקת תוכנה גדולה לחלקים
נפרדים
49מבוא לתכנות מערכות - 234122
mainהעברת פרמטרים ל- הפרמטריםargc-ו argvתכנית לדוגמה
50מבוא לתכנות מערכות - 234122
mainהעברת פרמטרים ל- את הפונקציהmain:המתחילה את ריצת התכנית ניתן להגדיר גם כך
int main(int argc, char** argv)
במקרה זה יילקחו הארגומנטים משורת ההרצה של התכנית ויושמו לתוךע"י מערכת ההפעלה argv ו-argcהמשתנים
–argc )יאותחל למספר הארגומנטים בשורת הפקודה )כולל שם הפקודה–argv -הוא מערך של מחרוזות כאשר התא ה- בו יכיל את הארגומנט ה
בשורת הפקודהNULLבנוסף, קיים איבר אחרון נוסף במערך המאותחל ל-–
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
52מבוא לתכנות מערכות - 234122
טענות נכונותהערות התוך הקוד שימוש במאקרוassertכיבוי המאקרו-מתי משתמשים בassert
53מבוא לתכנות מערכות - 234122
הערות בתוך הקוד?מה הבעיה בקוד הזה
int main(int argc, char** argv) {if (argc > 3) {
...} else if ( argc < 2) {
...} else {
// if we are here argc is 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
55מבוא לתכנות מערכות - 234122
כיבוי המאקרו ניתן לכבות את המאקרוassert ע"י הגדרת הקבוע NDEBUG
#define NDEBUG מוגדר המאקרו יוחלף בקוד שאינו עושה כלוםNDEBUGאם –כך ניתן לשחרר גרסה סופית של הקוד שאינה מואטת ע"י הבדיקות ללא הסרתן –
ידניתDNDEBUG- ישירות משורת ההידור ע"י הוספת הדגל NDEBUGניתן להגדיר את –
< מוסיף בתחילת כל קובץ הגדרה של המאקרו בשם D<stringהדגל -•<string>
:קוד שבתוך המאקרו לא יורץ כלל אם המאקרו כבוישימו לב.assertאסור לשים חישוביים הכרחיים לקוד בתוך –
?מה הבעיה כאן? מה הפתרוןassert(doSomethingImportant() != FAILED);
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מה הבעיה ב-מה צריך לעשות במקום?
57מבוא לתכנות מערכות - 234122
טענות נכונות - סיכום ניתן להשתמש במאקרוassertכדי לוודא קיום תנאים בתכנית -מומלץ להשתמש בassertכדי להקל על דיבוג התכנית ניתן לכבות בקלות את התנהגות המאקרו בגרסאות סופיות בעזרת
NDEBUGהגדרת אסור לשים חישובים הכרחיים בתוךassert-השימוש בassert מתאים רק עבור מציאת באגים של המתכנת ואינו
מתאים עבור שגיאות אחרות