Download presentation
Presentation is loading. Please wait.
1
תירגול 8:מצביעים והקצאה דינאמית
מבוא למדעי המחשב מ' - תירגול 2
2
מבוא למדעי המחשב מ' - תירגול 3
תוכנייה מצביעים מצביעים ומערכים הקצאה דינמית מבוא למדעי המחשב מ' - תירגול 3
3
מבוא למדעי המחשב מ' - תירגול 2
מצביעים מבוא למדעי המחשב מ' - תירגול 2
4
תזכורת- כתובות זיכרון הזיכרון במחשב מחולק לתאים, כל אחד בגודל בית אחד.
לכל תא בזיכרון יש כתובת כתובת התא תא 10 #1000 ערך השמור בתא #1004 -4 #1008 לא מאותחל #1012 67 הזיכרון במחשב מחולק לתאים, כל אחד בגודל בית אחד. תאי הזיכרון ממוספרים. מס' התא נקרא גם הכתובת של התא.
5
תזכורת- כתובות זיכרון לכל תוכנית מוקצה איזור בזיכרון בו היא יכולה לאכסן את נתוניה. אוסף כתובות זה נקרא מרחב הכתובות של התוכנית כל משתנה מאוחסן באחד או יותר תאי זיכרון רצופים. כתובת המשתנה היא הכתובת של התא הראשון שמכיל אותו. כדי לקרוא מהזיכרון על התוכנית לציין את הכתובת הרצויה ואת מס' הבתים שיש לקרוא (מס' הבתים שהמשתנה תופס)
6
מצביעים מצביעים הם משתנים המחזיקים כתובות זיכרון של משתנים אחרים.
הצהרה על מצביע: מיקום הכוכבית לא משנה להגדרה הצהרה על מצביע לint int* iptr; double *dptr; char* cptr, c; char *cptr, c; הצהרה על מצביע לdouble צריך להסביר שה מה הטיפוס של המשתנה cptr? מה הטיפוס של c? על מנת למנוע בלבול נכתוב את השורה כך, עם * ליד המשתנה שהוא מצביע
7
אופרטור & אופרטור & מחזיר את כתובתו של משתנה: דוגמא: &var_name d 6.4 a
int* iptr; double* dptr; int a = 3; double d = 6.4; iptr = &a; dptr = &d; 2000 2008 2012 2016 d a 6.4 3 dptr iptr 2000 2004
8
מצביעים int* iptr; double* dptr; int a = 3; double d = 6.4; iptr = &a; dptr = &d; שימו לב! מצביעים הם משתנים לכל דבר, ולכן הם מאוחסנים במחסנית כמו כל משתנה אחר. כמות הבתים הדרושה לאחסון כתובת זיכרון היא זהה לכל סוגי המצביעים ללא תלות בטיפוס עליו מצביעים 2000 2008 2012 2016 d a 6.4 3 כמות הבתים הדרושה לאחסון כתובת זיכרון תלויה בארכיטקטורה של המחשב עליו רצה התוכנית. ברוב המחשבים כיום מצביע תופס 8 בתים (מערכות 64 ביט). dptr iptr 2000 2004
9
אופרטור * בהינתן מצביע אופרטור * מחזיר את המשתנה שעליו הוא מצביע:
דוגמא: מצביע למשתנה מטיפוס int *ptr_name int a = 3, b = 5; int* ptr; ptr = &a; *ptr = 8; b = *ptr + 3; הטיפוס של ptr הוא int* ptr 2008 ??? b a 11 5 8 3 *ptrמתנהג כמו משתנה מטיפוס int
10
מתי נשתמש במצביעים? מתי נעביר מצביעים כפרמטר לפונקציה?
נעביר כתובת של משתנה לפונקציה כאשר נרצה לשנות את ערכו של המשתנה- כך אנו מקנים לפונקציה גישה ישירה לזיכרון המשתנה דוגמא: (שראינו בהרצאה) ב-C לכל פונקציה ערך החזרה אחד. אם נרצה פונקציה שמחזירה יותר מערך החזרה אחד, נממש זאת באמצעות מצביעים. דוגמא: פונקציה עם החתימה: יכולה להחזיר 2 ערכים. ערכו של אחד מהם יוחזר כערכו של המשתנה res. void swap(int* x, int* y) int foo(int x, int* res)
11
מתי נשתמש במצביעים? (המשך)
בשפת C ניתן להגדיר טיפוסים התופסים זיכרון רב (למשל מערכים). במקרה זה העברתם לפונקציה by value (כלומר ע"י העתקה) תהיה פעולה כבדה מאוד. ניתן לחסוך את עבודת ההעתקה על ידי העברת הכתובת של משתנים מטיפוסים כאלה לפונקציה, במקום את התוכן. שימו לב! העברת מצביע לפונקציה נעשית תמיד by value, כלומר ערך המצביע מועתק למשתנה פנימי של הפונקציה
12
מה היה קורה אם לא היינו משתמשים במצביעים?
Swap (תזכורת) בהרצאה ראינו פונקציה המחליפה בין התוכן של שני מספרים מטיפוס int: נשתמש בפונקציה כך: void swap(int* p, int* q) { int tmp = *p; *p = *q; *q = tmp; } מה היה קורה אם לא היינו משתמשים במצביעים? int a = 3, b = 7; swap(&a, &b); printf("a=%d, b=%d", a, b); a=7, b=3
13
מצביעים ופונקציות תרגיל 1: כתבו פונקציה המקבלת כקלט אורך רדיוס של מעגל (לא בהכרח שלם) ומחזירה את שטח המעגל והיקפו #include <stdio.h> #define PI 3.14 void calc_perimeter_and_area(double radius, double* p_perim, double* p_area) { *p_perim = 2 * PI * radius; *p_area = PI * (radius * radius); }
14
מצביעים ופונקציות תרגיל 2: כתבו פונקציה המקבלת 3 מצביעים a, b, ו-c, למספרים שלמים, ומחליפה את תוכנם כך שיהיו מסודרים בסדר עולה
15
מצביעים ופונקציות #include <stdio.h>
void swap_int(int* p, int* q) { int tmp = *p; *p = *q; *q = tmp; } void sort3(int* a, int* b, int* c) if (*a > *b) swap_int(a, b); if (*a > *c) swap_int(a, c); if (*b > *c) swap_int(b, c);
16
כתובת 0 והקבוע NULL כאשר משתני רגילים אינם מאוחלים הם מכילים זבל.
מה קורה כאשר ניגשים לפוינטרים לא מאותחלים? מצביע לא מאותחל מצביע למקום שרירותי בזיכרון char *ptr; *ptr=‘A’;
17
כתובת 0 והקבוע NULL הכתובת 0 הינה כתובת מיוחדת, שאינה כתובת חוקית בזיכרון, ומשמשת לאתחול מצביע כאשר רוצים לציין שהוא אינו מאותחל לזיכרון חוקי כלשהו. במקום הערך 0, יש להשתמש בקבוע NULL – זהו #define שמוגדר אוטומטית כאפס. מנגנון זה מאפשר לבדוק את חוקיותו של כל מצביע לפני השימוש בו: if (p != NULL) { *p = … } else { printf("p is unallocated!\n"); יש לאתחל ל-NULL כל מצביע שאינו מאותחל לזיכרון חוקי, וכן לשים NULL במצביע במידה והזיכרון שאליו הוא מצביע מפסיק להיות מוקצה
18
כתובת 0 והקבוע NULL ניסיון לגשת לתוכן של מצביע NULL גורר מיידית שגיאת זמן ריצה: int *p = NULL; *p = 3;
19
מצביעים- סיכום ביניים מצביעים מאפשרים גישה ישירה לזיכרון של משתנה
שימושים: שינוי הערך של משתנה בתוך פונקציה ריבוי ערכי החזרה מפונקציה העברת משתנים גדולים לפונקציה בלי לבצע העתקה טיפוסים שונים למצביעים על פי סוג המידע שהם מצביעים עליו int*, char*, float*, etc. void* אתחול מצביעים ל-NULL (כתובת 0) אם אינם מצביעים לכתובת חוקית
20
מערכים ומצביעים אריתמטיקה של מצביעים
מבוא למדעי המחשב מ' - תירגול 2
21
מערכים כפרמטר לפונקציה
#define N 3 int salaries[N] = {3000, 8500, 5500}; printf(“Old salaries:\n”); for (i=0; i<N; ++i) { printf(“%d\n", salary_array[i]); } cutSalaries(salaries, N); printf(“New salaries:\n”); חידה: מה ידפיס קטע הקוד הבא? Old salaries: 3000 8500 5500 New salaries: 1500 4250 2750 void cutSalaries (int salary_array[], int len) { for (i=0; i<len; ++i) { salary_array[i] /= 2; }
22
מערכים כפרמטר לפונקציה
הפונקציה שינתה את תוכן המערך! ב-C מעבירים משתנים by value, אז איך ייתכן שהמערך שונה והערך החדש מופיע מחוץ לפונקציה בה התבצע השינוי? כמו כן, למה היינו צריכים להעביר את אורך המערך לפונקציה? בשקפים הבאים נסביר מה קורה מאחורי הקלעים כאשר מעבירים מערך לפונקציה. לשם כך נציג אריתמטיקה של מצביעים.
23
מערכים בזיכרון לפני שנסביר איך משנים מערכים בתוך פונקציה, נרצה לדעת בתור התחלה איך שמורים מערכים בזיכרון: מערך יישמר ברצף של תאים בזיכרון. לדוגמא מערך של int: מטריצה או מערך דו מימדי שמורים בזיכרון גם כן ברצף, שורה אחרי שורה. לדוגמא במערך דו מימדי של char: מס' התא int a = {1,-3,5,7,8,4} #1000 #1004 #1008 #1012 #1016 #1020 1 -3 5 7 8 4 char b = {{‘a’, ‘b’, ‘c’}, {‘d’, ‘e’, ‘f’}, {‘g’, ‘h’, ‘i’}}; #100 #101 #102 #103 #104 #105 #106 #107 #108 a b c d e f g h i
24
אריתמטיקה של מצביעים אריתמטיקת מצביעים מאפשרת לקדם מצביעים, וכן לחבר אליהם מספרים שלמים. ניתן לעשות שימוש בכל האופרטורים החיבוריים: האריתמטיקה מוגדרת כך: כל תוספת/הפחתה של 1 משנה את הכתובת שרשומה במצביע במספר בתים כגודל הטיפוס אליו מצביעים. ניתן לחסר שני מצביעים זה מזה, רק אם הם מאותו טיפוס. התוצאה מומרת למספר שלם (int) המחזיק את המרחק ביניהם בזיכרון. אסור לחבר/להכפיל/לחלק שני מצביעים או להכפיל/לחלק מצביע בקבוע. + - += -= ++ --
25
long *lptr; short *sptr;
אריתמטיקה של מצביעים משתמשים באריתמטיקת מצביעים כאשר יש לנו בזיכרון מספר איברים רצופים מאותו הטיפוס (למשל במערך). בהינתן מצביע לאחד מהאברים בזיכרון, הוספת 1 אליו תקדם אותנו לאיבר הבא בזיכרון, ואילו הפחתת 1 תחזיר אותנו לאיבר הקודם. long *lptr; short *sptr; lptr lptr+1 lptr+2 lptr+3 --- long --- short --- long --- short sptr sptr+1 sptr+2 sptr+3 sptr+4 sptr+5 sptr+6 sptr+7 sptr sptr+1 sptr+2 sptr+3 sptr+4 sptr+5 sptr+6 sptr+7
26
השוואה בין מצביעים בשפת C, ניתן להשוות בין שני מצביעים על ידי השוואת הכתובות שהם מכילים. מצביע ptr1 הוא קטן ממצביע ptr2 אם הוא מכיל כתובת מוקדמת יותר בזיכרון. השוואה בין מצביעים מועילה, למשל, כאשר יש לנו שני מצביעים לאותו המערך, ואנו רוצים לדעת איזה מהם מצביע למקום קודם במערך. במקרה זה, אם מצביע אחד קטן מהשני – סימן שהוא מצביע לתא מוקדם יותר במערך. ניתן להשתמש בכל אחד מן האופרטורים הבאים להשוואת מצביעים: > < >= <= == !=
27
מערכים כמצביעים בשפת C, כתיבת שמו של מערך כלשהו ללא האופרטור [] מחזירה אוטומטית מצביע לאיבר הראשון במערך. אם נניח שטיפוס המערך הוא T[N], אזי טיפוס המצביע המוחזר יהיה T*. int speeds[] = { 25, 50, 80, 90, 110 }; printf("%p", &speeds[0]); printf("%p", speeds); דגל להדפסת מצביע הביטויים שקולים הביטויים האלה שקולים
28
מערכים כמצביעים חידה: איך נפנה לאיבר ה-3 במערך speeds?
תזכורת: איברי המערך נמצאים בזיכרון בצורה רציפה. כיוון שיש לנו מצביע לתחילת המערך, ניתן באמצעות אריתמטיקה של מצביעים להגיע לאיבר הרביעי כך: int speeds[] = { 25, 50, 80, 90, 110 }; printf("%p", speeds+3); *(speeds+3) += 10; נדפיס את כתובת האיבר השלישי נשנה את תוכנו של האיבר השלישי
29
מערכים כמצביעים עבור מערך בשם a &a[0] a &a[i] a+i a[i] *(a+i) speeds[]
כתיב מצביעים כתיב מערכים 25 50 80 90 110 &a[0] a speeds &a[i] a+i &speeds[0] speeds + 2 *(speeds+1) a[i] *(a+i) &speeds[2] speeds[1]
30
מערכים כמצביעים תרגיל 3: כתבו פונקציה המקבלת מערך שלמים ומצביע לאיבר האחרון שבו ומחזירה את אורכו. int get_array_length(int a[], int *p_last) { return p_last - a + 1; }
31
מצביעים לעומת מערכים- השוואה
אילו הבדלים מהותיים בין מצביע למערך? שמו של מערך הוא קבוע, כלומר לא ניתן לשנות את הכתובת שאליה הוא מצביע. לעומת זאת מצביע יכול להכיל מס' כתובות שונות לאורך ריצת התוכנית: int a[5], b[8]; a = b; שגיאת קומפילציה int a, b, *ptr; ptr = &a; ptr = &b;
32
מצביעים לעומת מערכים- השוואה
אילו הבדלים מהותיים בין מצביע למערך? יצירת מערך מקצה זיכרון לאברי המערך. לעומת זאת מצביע זקוק לזיכרון שמוקצה על ידי גורם חיצוני. שגיאת warning. גם כן יתכן שגיאת זמן ריצה בהנחת שכתובת הזבל שמכיל ptr אינה חוקית int *ptr; ptr[0] = 100;
33
מצביעים לעומת מערכים- השוואה
אילו הבדלים מהותיים בין מצביע למערך? עבור משתנה שהוא מערך – כמו כל משתנה אחר – התוכנית יודעת כמה זיכרון הוא תופס, כיוון שזה מוגדר בטיפוס שלו. לכן, ניתן לקבל את גודלו של המערך בזיכרון באמצעות האופרטור sizeof. לעומת זאת, מצביע איננו יודע על איזה אורך מערך הוא מצביע, והפעלת sizeof עליו פשוט מחזירה את כמות הזיכרון הדרושה לאחסון המצביע עצמו. למשל, ב-Code::blocks נקבל: 20 8 int a[5], *p; p = a; printf("%d %d", sizeof(a), sizeof(p));
34
מערכים כפרמטר לפונקציה
בחידה בתחילת הפרק ראינו שהפונקציה יכולה לשנות ערכים בתוך מערך, שערכים אלה "שורדים" גם מחוץ לפונקציה כידוע בשפת C פרמטרים עוברים לפונקציה by value עכשיו יש בידינו את הכלים ליישב את הסתירה:
35
החתימה של פונקציה המקבלת מערך
למעשה החתימה הבאה: שקולה לחלוטין, מכל בחינה שהיא, לחתימה הבאה: בשני המקרים יש לקרוא לפונקציה כמו בקריאה הבאה: והטיפוס של הפרמטר הראשון בשני המקרים יהיה int*! צורת הכתיבה הראשונה היא פשוט צורת כתיבה ש-C מאפשרת להשתמש בה כשרוצים להדגיש כי הכוונה שהפונקציה תקבל מערך כפרמטר, ולא סתם מצביע ל-int בודד. ניתן להשתמש בצורת כתיבה זו רק עבור פרמטרים של פונקציות! לא ניתן להשתמש בה בהצהרות על משתנים, למשל. double average(int grades[], int n); double average(int* grades, int n); int grades[3]={90,98,65}; double res = average(grades,3);
36
מערכים כפרמטר לפונקציה
המשמעות היא שלמעשה בשפת C מערך אינו יכול להיות פרמטר לפונקציה הדרך היחידה להעביר מערך לפונקציה היא על ידי העברת הכתובת שלו. עושים זאת באמצעות שימוש בשם המערך המתפקד גם כמצביע לאיבר הראשון בו. מעשית, צורת העברה זו טובה יותר! זאת כיוון שמערך עשוי לתפוש זיכרון רב, ועדיף להמנע משכפול תוכנו בכל פעם שמעבירים אותו כפרמטר לפונקציה.
37
מערך כפרמטר לפונקציה לסיכום, פונקציה המקבלת מערך תראה כך:
void print(int *array, int len) { for (int i=0; i<len; ++i) printf("%d\n", array[i]); } int my_array[8]; print(my_array, 8); כיוון שקיבלנו מצביע (ולא מערך) איננו יודעים מה אורכו של המערך ולכן נעביר אותו כפרמטר נפרד לפונקציה הפונקציה תקבל מצביע בתור פרמטר בתוך הפונקציה נעבוד עם המצביע כמו שהיינו עובדים עם מערך בקריאה לפונקציה נציין את שם המערך (שיהפוך אוטומטית למצביע לתחילת המערך)
38
מערכים כפרמטר לפונקציה
תרגיל 4: כתבו פונקציה המקבלת מערך של מצביעים ל- int ומחזירה את מספר המצביעים שאינם בשימוש. כתבו פונקציית main המשתמשת בפונקציה שלכם בצורה תקינה הזכירו להם שכל מצביע שאינו בשימוש יש לאתחל ל-NULL, ולכן צריך בפונקצית main שבה נייצר את המערך צריך לדאוג לזה.
39
מערכים כפרמטר לפונקציה
#include <stdio.h> int get_non_used_ptrs(int* a[], int len) { int i, count = 0; for (i = 0; i < len; i++) if (a[i] == NULL) count++; return count; }
40
מערכים כפרמטר לפונקציה
int main() { int x = 3, y = 6; int* arr[] = {&x, NULL, &y, NULL, NULL}; printf("Non-used pointers in array: %d\n", get_non_used_ptrs(arr, 5)); return 0; }
41
מערכים כפרמטר לפונקציה
נשים לב שלמעשה העברנו את תוכן המערך לפונקציה by reference. כלומר, הפונקציה לא קיבלה עותק זמני של המערך (שזו העברה by value), אלא אנו מספקים לה גישה ישירה למערך שלנו. כל שינוי שהיא תבצע במערך יתרחש במערך המקורי עצמו. למשל, הפונקציה הבאה מעדכנת ציונים באמצעות פקטור חיבורי: void factor(int *grades, int n) { for (int i=0; i<n; ++i) { grades[i] += 5; }
42
העברת מערך דו מימדי לפונקציה
double matrix[N][M]; נגדיר מערך דו מימדי: מערך זה פרוש בזיכרון ברצף שורה אחר שורה: matrix[][] x … … matrix[0][0..M-1] matrix[1][0..M-1]
43
העברת מערך דו מימדי לפונקציה
כיצד נחשב מצביע לאיבר matrix[i][j] ? נבצע זאת בשלבים: כתובת תחילת המערך הדו-ממדי היא: כעת נמצא את כתובתו של האיבר הראשון בשורה ה-i הראשון, כלומר מצביע ל-matrix[i][0] : נשים לב ששורה זו היא למעשה השורה ה-i+1 במטריצה, ולכן יש i שורות לפניה. כיוון שבכל שורה יש M איברים, יוצא שבסה"כ ישנם M*i איברים בזיכרון לפני האיבר matrix[i][0], ובחשבון מצביעים נקבל: &matrix[0][0] &matrix[i][0] == &matrix[0][0] + M*i
44
העברת מערך דו מימדי לפונקציה
משהגענו לאיבר matrix[i][0], נותר לנו רק להתקדם בשורה לאיבר matrix[i][j]. לשם כך עלינו לדלג על עוד j איברים בזיכרון, ובסיכומו של דבר אנו מקבלים: &matrix[i][j] == &matrix[0][0] + M*i + j מסקנה: על מנת לאתר איבר כלשהו במערך דו-ממדי, הכרחי לדעת את אורך השורה שלו – דהיינו את מספר הטורים במערך M (שימו לב שאת מספר השורות הכולל דווקא אין הכרח לדעת). זו הסיבה, למשל, מדוע כאשר מאתחלים מערך דו-ממדי חייבים לציין את אורך השורה שלו, גם כאשר מציינים מפורשות רשימת אתחול.
45
העברת מערך דו מימדי לפונקציה
אם כן כיצד נעביר מערך דו מימדי כפרמטר לפונקציה? כמו במקרה החד מימדי, נעביר מצביע למערך ולא את תוכנו נהיה חייבים לציין באיזשהו אופן מה אורך השורות במערך נציין מפורשות את אורך השורה במטריצה באופן הבא: נציג פונקציה המקבלת מערך דו מימדי בעל אורך שורה 4. חתימת הפונקציה תראה כך: int funky(int matrix[][4]);
46
העברת מערך דו מימדי לפונקציה
חשוב לשים לב שהפונקציה שהגדרנו זה עתה יכולה לקבל כפרמטר אך ורק מערכים שאורך השורה שלהם הוא 4! כל ניסיון להעביר לה מערך בעל אורך שורה אחר יגרור שגיאת קומפילציה. int funky(int matrix[][4]);
47
דוגמא- חישוב מינימום ומקסימום
נכתוב פונקציה המקבלת מערך דו ממדי כפרמטר ומחזירה את האיבר המינימלי והמקסימאלי בו. מה תהיה החתימה של הפונקציה? שימו לב שבחתימה זו אנו חייבים לציין את אורך השורה של המערך הדו-ממדי (במקרה שלנו זהו M, המוגדר כ-#define). עם זאת, איננו מניחים דבר בנוגע למספר השורות במערך, ולכן ערך זה מועבר כפרמטר n של הפונקציה. void extremes(int a[][M], int n, int *min, int *max) 1) הזכירו שכשאנחנו רוצים להחזיר שני ערכי החזרה נעשה זאת ע"י העברת שני מצביעים שלתוכם הפונקציה תכתוב את התוצאות 2) הזכירו שצריך להעביר את מס' השורות במטריצה בפרמטר נפרד
48
דוגמא- חישוב מינימום ומקסימום
void extremes(int a[][M], int n, int *min, int *max) { int i, j; *min = *max = a[0][0]; for (i = 0; i < n; ++i) for (j = 0; j < M; ++j) { *min = (a[i][j] < *min) ? a[i][j] : *min; *max = (a[i][j] > *max) ? a[i][j] : *max; }
49
מערך דו מימדי באורך משתנה
בסטנדרט c99, ניתן לכתוב פונקציות המקבלות מערך דו מימדי, גם אם אורך השורה לא קבוע. דוגמת שימוש int funky2(int n, int m, int matrix[][m]){ … } int x, y; scanf(“%d%d”, &x, &y); int mat[x][y]; funky2(x, y, mat);
50
פויינטר כללי: void* הטיפוס void* הוא טיפוס לכל דבר, וניתן להגדיר ממנו משתנים. טיפוס זה משמש כאשר אנחנו רוצים לייצג כתובת זיכרון "טהורה", ללא כל מידע לגבי הטיפוס שמאוחסן שם. ניתן בשפת C להמיר כל מצביע למצביע מטיפוס void*. למעשה, אנו נפטרים כך מהמידע לגבי הטיפוס שמאוחסן בזיכרון, ונותרים עם כתובת הזיכרון בלבד. לא ניתן להפעיל אופרטור * על משתנה מטיפוס void*. אם ידוע לנו הטיפוס האמיתי אליו מצביע משתנה מסוג void*, ניתן להמיר אותו חזרה לסוג המצביע האמיתי באמצעות המרה מפורשת.
51
void*- דוגמא לשימוש פונקציה המעתיקה n בתים של זכרון.
//n is the size (in bytes) to copy void memcopy(void* dest, void* src, int n) { int i; char *src_p, *dst_p; src_p = (char*)src; dst_p = (char*)dest; for (i = 0; i < n; i++) { *(dst_p+i) = *(src_p+i); } פונקציה המעתיקה n בתים של זכרון. חובה להמיר void* לסוג אחר כדי לבצע את ההעתקה. בחרנו char* כיוון שגודלו בייט אחד. שימוש: הסבירו שכדי לבנות פונקציה גנרית שמבצעת swap אנחנו עדיין חייבים לדעת מה הטיפוס של המשתנים. int a, b; memcopy(&a, &b, sizeof(int);//a=b
52
void*: generic swap תרגיל 5: כתבו פונקציה המחליפה שתי משתנים ומתאימה לכל טיפוס.
53
משתנה עזר בגודל n בתים בדיוק
void*: generic swap //Generic Swap!!! //n: size (bytes) of the variable to swap void gswap(void* dest, void* src, int n) { char temp[n]; memcopy(temp, dest, n); memcopy(dest, src, n); memcopy(src, temp, n); } תרגיל 3: כתבו פונקציה המעתיקה n בתים מכתובת מקור לכתובת יעד. שתי הכתובות יהיו נתונות כ- void*. משתנה עזר בגודל n בתים בדיוק int a=1, b=5; gswap(&a, &b, sizeof(int)); printf(“%d %d”, a, b); 5 1
54
מבוא למדעי המחשב מ' - תירגול 2
הקצאה דינאמית מבוא למדעי המחשב מ' - תירגול 2
55
מצביעים ואורך חיי המשתנה
העובדה שבידנו מצביע למשתנה, אינה אומרת שהמשתנה חי בזיכרון! int* getNumber() { int number; printf(“Enter a number: “); scanf(“%d”, &number); return &number; } p מצביע למשתנה שאינו מוקצה בזיכרון! לא ניתן לדעת מה נמצא כעת בזיכרון ש-p מצביע אליו. int x, *p; p = getNumber(); x = *p; // Bug!
56
זיכרון דינמי נניח שברצוננו לכתוב פונקציה שתקלוט מערך של מספרים מהמשתמש ותחזיר אותו: int* getNumbers(int n) { int numbers[n]; //C99 style printf(″Please enter %d numbers: ″,n); for (int i = 0; i < n; i++) { scanf(″ %d″, &numbers[i]); } return numbers; Warning: function returns address of local variable
57
void* malloc(size_t size);
זיכרון דינמי זיכרון דינמי הינו חלק מהזיכרון שניתן "להזמין" ממנו כמות זכרון לפי בקשה. הבקשות נעשות ע"י פקודת malloc (שמוגדרת בספריה stdlib.h) הפקודה מקבלת בפרמטר size את מספר הבתים שרוצים להקצות ומחפשת size בתים רצופים פנויים בזיכרון. אם מערכת ההפעלה הצליחה להקצות את הזיכרון הדרוש מוחזרת כתובת הבית הראשון ברצף, אחרת- מוחזרת הכתובת 0 (Null). void* malloc(size_t size);
58
זיכרון דינמי בניגוד להקצאת משתנים רגילה בה התוכנית יודעת באופן אוטומטי לפנות את הזיכרון ביציאה מהבלוק, כאן המתכנת הוא שהזמין את הזיכרון, ורק הוא יודע מתי הסתיים השימוש בו. לכן מוטלת עליו האחריות לשחרר את הזיכרון באופן מפורש באמצעות הפקודה free: void free(void *ptr);
59
זיכרון דינמי לא חובה להמיר באופן מפורש את תוצאת ה-malloc לטיפוס הדרוש, אך נעשה זאת כדי לשפר את קריאות הקוד כיוון שגודל טיפוס עשוי להשתנות בין מחשבים שונים, מומלץ להשתמש ב- sizeof על מנת לחשב את הגודל #include <stdlib.h> int* getNumbers(int n) { int* numbers = (int*)malloc(n*sizeof(int)); if (!numbers) { return NULL; } printf(″Please enter %d numbers: ″,n); for (int i = 0; i < n; i++) { scanf(″ %d″, &numbers[i]); return numbers;
60
לפני שנצא מפונקציה או תוכנית, נשחרר את הזיכרון לשימוש אחר.
זיכרון דינמי int main() { int *numbers = getNumbers(10); if (!numbers) { printf(″Memory allocation failed!″); return 1; } … /* Do things with the numbers array */ free(numbers); return 0; חייבים לזכור לשחרר זיכרון דינמי כדי להמנע מדליפות זיכרון הסבירו שאם לא משחררים את הזיכרון הגישה אליו אובדת ולא ניתן למחזר אותו יותר. לפני שנצא מפונקציה או תוכנית, נשחרר את הזיכרון לשימוש אחר.
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.