לדלג לתוכן

4.8 טיפול בשגיאות פתרון

תרגיל 1 - פתרון: שימוש ב-errno ו-perror

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main() {
    FILE *f = fopen("nonexistent_file.txt", "r");
    if (f == NULL) {
        printf("קוד שגיאה: %d\n", errno);
        perror("שגיאה בפתיחת הקובץ");
        return 1;
    }

    fclose(f);
    return 0;
}

הפונקציה fopen מחזירה NULL כשהיא נכשלת, ובמקביל מעדכנת את errno בקוד השגיאה המתאים. הפונקציה perror לוקחת את הערך הנוכחי של errno, מתרגמת אותו להודעה קריאה, ומדפיסה אותו יחד עם המחרוזת שסיפקנו.


תרגיל 2 - פתרון: שימוש ב-strerror

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>

int main() {
    const char *filename = "data.txt";

    printf("ניסיון לפתוח את הקובץ: %s\n", filename);

    FILE *f = fopen(filename, "r");
    if (f == NULL) {
        printf("תוצאה: נכשל - %s (קוד %d)\n", strerror(errno), errno);
        return 1;
    }

    printf("תוצאה: הצלחה\n");
    fclose(f);
    return 0;
}

ההבדל בין perror ל-strerror: הפונקציה perror מדפיסה ישר ל-stderr, בפורמט קבוע. הפונקציה strerror מחזירה מחרוזת שאפשר לשלב בתוך printf בכל פורמט שנרצה - מה שנותן יותר גמישות.


תרגיל 3 - פתרון: בדיקת שגיאות מרובות

#include <stdio.h>
#include <errno.h>

int main() {
    // ניסיון 1: קובץ שלא קיים
    FILE *f1 = fopen("nonexistent_file.txt", "r");
    if (f1 == NULL) {
        perror("ניסיון 1: שגיאה בפתיחת הקובץ");
    } else {
        printf("ניסיון 1: הקובץ נפתח בהצלחה\n");
        fclose(f1);
    }

    // ניסיון 2: קובץ ללא הרשאות
    FILE *f2 = fopen("/etc/shadow", "r");
    if (f2 == NULL) {
        perror("ניסיון 2: שגיאה בפתיחת הקובץ");
    } else {
        printf("ניסיון 2: הקובץ נפתח בהצלחה\n");
        fclose(f2);
    }

    // ניסיון 3: קובץ תקין
    FILE *f3 = fopen("/etc/hostname", "r");
    if (f3 == NULL) {
        perror("ניסיון 3: שגיאה בפתיחת הקובץ");
    } else {
        printf("ניסיון 3: הקובץ נפתח בהצלחה\n");
        fclose(f3);
    }

    return 0;
}

שימו לב שלכל ניסיון errno מתעדכן מחדש. לא צריך לאפס את errno בעצמנו בין הניסיונות - כי אנחנו קוראים ל-errno רק אחרי שכבר בדקנו ש-fopen החזירה NULL, ואז errno מכיל את קוד השגיאה של הפעולה האחרונה שנכשלה.


תרגיל 4 - פתרון: פונקציה עם טיפול בשגיאות

#include <stdio.h>
#include <errno.h>
#include <string.h>

FILE *safe_open(const char *filename, const char *mode) {
    FILE *f = fopen(filename, mode);
    if (f == NULL) {
        printf("שגיאה בפתיחת '%s': %s\n", filename, strerror(errno));
        return NULL;
    }
    printf("הקובץ '%s' נפתח בהצלחה\n", filename);
    return f;
}

int main() {
    FILE *f1 = safe_open("missing.txt", "r");
    // f1 הוא NULL, אין צורך לסגור

    FILE *f2 = safe_open("/etc/hostname", "r");
    if (f2 != NULL) {
        fclose(f2);
    }

    FILE *f3 = safe_open("/etc/shadow", "r");
    // f3 הוא NULL, אין צורך לסגור

    return 0;
}

הפונקציה safe_open עוטפת את fopen ומוסיפה טיפול בשגיאות מובנה. זוהי תבנית נפוצה - במקום לכתוב בדיקת שגיאה בכל מקום שפותחים קובץ, כותבים פונקציית עזר אחת שעושה את זה.


תרגיל 5 - פתרון: כתיבת לוג שגיאות לקובץ

#include <stdio.h>
#include <errno.h>
#include <string.h>

int main() {
    const char *files[] = {
        "/etc/hostname",
        "missing1.txt",
        "/etc/passwd",
        "missing2.txt"
    };
    int n = sizeof(files) / sizeof(files[0]);

    // פתיחת קובץ הלוג
    FILE *log = fopen("errors.log", "w");
    if (log == NULL) {
        perror("לא ניתן לפתוח את קובץ הלוג");
        return 1;
    }

    for (int i = 0; i < n; i++) {
        FILE *f = fopen(files[i], "r");
        if (f == NULL) {
            int err_code = errno;  // שמירת errno לפני קריאות נוספות
            fprintf(log, "שגיאה: %s | קוד: %d | תיאור: %s\n",
                    files[i], err_code, strerror(err_code));
            printf("נכשל: %s\n", files[i]);
        } else {
            printf("הצליח: %s\n", files[i]);
            fclose(f);
        }
    }

    fclose(log);
    printf("הלוג נכתב לקובץ errors.log\n");
    return 0;
}

שימו לב לנקודה חשובה: שומרים את errno למשתנה מקומי (err_code) מיד אחרי הפעולה שנכשלה, לפני שקוראים לפונקציות נוספות כמו fprintf או printf - כי גם הן עלולות לשנות את errno אם הן נתקלות בבעיה. זוהי תבנית טובה שמונעת באגים קשים לאיתור.