Page Content

Tutorials

What Is The C Error Handling During File Operations?

C Error Handling

Since a number of problems can make file operations fail, error handling is an essential part of file input/output in C. Functions in the Standard C Library, which are mostly defined in , are used by C programs to conduct file I/O. These features frequently offer methods for identifying and reporting mistakes.

The following describes how to handle errors in C file operations:

Checking if a File Could Be Opened (fopen)

Using fopen() to try to open a file is the first stage where an error may occur. A filename and an access mode are passed as parameters to the fopen() function. When successful, it produces a pointer to a FILE structure; however, if the file cannot be accessed for any reason, it returns NULL.

Verifying whether fopen() returned NULL after trying to open a file is crucial. The FILE pointer is incorrect if it returns NULL, and using it will result in problems.

Among the causes of fopen failure are:

  • Reading a file that doesn’t exist (“r” mode).
  • Opening a file without the proper authorisation to read or write.
  • When there is no disc space left, a file is opened for writing.
  • A file with an incorrect filename is being opened.
  • Trying to open an excessive number of files at once, since the operating system limits the quantity of files that can be opened.

Checking the return value right after the fopen call is a typical practice:

#include <stdio.h>
#include <stdlib.h> // For exit() and EXIT_FAILURE 
int main() {
    FILE *fp = NULL;
    const char *filename = "non_existent_file.txt";
    // Attempt to open the file for reading
    fp = fopen(filename, "r"); // "r" requires the file to exist
    // Check if fopen was successful 
    if (fp == NULL) { // Use == NULL to check for failure
        // Print an error message 
        fprintf(stderr, "Error: Could not open file \"%s\" for reading.\n", filename); 
        // Indicate failure and terminate the program 
        return EXIT_FAILURE; // Use EXIT_FAILURE from <stdlib.h> 
        // Or exit(EXIT_FAILURE); 
    }
    // If successful, proceed with file operations
    printf("File \"%s\" opened successfully.\n", filename);
    // ... perform reading ...
    // Close the file when done
    fclose(fp); 
    printf("File \"%s\" closed.\n", filename);
    return EXIT_SUCCESS; // Indicate success
}

Output

Error: Could not open file "non_existent_file.txt" for reading.

It is common practice to combine the assignment and the check against NULL into a single if statement:

if ((fp = fopen(filename, "r")) == NULL) { 
    fprintf(stderr, "Error: Could not open file \"%s\".\n", filename);
    return EXIT_FAILURE;
}

If the file doesn’t already exist, fopen creates it for writing in “w” mode; if it does, it immediately deletes its contents. This is a typical logical fallacy if data preservation was your goal. Exclusive write mode (“wx”, “w+x”, etc.) was added in C11, in which fopen fails if the file already exists.

Checking for End-Of-File During Reading (feof)

You must be aware of when you have reached the end of a file when reading data from it consecutively. Reading past the end may result in mistakes or endless loops. The feof() method detects end-of-file situations.

feof(FILE *fp): Needs FILE pointer fp. It returns a non-zero value if the file’s end-of-file indicator is set, zero otherwise. After reading the last data, the end-of-file indicator is set.

Use feof() with input functions like fgetc, fgets, fscanf, and fread to manage reading cycles.The feof() method detects end-of-file situations.

#include <stdio.h>
int main() {
    FILE *fp = NULL;
    int c;
    const char *filename = "my_file.txt"; // Assuming this file exists for reading
    fp = fopen(filename, "r");
    if (fp == NULL) {
        perror("Error opening file"); // Using perror here
        return 1;
    }
    printf("Reading from \"%s\":\n", filename);
    // Read characters until the end-of-file is reached 
    // The fgetc function returns EOF when the end of file is encountered 
    while ((c = fgetc(fp)) != EOF) { 
        // Process the character, e.g., print it
        putchar(c);
    }
    // After the loop, feof() would return non-zero if reading stopped because of EOF
    if (feof(fp)) { 
        printf("\nEnd of file reached.\n"); 
    }
    // fgetc itself returns EOF on end-of-file or error, making the while condition robust 
    fclose(fp); // Close the file
    return 0;
}

Output

Error opening file: No such file or directory 

Because the EOF indicator is only set after an attempt to read past the end, it is more reliable to check feof() after a read operation has already failed (for example, returned EOF for fgetc) than to check it before each read. The read and the EOF/error check are successfully combined by the while ((c = fgetc(fp))!= EOF) pattern.

Checking for Other I/O Errors (ferror)

In addition to reaching the file’s end, read or write operations may result in additional errors. Examples include interruptions, device problems, or attempting to read from a file that has only been opened for writing. You may look for these kinds of issues with the ferror() function.

ferror(FILE *fp): A FILE pointer fp is required. If the error indication has been set for the given file stream, it returns a non-zero value; if not, it returns zero. When an error happens during an I/O operation, the error indicator is set. Clearerr() or a file positioning function (fseek, fsetpos, rewind) must explicitly clear the error indicator before it can be removed.

