Download presentation
Presentation is loading. Please wait.
1
תרגול 2 העברת פרמטרים לתוכניות ב -C קלט ופלט ב -C I/O redirection זיכרון דינמי מצביעים מערכים גישות לא חוקיות לזיכרון
2
העברת פרמטרים לתוכניות ב -C כאשר main נקראת מועברים אליה שני פרמטרים : – argc - מספר הארגומנטים בשורת הפקודה ( כולל הפקודה עצמה ) – argv - מצביע למערך של מחרוזות אשר מכילות את הארגומנטים בשורת הפקודה נכתוב תוכנית בשם echo אשר תדפיס את הארגומנטים בשורת הפקודה שלה בשורה אחת עם רווח ביניהם לדוגמא : > echo hello, world hello, world > argv[0] מחזיק את השם של התוכנית הנקראת. לכן argc הוא לפחות 1. אם argc הוא 1 אין ארגומנטים אחרי שם התוכנית. בדוגמא לעיל argc הוא 3 ו argv[0], argv[1] ו argv[2] הם "echo", "hello," ו "world" בהתאמה 2
3
העברת פרמטרים לתוכניות ב -C הארגומט האמיתי הראשון יהיה ב - argv[1] והאחרון יהיה ב -argv[argc-1]. argv[argc] יהיה NULL. התוכנית echo: #include int main (int argc, char* argv[]){ /* echo command-line arguments */ int i; for (i = 1; i < argc; i++){ printf ("%s ", argv[i]); { printf ("\n"); return 0; } echo\0 hello,\0 World\0 argv argc=3 NULL 3
4
חזרה על קלט פלט ב -C פונקציות קלט / פלט נמצאות בספריה stdio.h –בקובץ שבו משתמשים בהן צריך לבצע #include ניתן בעזרתן לבצע קלט מהמקלדת, פלט למסך וקלט / פלט לקבצים אופן העבודה עם קבצים : 1. פותחים את הקובץ לקריאה או לכתיבה 2. קוראים / כתובים מ -/ אל הקובץ 3. סוגרים את הקובץ כל הפעולות על קבצים נעשות ע " י משתנה מטיפוס מצביע ל FILE- ( הטיפוס FILE מוגדר ב -stdio.h) 4
5
עבודה עם משאבי מערכת משאבי מערכת system resourcesקבצים הם משאבי מערכת (system resources) מערכת הפעלה מנהלת פתיחה / סגירה של הקבצים קבצים הם משאב מוגבל – קיימת מגבלה על מספר קבצים שיכולים להיות פתוחים בו זמנית. –לכן חייבים לסגור קבצים אחרי שמסיימים לעבוד איתם –אי סגירה של קבצים עלולה לגרום למילוי המכסה של הקבצים וכשלון התוכנית 5
6
עבודה עם משאבי מערכת אופן העבודה הכללי עם משאבי מערכת : 1. פותחים / מקצים משאב 2. משתמשים במשאב 3. סוגרים / משחררים משאב אי ביצוע שחרור משאבים גורם לדליפת משאבים (resource leak) ועלול לגרום לכישלון התוכנה. –טעות תכנות נפוצה דוגמא נוספת למשאב היא זיכרון 6
7
פתיחה של קובץ פתיחת קובץ נעשית ע " י הפונקציה fopen: FILE* fopen(const char* filename, const char* mode); filename - שם הקובץ לפתיחה mode - המצב בו יש לפתוח את הקובץ : – "r" - פתח לקריאה בלבד אם הקובץ לא קיים, תקרה שגיאה ( החזרת NULL) – "w" - פתח לכתיבה בלבד אם הקובץ לא קיים, ייווצר קובץ ריק אם הקובץ קיים, התוכן שלו יימחק והקובץ יהפוך לריק – "a" - פתח לכתיבה בסוף הקובץ אם הקובץ לא קיים, ייווצר קובץ ריק הפונקציה מחזירה מצביע ל -FILE שמייצג את הקובץ אם הצליחה לפותחו ואחרת מחזירה NULL. FILE* fd = fopen("hello.c", "r"); 7
8
סגירה של קובץ סגירת קובץ שנפתח ע " י fopen נעשית ע " י הפונקציה fclose: int fclose (FILE* fd); fclose מחזירה 0 אם הצליחה ו -EOF אם קרתה שגיאה. לדוגמא : FILE* fd; fd = fopen("hello.c", "r"); if(fd == NULL) error(“Failed to open hello.c!\n”);... /* Perform input on file */ if (fclose(fd) == EOF) printf (“Failed to close file!\n"); else printf ("File closed successfuly.\n"); בסיום ריצת תוכנית מערכת ההפעלה דואגת לסגור את כל הקבצים למרות זאת, תכנות נכון הוא לסגור את כל הקבצים בצורה מפורשת גם בסוף main 8
9
בדיקת שגיאות if(fd == NULL) error(“Failed to open hello.c\n”); if(fclose(fd) == EOF) printf(“Failed to close file”); חייבים לבדוק ערך חזרה של כל פונקציה שאנחנו קוראים לה – לוודא שלא קרו שגיאות. כדאי לזהות מצב שבו יש שגיאה כמה שיותר מוקדם –כדי למנוע שגיאות נגררות ונזק נוסף –כדי שיהיה יותר קל למצוא מה גרם לבעיה הראשונית 9
10
בדיקת שגיאות כדאי שהתוכנה תהיה כמה שיותר " פרנואידית " וכל הזמן תבדוק את עצמה ותוודא שאין שגיאות : בדיקת תקינות הקלט בדיקת פרמטרים לפונקציה שלנו בדיקת ערכי חזרה של פונקציות שאנחנו קוראים להן סגנון תכנות כזה נקרא " תכנות הגנתי " (defensive programming) 10
11
ערוצים סטנדרטיים ב C- יש התייחסות אחידה לכל סוגי הקלט והפלט. גם הקלט מהמקלדת והפלט מהמסך נעשים ע " י מצביעים ל -FILE כאשר תוכנית C מתחילה לרוץ, מערכת ההפעלה פותחת עבורה בצורה אוטומטית שלושה ערוצים סטנדרטיים : – stdin נפתח לקריאה ( בד " כ מהמקלדת ) – stdout נפתח לכתיבה ( בד " כ אל המסך ) – stderr נפתח לכתיבה ( בד " כ אל המסך ) stdin, stdout ו -stderr הם למעשה שמות משתנים כאשר כולם מטיפוס מצביעים ל -FILE. לדוגמא הפקודה : fclose (stdin); תגרום לכך שלא ניתן יותר לקרוא קלט מהמקלדת הערה : אם תוכנית קוראת נתונים מהמקלדת ורוצים להקליד תו EOF יש להקיש Ctrl-d 11
12
דוגמא : התוכנית copy במידה וקיבלה שני פרמטרים, מעתיקה את הקובץ אשר שמו הנו הפרמטר הראשון, לקובץ אשר שמו הוא הפרמטר השני במידה וקיבלה פרמטר יחיד, מעתיקה את הקובץ אשר שמו הנו פרמטר זה לערוץ הפלט הסטנדרטי במידה ולא התקבלו פרמטרים כלל, התוכנית מעתיקה את מה שמתקבל בערוץ הקלט הסטנדרטי לערוץ הפלט הסטנדרטי 12
13
ערוצים סטנדרטיים #include #define CHUNK_LEN 100 void error(char *msg) ) { fprintf(stderr, "Error: %s\n", msg); exit(1); } int main(int argc, char *argv[]) { FILE *in = stdin; FILE *out = stdout; char buf[CHUNK_LEN]; if (argc>3) error("Bad number of parameters"); if (argc>1) in = fopen(argv[1], "r"); if (in==NULL) error("Can't open input file"); if (argc>2) out = fopen(argv[2], "w"); if (out==NULL) error("Can't open output file"); while(fgets(buf, CHUNK_LEN, in) != NULL) fputs(buf, out); fclose(in); fclose(out); return 0; } סגנון תכנות צפוף מדי ! ( מתאים לשקפים, אך אסור לתוכנה אמיתית ) מספר statements בשורה הגוף של if באותה שורה ובלי סוגריים מסולסלים 13
14
ערוצים סטנדרטיים char* fgets (char* line, int maxline, FILE *fp) קוראת שורה, כולל '\n’, מקובץ fp לתוך line. לכל היותר maxline-1 תווים יקראו מחזירה NULL בתנאים הבאים : –בסוף הקובץ אם לא נקרא אף תו – אם קרתה שגיאה אחרת מחזירה line מוסיפה ‘\0’ בסוף המחרוזת int fputs(char* line, FILE *fp) כותבת את line לקובץ מחזירה EOF אם קרתה שגיאה, מספר אי שלילי אחרת 14
15
ערוצים סטנדרטיים char* gets(char* s) קוראת שורה, לא כולל,'\n’ מהקלט הסטנדרטי לתוך s מוסיפה ‘\0’ בסוף המחרוזת ערך מוחזר – כמו של fgets מקור לבאגים ובעיות security, עדיף להשתמש ב -fgets int puts(char* s) כותבת s אל הפלט הסטנדרטי כותבת ‘\n’ אחרי s ערך מוחזר – כמו של fputs 15
16
I/O redirection בכל תוכנית שרצה תחת Unix ערוץ הקלט הסטנדרטי מחובר אל המקלדת, וערוצי הפלט והודעות השגיאה הסטנדרטיים מחוברים אל המסך כברירת מחדל ניתן לכוון מחדש הן את ערוץ הקלט הסטנדרטי והן את ערוצי הפלט והשגיאות הסטנדרטיים אל ומאת קבצים כלשהם. הכיוון מחדש נקרא redirection stdout stdin stderr Program 16
17
Input redirection תגרום ל -program לקבל את הקלט הסטנדרטי שלה מהקובץ filename לדוגמא : % copy < filename תגרום ל - copy הנתונה לקחת את הקלט מהקובץ filename ההפעלה בצורה הנ " ל שקולה ( מבחינת התוצאה ) עבור התוכנית copy להפעלה : % copy filename אך מה ההבדל מבחינת התוכנית ? 17
18
Output redirection > > תגרום ל -program לכתוב את הפלט הסטנדרטי שלה לקובץ filename. אם קיים כבר קובץ בשם filename לפני ביצוע הפקודה, הפקודה לא תבוצע % cat myfile line 1 line 2 % cat myfile > out % cat out line 1 line 2 % 18
19
Output redirection >> >> מכוונת את הפלט הסטנדרטי של program לקובץ filename אך משרשרת אותו לסוף הקובץ. אם לא קיים קובץ בשם filename לפני ביצוע הפקודה, הפקודה לא תבוצע % cat yourfile line 3 % cat yourfile >> out % cat out line 1 line 2 line 3 % 19
20
Output redirection ראינו ששתי פקודות כיוון הפלט הקודמות עלולות להיכשל. הראשונה (<) אם הקובץ קיים, והשנייה (<<) אם הקובץ אינו קיים ניתן להבטיח שהפקודות הנ " ל יצליחו בכל מקרה ע " י הוספת ! לפקודות הכיוון : >! >! הורסת את הקובץ filename וכותבת עליו את הפלט הסטנדרטי של program בכל מקרה אם הקובץ filename אינו קיים, אזי מתקבלת אותה התנהגות כשל < >>! >>! יוצרת את הקובץ filename אם הוא לא קיים. אם הקובץ filename קיים, אזי מתקבלת אותה התנהגות כשל << 20
21
Input and output redirection ניתן להפנות גם את ערוץ הקלט הסטנדרטי של התוכנית וגם את ערוץ הפלט לדוגמא הפקודות : % cat out % cat ! out גורמות להעתקת קובץ in לקובץ out ( מה ההבדל ?) % cat > out % cat >! out גורמות להוספת קובץ in לסוף קובץ out ( מה ההבדל ?) 21
22
Multiple redirection הבעיה : נניח אנחנו רוצים לשמור את הודעות השגיאה של קומפיילר בקובץ. הדבר יכול להיות שימושי כאשר נרצה לראות את השגיאות הראשונות שהקומפיילר מוציא % gcc try_input.c -o try_input > out_err try_input.c: In function `main': try_input.c:4: parse error before `printf' *** Error code 1 % more out_err % השגיאות הודפסו למסך ! קובץ הפלט ריק ! 22
23
Multiple redirection הפתרון : בנוסף להפניית קלט / פלט סטנדרטיים ניתן להפנות גם את ערוץ השגיאות הסטנדרטי מהמסך אל קבצים >& מפנה הן את הפלט הסטנדרטי והן את ערוץ השגיאות הסטנדרטי לקובץ file % gcc try_input.c -o try_input >& out_err % % more out_err try_input.c: In function `main': try_input.c:4: parse error before `printf' *** Error code 1 23
24
Multiple redirection >&! >&! זהה לפקודה הקודמת אבל עובדת גם אם הקובץ file קיים >>& >>& פועלת כמו במקרה הקודם, אבל כאן המידע החדש משורשר אל סוף הקובץ. ניתן גם להיעזר ב - ! אם רוצים להבטיח כי הפקודה תתבצע >>&! >>&! הפקודה > >& ( > ) >& גורמת לפלט הסטנדרטי להיכתב אל הקובץ f1 ואילו להודעות השגיאה להכתב אל קובץ f2 24
25
Multiple redirection #include int main(){ fprintf(stderr, “This is error\n”); printf(“This is stdout\n”); return 0; } % gcc prog.c –o prog % prog This is error This is stdout % prog > out.txt This is error % cat out.txt This is stdout % prog >& errout.txt % cat errout.txt This is error This is stdout % (prog > out) >& err % cat out This is stdout % cat err This is error 25
26
ניהול זיכרון של תוכניות ב -C משתנים ב -C נבדלים בשני אופנים : 1. טיפוס – למשל int, char ו -double. טיפוסים שונים אחד מהשני מבחינת הערכים האפשריים עבור כל אחד, אילו פעולות ניתן לבצע על כל אחד מהם, וכמות הזיכרון שנדרשת עבור כל אחד מהם 2. אופן ההקצאה ונגישות מבחינת אופן ההקצאה והנגישות קיימים 5 סוגי משתנים : –משתנים גלובליים –משתנים סטאטיים של קובץ –משתנים לוקאליים –משתנים סטאטיים של פונקציה –משתנים דינמיים 26
27
ניהול זיכרון של תוכניות ב -C משתנים גלובליים - משתנים אשר מוגדרים לאורך כל התוכנית וניתן לגשת אליהם מכל הפונקציות של התוכנית. המשתנים מוקצים באופן אוטומטי כאשר התוכנית מתחילה, ונשמרים לאורך כל הריצה משתנים סטאטיים של קובץ - משתנים אשר מוגדרים לאורך כל התוכנית. ניתן לגשת אליהם מכל הפונקציות של הקובץ בה מוגדר המשתנה. המשתנים מוקצים באופן אוטומטי כאשר התוכנית מתחילה, ונשמרים לאורך כל הריצה משתנים לוקאליים - משתנים פנימיים של פונקציות, רק הפונקציה שבה הם מוגדרים יכולה לגשת אליהם. משתנים אלו מוקצים בכל פעם שהפונקציה נקראת, ומשוחררים כאשר הפונקציה מסתיימת משתנים סטאטיים של פונקציה - משתנים פנימיים של פונקציות. משתנים אילו שומרים על ערכם בין קריאות שונות לפונקציה, ומשוחררים רק בסיום התוכנית. קיים רק עותק אחד של כל משתנה כזה לאורך כל התוכנית 27
28
ניהול זיכרון של תוכניות ב -C מבין ארבע הקבוצות הללו סוג המשתנים השימושי ביותר הנו המשתנים הלוקאליים. שימוש בשאר סוגי המשתנים נחשב לרוב כתכנות רע - משתמשים בהם רק כשכל הפתרונות האחרים יותר גרועים. הבעיות : –נגישות יתר של משתנים – side-effects של פונקציות קשה יותר להבין מה הפונקציה עושה בלי ההקשר קשה לבודד בעיות כשמדבגים – הבאגים יכולים להופיע רק בהקשר מסוים 28
29
משתנים דינמיים הבעיה עם משתנים לוקאליים היא שהמשתנים נהרסים כאשר פונקציה נגמרת. לעיתים נרצה כי משתנים מסוימים יישארו בזיכרון גם לאחר שהפונקציה נגמרת. יתר על כן, לעיתים נרצה להקצות בזמן ריצה מספר רב של משתנים אשר מספרם אינו ידוע בזמן כתיבת התוכנית אלא רק בזמן ריצה משתנים דינמיים - מוקצים ע " י התוכנית בזמן ריצה. המתכנת מקצה את המשתנים מאזור בזיכרון המכונה ערימה (Heap) ובאחריות המתכנת לשחרר את המשתנים שהוקצו לאחר שאין בהם צורך 29
30
זיכרון הוא משאב מערכת כמו בעבודה עם משאבי מערכת אחרים : 1. מקצים זיכרון 2. משתמשים בזיכרון 3. משחררים את הזיכרון שהקצנו אחרי השימוש הקצאת הזיכרון מתבצעת ע " י פונקציה malloc שחרור הזיכרון מתבצע ע " י פונקציה free אי ביצוע שחרור זיכרון גורם לדליפות זיכרון (memory leaks) והתוכנית עלולה להיכשל –טעות תכנות נפוצה 30
31
מבנה הזיכרון תוכנית המחשב רואה את הזיכרון כמערך גדול, של בתים (bytes) –לכל בית יש כתובת ייחודית לו –התוכנית יכולה להיעזר בבית אחד או יותר בכדי לשמור משתנים שמות משתנים נדרש בית אחד עבור ערך מסוג char. נדרשים 4 בתים עבור ערך מסוג int. c i ערךכתובת זבל 0x200 'a'0x201 ? 0x202 6 0x203 0 0x204 0 0x205 0 0x206 ? 0x207 …… 31 שמות משתנים
32
מבנה הזיכרון זיכרון המחשב מחולק עבור התוכנית לשני חלקים עיקריים –חלק המנוהל באופן אוטומטי ע " י התוכנית שם נשמרים המשתנים הגלובליים, הלוקאליים והסטאטיים – heap ( ערימה ) - חלק המנוהל ע " י המתכנת במפורש ניתן לבקש להקצות מחלק זה שטחי זיכרון בגדלים שונים ע " י הפקודה malloc לשחרר זיכרון אשר אין בו צורך יותר ע " י הפקודה free פעולת ההקצאה מחזירה את הכתובת של תחילת שטח הזיכרון שהוקצה. בכדי לשמור כתובת זו יש להיעזר במשתנה מסוג מצביע 32
33
מצביעים משתנה מסוג מצביע –משתנה שערכו הוא כתובת בזיכרון שבתוכה נמצא נתון כלשהוא * * –המשתנה מיועד להצביע על מקום בזיכרון המכיל משתנה מטיפוס int *ptr; ptr הוא משתנה אשר מיועד להצביע על מקום בזיכרון מטיפוס int כתובות –נניח שהגדרנו משתנה ע " י int x; הביטוי : &x הוא הכתובת בזיכרון בה שמור תוכנו של.x – למשל, אם x הוא שלוש ומאוחסן בכתובת 2000 אזי ערכו של הביטוי &x הוא 2000 33
34
מצביעים שימוש במשתנה מסוג מצביע –גישה למשתנה בעזרת מצביע לכתובתו הפקודה האחרונה שקולה ל : x=5.8. שימו לב כי מצביע הוא משתנה בעצמו ולכן גם הוא נשמר בזיכרון. float x; float* ptr; ptr = &x; *ptr=5.8; 34
35
מצביעים ומוצבעים הגדרת מצביע אינה גורמת ליצירת המקום אשר אליו הוא יצביע int *pi; pi אינו מצביע למקום בזכרון המכיל integer אלא רק יכול להצביע למקומות בזכרון המכילים int *pi = 0 ; הצבת 0 למקום אליו מצביע pi עלולה לגרום להעפת התוכנית כיוון שלא ידוע להיכן בזכרון pi מצביע וגישה למקום אליו הוא " מצביע " עלולה להיות גישה לא חוקית לזכרון לפני גישה למקום בזכירון המוצבע ע " י מצביע יש לוודא כי המצביע מצביע לכתובת חוקית. יש לאתחל מצביעים כך שיכילו את הערך NULL לפני שמוכנסת אליהם כתובת חוקית, בצורה זאת ניתן לבדוק האם הם אינם מצביעים לכתובת חוקית int j ; int *pi = NULL;... pi = &j; *pi = 0; 35
36
מצביעים ומערכים יצירת מערך עם number איברים כאשר ערכי האינדקסים נעים בין 0 ל number-1 [ ] [ ] int x[3]; [ ]גישה למקום במערך ( חד - ממדי ) ע " י : [ ] x[2]; " שם המערך " הנו מצביע אשר לא ניתן לשנות את המקום אליו הוא מצביע. לכן הביטוי x ללא אינדקסים מהווה מצביע אל המקום הראשון במערך ! כלומר, *x=3 שקול ל x[0]=3 הגדרת מערך בגודל number גוררת הקצאה אוטומטית number אברים מהטפוס המתאים, בעוד שהגדרת מצביע אינה גורמת להקצאת האיבר המוצבע ניתן להצביע למערך ( או איבר במערך ) גם ע " י מצביע מערכים מוקצים ומשוחררים אוטומטית int *p, *q ; p = x ; /* p points to x[0] */ q = x+2 ; /* q points to x[2] */ p = q-1; /* p points to x[1] */ הערות כאלה מתאימות רק לשקפים ! בתוכנה אמיתית הן לא מוסיפות שום מידע לקוד 36
37
מצביעים למצביעים מצביע הנו משתנה, ולכן יש לו כתובת. ניתן להציב את כתובתו במשתנה מטיפוס מצביע למצביע int a, b ; /* a and b are integers */ int *pi = &a ; /* pi is a pointer to integer that points to a */ int** ppi = π /* ppi is a pointer to pointer to integer. It points to pi */ pi = &b ; /* pi points to b */ *pi = 5 ; /* b is set to 5 */ *ppi = &a ; /* pi is set to &a, pi points to a now */ *pi = 6 ; /* a is set to 6 */ *(*ppi) = 7; /* a is set to 7 */ ( נתעלם בדוגמא ממספר הבתים שנדרשים לכל משתנה ( משתנהערךכתובת ppi0x2020x200 'a'0x201 pi0x2050x202 6 0x203 0x204 a 7 0x205 0x206 b50x207 …… 37
38
מצביע ל - void ניתן להגדיר מצביע מטיפוס void*. במצביע מטיפוס זה ניתן לשמור כתובת של טיפוס כלשהו כיוון שלקומפיילר לא ידוע מהו הטיפוס המוצבע ע " י על ידי מצביע מסוג void*, בכדי לקבל את הערך המוצבע נהוג לבצע cast שימיר את המצביע למצביע לטיפוס ששונה מ -void: מצביע ל void הנו שמושי עבור פונקציות ניהול הזיכרון הדינמי אשר מסוגלות להקצות שטח זיכרון עבור טיפוס כלשהו. שמושים נוספים למצביע מסוג זה נראה בהמשך. *ptr; void *ptr; i; int i; d ; double d ;….. (…) { if (…) { ptr = &d ; } else ptr = &i; } *pi; int *pi; j; int j; pi = (*) ptr ; pi = (int*) ptr ; j = *((*) ptr); j = *((int *) ptr); 38
39
מצביעים והקצאות זיכרון דינמיות void* malloc (unsigned int size) * אם ההקצאה הצליחה מוחזר מצביע לשטח שהוקצה בזיכרון, אחרת מוחזר NULL כיוון שהזיכרון המוקצה יכול לשמש לשמירת ערכים מטיפוס כלשהו, הטיפוס המוחזר ע " י malloc הנו void* ההגדרות של malloc ו -NULL נמצאים ב - stdlib.h. לדוגמא : int *ptr; ptr=(int*)malloc(4); /* allocate 4 bytes */ if (ptr==NULL) { /* check if allocated */ error(“Failed to allocate 4 bytes”); } כעת ptr מצביע למקום בזיכרון שבו ארבעה בתים פנויים ( אם ההקצאה הצליחה ) כיוון ש -ptr הוא מיטפוס int* ו -malloc מחזירה ערך מטפוס void*, נהוג לבצע casting * ההגדרה האמיתית של malloc היא void * malloc (size_t size), size_t מחוץ לחומר של הקורס 39
40
מספרי קסם מה הבעיה בשורה הזאת ? ptr=(int*)malloc(4); /* allocate 4 bytes */ מספר קסם magic number 4 הוא מספר קסם (magic number) –כינוי גנאי לכל מספר בקוד התוכנית, שהוא לא 0 או 1 ( חוץ מהמספרים שמופיעים ב -define) –תכנות רע המספר לא מתעד את עצמו כל פעם שמתעורר צורך בשינוי המספר, חייבים לעבור על כל המקומות שהמספר מופיע ולתקן אותם סכנה לבעיות שינבעו מכך שהמתכנת ישכח לתקן את כל המקומות 40
41
מצביעים והקצאות זיכרון דינמיות ptr=(int*)malloc(4); /* allocate 4 bytes */ מה הבעיה עם התיקון הבא ? #define SIZE_OF_INT (4)... ptr=(int*)malloc(SIZE_OF_INT); הקוד הזה לא יעבוד במחשב שבו int הוא בגודל שונה מ -4 קוד כזה נחשב כ -non portable – לא ניתן להעבירו בלי שינוים בין מחשבים שונים 41
42
מצביעים והקצאות זיכרון דינמיות sizeofכדי למנוע את הצורך בשינוי תוכנית בגלל גודל שונה של טיפוסים, במהדרים / מערכות הפעלה / מחשבים שונים, ניתן להשתמש באופרטור sizeof sizeof( ) מחזיר את מספר הבתים של הטיפוס במערכת הנוכחית sizeof ptr=(int*)malloc(sizeof(int)); *ptr=3; כעת ניתן להשתמש ב *ptr כמשתנה מסוג int ( אם ההקצאה הצליחה ) מה הטעות במקרה הזה ? ptr=(int*)malloc(sizeof(int *)); 42
43
מצביעים והקצאות זיכרון דינמיות free( ) משחרר את בלוק הזיכרון שהוקצה ושהמשתנה מצביע עליו. דוגמא : ptr=(int *)malloc(sizeof(int)); if(ptr == NULL) error(“Not enough memory”);... /* use ptr */ if(ptr != NULL) free if(ptr != NULL) free (ptr); freeאם מעבירים ל -free NULL, לא קורה כלום free- –לכן אין צורך לבדוק ש -ptr לא NULL לפני הקריאה ל free- 43
44
גישות לא חוקיות לזיכרון טעויות נפוצות
45
גישה לא חוקית לזיכרון מצביע יכול להכיל כל כתובת שהיא –זיכרון שלא הוקצה לשימוש –זיכרון שמשמש לדברים אחרים התוכנית עלולה לקרוס /Bus error Segmentation Fault 45 (from The Seattle Times)The Seattle Times
46
גישה לא חוקית לזיכרון התוכנית עלולה לרוץ כרגיל –אבל... ריצה מוצלחת על קלט אחד לא מבטיחה כלום על קלט אחר כל גישה ( קריאה או כתיבה ) דרך מצביע שאינו מצביע לכתובת חוקית עלולה לגרום לשגיאה 46
47
טעויות נפוצות הקצאת מחרוזת קצרה מדיי –לא לשכוח את ‘\0’ – strlen איננה סופרת את ‘\0’ כאשר היא מחזירה אורך של מחרוזת שימוש שגוי ב -sizeof –האופרטור מקבל טיפוס של משתנה –איזה שורה מבין השורות הבאות נכונה ? 1.ptr = (int*)malloc(sizeof(int *)); 2.ptr = (int*)malloc(sizeof(int)); 3.ptr = (int*)malloc(sizeof(ptr)); 47
48
טעויות נפוצות חריגה מגבולות המערך –הקומפיילר לא שומר את האינפורמציה מהו גודל המערך ולכן אינו מתריע בזמן קומפילציה על גישה מחוץ לגבולות המערך 48
49
טעויות נפוצות הזבל לסל וחסל –אחרי free אי אפשר להשתמש בזיכרון, גם לא ממצביע אחר ptr=(int *)malloc(sizeof(int));... ptr2 = ptr;... free (ptr);... *ptr = i; i = *ptr; *ptr2 = i; i = *ptr2; –אסור לשחרר את הזיכרון פעם שנייה אחרי שכבר שוחרר, גם לא דרך מצביע אחר 49
50
טעויות נפוצות שימוש במשתנים לוקליים של פונקציה מחוץ לפונקציה –יצירת משתנה לוקלי בפונקציה, והחזרת הכתובת שלו לפונקציה הקוראת ההבדל בין = ל - == if(ptr == NULL) printf(“Bad pointer!\n”); if(ptr = NULL) printf(“Bad pointer!\n”); 50
51
דוגמא – חריגה מגבולות המערך #include #define TITLE ("\n\tMemory Corruption: How easy it is\n\n") #define BUF_SIZE (8) int main() { int i; char c[BUF_SIZE], buf[BUF_SIZE]; printf("Type string c[]\n"); gets(c); printf("%s\n",c); printf("Type string buf[]\n"); gets(buf); printf("%s\n",buf); for (i=4; i > -10 ; i--){ c[i] = 'a'; printf("i= %2d, buf = %s c= %s\n", i, buf, c); } return 0; } 51
52
0 1 2 3 4 5 6 \0 0 1 2 \0 זבל זבל buf c aa a a a a a a 52
Similar presentations
© 2025 SlidePlayer.com. Inc.
All rights reserved.