Ferror() should be checked following a series of I/O operations or whenever an I/O function produces a possible failure indicator (such as EOF for fgetc or a short count for fread/fwrite).

Example

#include <stdio.h>
int main() {
    FILE *fp = NULL;
    int c;
    const char *filename = "my_file.txt"; // Assuming this file exists for reading
    fp = fopen(filename, "r");
    if (fp == NULL) {
        perror("Error opening file");
        return 1;
    }
    printf("Reading from \"%s\":\n", filename);
    // Loop, checking for both EOF and errors 
    while (1) { // Loop indefinitely
        c = fgetc(fp); // Attempt to read a character
        if (c == EOF) { // Check for EOF or error
            // If EOF or error, check specifically using ferror() 
            if (ferror(fp)) { 
                printf("Error reading from file.\n"); 
                // Specific error handling can go here
            } else if (feof(fp)) { // Check if it was just the end of the file
                 printf("\nEnd of file reached.\n");
            }
            break; // Exit the loop in either case (EOF or error)
        }
        // If no EOF and no error (c is a valid character)
        putchar(c);
    }
    // You could also check ferror() once after the loop if you didn't check inside
    // if (ferror(fp)) { /* handle error */ }
    fclose(fp); // Close the file
    return 0;
}

Output

Error opening file: No such file or directory

For binary I/O or complex sequences, where only verifying the return value of a single function call might not be enough to differentiate between an EOF and a read/write error, validating ferror() is very crucial.

Reporting System Errors (perror, strerror, errno)

An internal error code is frequently set in a global variable called errno when a standard library function fails. This errno variable, usually a macro specified in , has an integer number that indicates the particular error that took place. Only just after a function indicates failure does the value of errno have any significance.

You can use the following to obtain a description of the error linked to errno that is accessible by humans:

perror(const char *s): An error message is printed to the standard error stream (stderr) using this function. It prints the string you supply first, then a colon and a space, and finally a newline at the end with the system error message that corresponds to the current value of errno. Because system error messages are frequently localised and compiler/system specific, this is helpful. perror(“Error opening file”); is an example of its use.

strerror(int errornum): This method returns a pointer to a C string with the human-readable error explanation after receiving an error number as input (such as the value in errno). You can use fprintf to stderr to print this string manually, for instance.

The simplest method for reporting failures that library functions encounter is frequently to use perror:

Example

#include <stdio.h>
#include <stdlib.h>
int main() {
    FILE *fp = NULL;
    const char *filename = "some_protected_file.dat"; // Example: maybe read-only
    fp = fopen(filename, "w"); // Attempt to open for writing
    if (fp == NULL) {
        // Use perror to print a helpful error message including the system's explanation
        perror("Could not open file for writing");
        return EXIT_FAILURE;
    }
    // ... file operations ...
    fclose(fp);
    return EXIT_SUCCESS;
}

Even when the standard output (stdout) is diverted to a file, error messages are normally displayed via the stderr stream, which is usually connected to the screen. This guarantees that the user will notice error messages right away.

Checking Return Values of Other File Functions

Return values that indicate success or failure are also provided by various file functions in addition to opening, basic character I/O, and end-of-file/error indicators. In “industrial-strength code,” it is crucial to verify certain return values:

fscanf(FILE *fp, …): Gives back how many input items were assigned and matched successfully. If an error arises before any items are matched, EOF is returned. Make that the return value corresponds to the anticipated quantity of items to be read.

fprintf(FILE *fp, …): Gives back how many characters were written. On error, a negative value is returned.

fread(void *buffer, size_t size, size_t n, FILE *fp): Gives back how many items were successfully read. A read error occurred or the end-of-file was reached if the returned number was less than n. To differentiate between an EOF and a read error, you would normally perform a brief count check using feof and ferror.

fwrite(const void *buffer, size_t size, size_t n, FILE *fp): Gives back how many things were written successfully. A write error occurred if the number that was returned was less than n.

fseek(FILE *fp, …): If the search operation is unsuccessful, it returns a non-zero value. Over-rewinding is advised since it doesn’t produce an error message.

fclose(FILE *fp): Returns EOF on error and zero on success. Checking it guarantees that buffered data is flushed, even if it is frequently disregarded.

remove(const char *filename): On failure, a non-zero value is returned.

rename(const char *old, const char *new): On failure, a non-zero value is returned.

You can write reliable programs that gracefully accept file operation errors by regularly verifying the return values of C’s file I/O functions, using feof and ferror where necessary, and reporting with perror.

You can also read What Is C Dynamic Memory Allocation With Code Example?

Agarapu Geetha
Agarapu Geetha
My name is Agarapu Geetha, a B.Com graduate with a strong passion for technology and innovation. I work as a content writer at Govindhtech, where I dedicate myself to exploring and publishing the latest updates in the world of tech.
Index