C Programming Basics: Part 9 – File Handling

Meta Description: Master C file handling with practical examples. Learn to read, write, and manipulate files while avoiding common pitfalls in C programming.

Welcome to the ninth part of our C programming tutorial series! In previous articles, we’ve covered foundational concepts like variables, operators, control flow, functions, arrays, strings, structures, and pointers. Now, we’ll explore file handling – an essential skill that allows your programs to interact with the file system to store and retrieve data.

File handling is crucial for creating programs that can save their state, process large datasets, log information, or read configuration settings. In this article, we’ll explore how C handles files and provides functions for various file operations.

Understanding File Handling Concepts

In C, a file is treated as a stream – a sequence of bytes that can be accessed sequentially or randomly. The standard library provides a set of functions to work with files through the FILE structure defined in the <stdio.h> header.

graph TD
    A[Start Program] --> B{"Need File Access?"}
    B -- Yes --> C["fopen()"]
    C -- "Success (File Pointer != NULL)" --> D{"Read or Write?"}
    C -- "Failure (File Pointer == NULL)" --> E["Handle Error / Exit"]
    D -- Read --> F["Read: fgetc(), fgets(), fscanf(), fread()"]
    D -- Write --> G["Write: fputc(), fputs(), fprintf(), fwrite()"]
    F --> H{"More Operations?"}
    G --> H
    H -- Yes --> D
    H -- No --> I["fclose()"]
    I --> J["End Program / Continue"]
    E --> J

File Pointers

To work with a file, you need a file pointer (a pointer to a FILE structure):

C
FILE *filePointer;

The FILE structure contains information about the file being accessed, including its position indicator, error indicators, and a buffer.

Common File Operations

The basic file operations in C include:

  1. Opening a file: Establishes a connection between your program and the file
  2. Reading from a file: Retrieves data from the file
  3. Writing to a file: Stores data in the file
  4. Closing a file: Terminates the connection when you’re done

File Access Modes

When opening a file, you specify an access mode that determines what operations are allowed:

ModeDescriptionFile ExistenceOperation
"r"Open for readingMust existRead
"w"Open for writing (truncates if exists)Creates if not existWrite
"a"Open for appending (writing at the end)Creates if not existAppend
"r+"Open for reading and writingMust existRead/Write
"w+"Open for reading and writing (truncates if exists)Creates if not existRead/Write
"a+"Open for reading and appendingCreates if not existRead/Append
bAdd ‘b’ for binary mode (e.g., "rb", "wb+") – optionalN/ABinary operation

Add b to the mode string for binary mode (e.g., "rb""wb") when working with binary files.

Opening and Closing Files

Opening a File

The fopen() function opens a file and returns a pointer to the file:

C
FILE *fopen(const char *filename, const char *mode);

Example:

C
#include <stdio.h>

int main() {
    FILE *file;
    
    // Open a file for reading
    file = fopen("data.txt", "r");
    
    // Always check if file opening was successful
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // File operations would go here
    
    return 0;
}

Closing a File

After you’re done with a file, you should close it with the fclose() function:

C
int fclose(FILE *stream);

Always close files when you’re done with them to:

  • Free up system resources
  • Ensure data is properly written (flush buffers)
  • Allow other programs to access the file

Example:

C
#include <stdio.h>

int main() {
    FILE *file;
    
    file = fopen("data.txt", "r");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // File operations...
    
    // Close the file when done
    fclose(file);
    
    return 0;
}

3. Checking End-of-File Condition

The feof() function checks if the end-of-file indicator is set:

C
#include <stdio.h>

int main() {
    FILE *file;
    int c;
    
    file = fopen("data.txt", "r");
    
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }
    
    // Read until EOF
    while ((c = fgetc(file)) != EOF) {
        putchar(c);
    }
    
    // Check if we reached EOF normally or due to an error
    if (feof(file)) {
        printf("\nEnd of file reached successfully\n");
    } else if (ferror(file)) {
        perror("Error reading file");
    }
    
    fclose(file);
    
    return 0;
}

4. Checking Error Indicator

The ferror() function checks if the error indicator is set for a stream:

C
if (ferror(file)) {
    printf("An error occurred while reading the file\n");
    clearerr(file);  // Clear the error indicator
}

5. Clearing Error Indicators

The clearerr() function clears both the end-of-file and error indicators:

C
clearerr(file);

Standard Streams

C provides three standard streams that are automatically opened when your program starts:

  1. stdin: Standard input (keyboard by default)
  2. stdout: Standard output (screen by default)
  3. stderr: Standard error (screen by default)
StreamDefault Source/DestinationDescription
stdinKeyboardStandard Input
stdoutScreenStandard Output
stderrScreenStandard Error

These are file pointers that you can use with the file I/O functions:

C
#include <stdio.h>

int main() {
    char name[50];
    
    printf("Enter your name: ");  // Using stdout
    fgets(name, sizeof(name), stdin);  // Using stdin
    
    fprintf(stdout, "Hello, %s", name);  // Equivalent to printf
    fprintf(stderr, "This is an error message\n");  // Write to stderr
    
    return 0;
}

Standard streams can be redirected from/to files or other programs in the command line:

C
# Redirect input from a file
./program < input.txt

# Redirect output to a file
./program > output.txt

# Redirect error to a file
./program 2> error.txt

# Redirect both output and error to a file
./program > output.txt 2>&1

graph TD
    subgraph "Default"
        Keyboard --> stdin;
        stdout --> Screen;
        stderr --> Screen2[Screen];
        Program --> stdin;
        Program --> stdout;
        Program --> stderr;
    end

    subgraph "Input Redirection (<)"
        InputFile[input.txt] --> stdin_redir{stdin};
        Program_redir[Program] --> stdin_redir;
        Program_redir --> stdout_redir[stdout];
        Program_redir --> stderr_redir[stderr];
        stdout_redir --> Screen_redir[Screen];
        stderr_redir --> Screen2_redir[Screen];
    end

    subgraph "Output Redirection (>)"
        Keyboard_redir2[Keyboard] --> stdin_redir2{stdin};
        Program_redir2[Program] --> stdin_redir2;
        Program_redir2 --> stdout_redir2{stdout};
        Program_redir2 --> stderr_redir2[stderr];
        stdout_redir2 --> OutputFile[output.txt];
        stderr_redir2 --> Screen_redir2[Screen];
    end

    subgraph "Error Redirection (2>)"
        Keyboard_redir3[Keyboard] --> stdin_redir3{stdin};
        Program_redir3[Program] --> stdin_redir3;
        Program_redir3 --> stdout_redir3[stdout];
        Program_redir3 --> stderr_redir3{stderr};
        stdout_redir3 --> Screen_redir3[Screen];
        stderr_redir3 --> ErrorFile[error.txt];
    end

File Information and Manipulation

C provides functions to get information about files and perform operations on them:

FunctionPurposeKey Parameter(s)
fopen()Check existence (if opened in “r”)filename, mode
fseek/ftellGet file size (seek to end)filename
remove()Deletes a filefilename
rename()Renames or moves a fileoldname, newname
tmpfile()Creates a temporary binary file (“wb+”)None

1. Checking if a File Exists

C
#include <stdio.h>

int main() {
    const char *filename = "data.txt";
    FILE *file = fopen(filename, "r");
    
    if (file != NULL) {
        printf("File '%s' exists\n", filename);
        fclose(file);
    } else {
        printf("File '%s' does not exist or cannot be opened\n", filename);
    }
    
    return 0;
}

2. Getting File Size

C
#include <stdio.h>

long getFileSize(const char *filename) {
    FILE *file = fopen(filename, "rb");
    long size = -1;
    
    if (file != NULL) {
        // Go to the end of the file
        fseek(file, 0, SEEK_END);
        
        // Get the position (which is the size)
        size = ftell(file);
        
        fclose(file);
    }
    
    return size;
}

int main() {
    const char *filename = "data.txt";
    long size = getFileSize(filename);
    
    if (size >= 0) {
        printf("Size of '%s': %ld bytes\n", filename, size);
    } else {
        printf("Error getting size of '%s'\n", filename);
    }
    
    return 0;
}

3. Deleting a File

C
#include <stdio.h>

int main() {
    const char *filename = "temp.txt";
    
    // Create a temporary file
    FILE *file = fopen(filename, "w");
    if (file != NULL) {
        fputs("This is a temporary file", file);
        fclose(file);
        printf("File '%s' created\n", filename);
    }
    
    // Delete the file
    if (remove(filename) == 0) {
        printf("File '%s' deleted successfully\n", filename);
    } else {
        perror("Error deleting file");
    }
    
    return 0;
}

4. Renaming a File

C
#include <stdio.h>

int main() {
    const char *oldname = "old.txt";
    const char *newname = "new.txt";
    
    // Create a file with the old name
    FILE *file = fopen(oldname, "w");
    if (file != NULL) {
        fputs("This file will be renamed", file);
        fclose(file);
        printf("File '%s' created\n", oldname);
    }
    
    // Rename the file
    if (rename(oldname, newname) == 0) {
        printf("File renamed successfully from '%s' to '%s'\n", oldname, newname);
    } else {
        perror("Error renaming file");
    }
    
    return 0;
}

Temporary Files

Sometimes you need a file for temporary storage. C provides functions to create and manage temporary files:

C
#include <stdio.h>

int main() {
    // Create a temporary file
    FILE *tempFile = tmpfile();
    
    if (tempFile == NULL) {
        perror("Error creating temporary file");
        return 1;
    }
    
    // Write to the temporary file
    fputs("This is data in a temporary file\n", tempFile);
    
    // Go back to the beginning of the file
    rewind(tempFile);
    
    // Read from the temporary file
    char buffer[100];
    if (fgets(buffer, sizeof(buffer), tempFile) != NULL) {
        printf("Read from temp file: %s", buffer);
    }
    
    // The temporary file is automatically deleted when closed
    fclose(tempFile);
    
    return 0;
}

The tmpfile() function creates a temporary file that is automatically deleted when it is closed or when the program terminates.

Buffering

When reading from or writing to files, C uses buffering to improve performance by reducing the number of actual I/O operations:

Buffer Types

  1. Fully buffered: Output is saved in the buffer until it’s full.
  2. Line buffered: Output is saved until a newline character is encountered.
  3. Unbuffered: Output is written as soon as possible.

Controlling Buffering

The setvbuf() function lets you control buffering behavior:

C
#include <stdio.h>

int main() {
    FILE *file = fopen("output.txt", "w");
    
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }
    
    // Set line buffering with a buffer size of 1024 bytes
    char buffer[1024];
    setvbuf(file, buffer, _IOLBF, sizeof(buffer));
    
    // Write to the file
    fputs("Line 1\n", file);  // Written immediately due to newline
    fputs("Line 2 without newline", file);  // Stays in buffer
    
    fclose(file);  // Buffer is flushed on close
    
    return 0;
}

Flushing the Buffer

The fflush() function forces any buffered data to be written immediately:

C
#include <stdio.h>

int main() {
    FILE *file = fopen("log.txt", "w");
    
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }
    
    // Write critical information
    fputs("Critical operation started\n", file);
    fflush(file);  // Ensure it's written to disk immediately
    
    // Perform the critical operation
    // ...
    
    fputs("Critical operation completed\n", file);
    fflush(file);
    
    fclose(file);
    
    return 0;
}

Practical Example: Simple Text Editor

Let’s put everything together in a practical example – a simple text editor that can create, open, edit, and save text files:

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

#define MAX_FILENAME 100
#define MAX_LINE_LENGTH 1000
#define MAX_LINES 1000

// Function prototypes
void displayMenu();
void createNewFile();
void openFile();
void displayContent(char lines[][MAX_LINE_LENGTH], int lineCount);
void editLine(char lines[][MAX_LINE_LENGTH], int lineCount);
void saveFile(char lines[][MAX_LINE_LENGTH], int lineCount, const char *filename);

int main() {
    int choice;
    
    do {
        displayMenu();
        printf("Enter your choice: ");
        scanf("%d", &choice);
        getchar();  // Consume newline
        
        switch (choice) {
            case 1:
                createNewFile();
                break;
            case 2:
                openFile();
                break;
            case 3:
                printf("Exiting program.\n");
                break;
            default:
                printf("Invalid choice. Please try again.\n");
        }
    } while (choice != 3);
    
    return 0;
}

void displayMenu() {
    printf("\n===== Simple Text Editor =====\n");
    printf("1. Create a new file\n");
    printf("2. Open an existing file\n");
    printf("3. Exit\n");
}

void createNewFile() {
    char filename[MAX_FILENAME];
    char lines[MAX_LINES][MAX_LINE_LENGTH];
    int lineCount = 0;
    int choice;
    
    printf("Enter filename: ");
    fgets(filename, MAX_FILENAME, stdin);
    filename[strcspn(filename, "\n")] = '\0';  // Remove newline
    
    printf("Enter text (type 'END' on a new line to finish):\n");
    
    while (lineCount < MAX_LINES) {
        fgets(lines[lineCount], MAX_LINE_LENGTH, stdin);
        
        // Remove newline character
        lines[lineCount][strcspn(lines[lineCount], "\n")] = '\0';
        
        // Check for exit condition
        if (strcmp(lines[lineCount], "END") == 0) {
            lineCount--;  // Don't include the "END" line
            break;
        }
        
        lineCount++;
    }
    
    do {
        printf("\nWhat would you like to do?\n");
        printf("1. Display content\n");
        printf("2. Edit a line\n");
        printf("3. Save and exit\n");
        printf("Enter your choice: ");
        scanf("%d", &choice);
        getchar();  // Consume newline
        
        switch (choice) {
            case 1:
                displayContent(lines, lineCount);
                break;
            case 2:
                editLine(lines, lineCount);
                break;
            case 3:
                saveFile(lines, lineCount, filename);
                printf("File saved successfully.\n");
                return;
            default:
                printf("Invalid choice. Please try again.\n");
        }
    } while (choice != 3);
}

void openFile() {
    char filename[MAX_FILENAME];
    char lines[MAX_LINES][MAX_LINE_LENGTH];
    int lineCount = 0;
    int choice;
    
    printf("Enter filename to open: ");
    fgets(filename, MAX_FILENAME, stdin);
    filename[strcspn(filename, "\n")] = '\0';  // Remove newline
    
    FILE *file = fopen(filename, "r");
    
    if (file == NULL) {
        perror("Error opening file");
        return;
    }
    
    // Read the file content
    while (lineCount < MAX_LINES && fgets(lines[lineCount], MAX_LINE_LENGTH, file) != NULL) {
        // Remove newline character
        lines[lineCount][strcspn(lines[lineCount], "\n")] = '\0';
        lineCount++;
    }
    
    fclose(file);
    
    printf("File loaded successfully.\n");
    
    do {
        printf("\nWhat would you like to do?\n");
        printf("1. Display content\n");
        printf("2. Edit a line\n");
        printf("3. Save and exit\n");
        printf("Enter your choice: ");
        scanf("%d", &choice);
        getchar();  // Consume newline
        
        switch (choice) {
            case 1:
                displayContent(lines, lineCount);
                break;
            case 2:
                editLine(lines, lineCount);
                break;
            case 3:
                saveFile(lines, lineCount, filename);
                printf("File saved successfully.\n");
                return;
            default:
                printf("Invalid choice. Please try again.\n");
        }
    } while (choice != 3);
}

void displayContent(char lines[][MAX_LINE_LENGTH], int lineCount) {
    printf("\n===== File Content =====\n");
    
    for (int i = 0; i < lineCount; i++) {
        printf("%3d: %s\n", i + 1, lines[i]);
    }
    
    printf("======================\n");
}

void editLine(char lines[][MAX_LINE_LENGTH], int lineCount) {
    int lineNum;
    
    displayContent(lines, lineCount);
    
    printf("Enter line number to edit (1-%d): ", lineCount);
    scanf("%d", &lineNum);
    getchar();  // Consume newline
    
    if (lineNum < 1 || lineNum > lineCount) {
        printf("Invalid line number.\n");
        return;
    }
    
    printf("Current content: %s\n", lines[lineNum - 1]);
    printf("Enter new content: ");
    fgets(lines[lineNum - 1], MAX_LINE_LENGTH, stdin);
    
    // Remove newline character
    lines[lineNum - 1][strcspn(lines[lineNum - 1], "\n")] = '\0';
    
    printf("Line updated successfully.\n");
}

void saveFile(char lines[][MAX_LINE_LENGTH], int lineCount, const char *filename) {
    FILE *file = fopen(filename, "w");
    
    if (file == NULL) {
        perror("Error opening file for writing");
        return;
    }
    
    for (int i = 0; i < lineCount; i++) {
        fprintf(file, "%s\n", lines[i]);
    }
    
    fclose(file);
}

graph TD
    A["Start Editor"] --> B["Display Menu"]
    B --> C{"Choose Option"}
    
    C -- "1. Create New" --> D["Get Filename"]
    D --> E["Input Text (until 'END')"]
    E --> F["File Actions"]
    
    C -- "2. Open Existing" --> G["Get Filename"]
    G --> H["fopen('r')"]
    
    H -- "Success" --> I["Read Content into Memory"]
    H -- "Failure" --> J["Error Message"]
    
    I --> F
    J --> B
    
    F --> K{"Choose Action"}
    
    K -- "1. Display" --> L["Display Content"]
    L --> F
    
    K -- "2. Edit" --> M["Select Line & Edit"]
    M --> F
    
    K -- "3. Save & Exit" --> N["saveFile() → fopen('w')"]
    N --> O["Write Memory to File"]
    O --> P["fclose()"]
    P --> Q["Exit to Main Menu / Exit Program"]
    
    C -- "3. Exit" --> R["Exit Program"]
    Q --> B

This text editor program demonstrates many file handling concepts:

– Opening files in different modes

– Reading from and writing to files

– Managing file content in memory

– Handling user interactions with file data

Reading from Files

C offers several ways to read data from files, depending on your needs:

FunctionReadsInput TypeStops AtReturn ValueNotes
fgetc()CharacterTextEOFCharacter (int) or EOFReads one character at a time.
fgets()String (line)TextNewline, EOF, or buffer fullBuffer pointer or NULLSafer than gets(), includes newline.
fscanf()FormattedTextWhitespace or format specifierNumber of items readSimilar to scanf() but reads from file.
fread()Binary dataBinarySpecified count or EOFNumber of items readReads raw bytes, good for structures.

1. Reading Characters – fgetc()

C
#include <stdio.h>

int main() {
    FILE *file;
    int c;
    
    file = fopen("data.txt", "r");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Read and print each character
    while ((c = fgetc(file)) != EOF) {
        printf("%c", c);
    }
    
    fclose(file);
    
    return 0;
}

fgetc() reads a single character from the file and returns it as an integer, or EOF (end-of-file) when it reaches the end.

2. Reading Strings – fgets()

C
#include <stdio.h>

int main() {
    FILE *file;
    char buffer[100];
    
    file = fopen("data.txt", "r");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Read and print each line
    while (fgets(buffer, sizeof(buffer), file) != NULL) {
        printf("%s", buffer);
    }
    
    fclose(file);
    
    return 0;
}

fgets() reads up to n-1 characters into the buffer, stopping when it encounters a newline or EOF. It adds a null terminator at the end.

3. Formatted Reading – fscanf()

C
#include <stdio.h>

int main() {
    FILE *file;
    int id;
    char name[50];
    float score;
    
    file = fopen("students.txt", "r");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    printf("ID\tName\t\tScore\n");
    printf("-------------------------------\n");
    
    // Read formatted data
    while (fscanf(file, "%d %s %f", &id, name, &score) == 3) {
        printf("%d\t%-10s\t%.1f\n", id, name, score);
    }
    
    fclose(file);
    
    return 0;
}

fscanf() reads formatted data from a file, similar to how scanf() reads from standard input. The return value indicates the number of items successfully matched and assigned.

4. Binary Reading – fread()

C
#include <stdio.h>

struct Student {
    int id;
    char name[50];
    float score;
};

int main() {
    FILE *file;
    struct Student student;
    
    file = fopen("students.bin", "rb");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    printf("ID\tName\t\tScore\n");
    printf("-------------------------------\n");
    
    // Read binary data
    while (fread(&student, sizeof(struct Student), 1, file) == 1) {
        printf("%d\t%-10s\t%.1f\n", student.id, student.name, student.score);
    }
    
    fclose(file);
    
    return 0;
}

fread() reads binary data from a file. It’s useful for reading structured data or when you need to maintain the exact binary representation.

Writing to Files

Similar to reading, C offers multiple ways to write data to files:

FunctionWritesInput TypeAdds NewlineReturn ValueNotes
fputc()CharacterTextNoCharacter (int) or EOFWrites one character at a time.
fputs()StringTextNoNon-negative or EOFWrites a string (null terminator not written).
fprintf()FormattedTextOptionalNumber of chars writtenSimilar to printf() but writes to file.
fwrite()Binary dataBinaryNoNumber of items writtenWrites raw bytes, good for structures.

1. Writing Characters – fputc()

C
#include <stdio.h>

int main() {
    FILE *file;
    char c;
    
    file = fopen("output.txt", "w");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Write a string character by character
    char *message = "Hello, File Handling!";
    for (int i = 0; message[i] != '\0'; i++) {
        fputc(message[i], file);
    }
    
    fclose(file);
    printf("Data written successfully\n");
    
    return 0;
}

fputc() writes a single character to a file.

2. Writing Strings – fputs()

C
#include <stdio.h>

int main() {
    FILE *file;
    
    file = fopen("output.txt", "w");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Write strings to the file
    fputs("Line 1: C Programming\n", file);
    fputs("Line 2: File Handling\n", file);
    fputs("Line 3: Using fputs\n", file);
    
    fclose(file);
    printf("Data written successfully\n");
    
    return 0;
}

fputs() writes a string to a file without adding a newline (unless you include it in the string).

3. Formatted Writing – fprintf()

C
#include <stdio.h>

int main() {
    FILE *file;
    
    file = fopen("students.txt", "w");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Write formatted data
    fprintf(file, "%d %s %.1f\n", 101, "John", 85.5);
    fprintf(file, "%d %s %.1f\n", 102, "Alice", 92.0);
    fprintf(file, "%d %s %.1f\n", 103, "Bob", 78.5);
    
    fclose(file);
    printf("Data written successfully\n");
    
    return 0;
}

fprintf() writes formatted data to a file, similar to how printf() writes to standard output.

4. Binary Writing – fwrite()

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

struct Student {
    int id;
    char name[50];
    float score;
};

int main() {
    FILE *file;
    struct Student students[3];
    
    // Initialize student records
    students[0].id = 101;
    strcpy(students[0].name, "John");
    students[0].score = 85.5;
    
    students[1].id = 102;
    strcpy(students[1].name, "Alice");
    students[1].score = 92.0;
    
    students[2].id = 103;
    strcpy(students[2].name, "Bob");
    students[2].score = 78.5;
    
    file = fopen("students.bin", "wb");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Write binary data
    fwrite(students, sizeof(struct Student), 3, file);
    
    fclose(file);
    printf("Data written successfully\n");
    
    return 0;
}

fwrite() writes binary data to a file. It’s the counterpart to fread() and is used for the same purposes.

Random Access to Files

Sometimes you need to read or write data at specific positions in a file rather than sequentially. C provides functions to move the file position indicator:

FunctionPurposeKey Parameters
fseek()Moves the file position indicatoroffset, whence (SEEK_SET, SEEK_CUR, SEEK_END)
ftell()Gets the current file position indicatorNone
rewind()Resets the file position indicator to beginningNone

1. fseek() – Repositioning the File Position Indicator

C
int fseek(FILE *stream, long offset, int whence);

The whence parameter can be:

  • SEEK_SET: Beginning of file
  • SEEK_CUR: Current position
  • SEEK_END: End of file

Example:

C
#include <stdio.h>

int main() {
    FILE *file;
    char buffer[100];
    
    file = fopen("data.txt", "r");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Go to the 10th byte in the file
    fseek(file, 10, SEEK_SET);
    
    // Read from that position
    fgets(buffer, sizeof(buffer), file);
    printf("Data from position 10: %s", buffer);
    
    // Move 5 bytes forward from current position
    fseek(file, 5, SEEK_CUR);
    
    // Read from the new position
    fgets(buffer, sizeof(buffer), file);
    printf("Data after moving 5 bytes: %s", buffer);
    
    // Go to 10 bytes before the end of file
    fseek(file, -10, SEEK_END);
    
    // Read from that position
    fgets(buffer, sizeof(buffer), file);
    printf("Last 10 bytes of file: %s", buffer);
    
    fclose(file);
    
    return 0;
}

2. ftell() – Getting the Current Position

C
long ftell(FILE *stream);

Returns the current position of the file position indicator:

C
#include <stdio.h>

int main() {
    FILE *file;
    long position;
    
    file = fopen("data.txt", "r");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Read the first line
    char buffer[100];
    fgets(buffer, sizeof(buffer), file);
    
    // Get current position
    position = ftell(file);
    printf("Current position after reading first line: %ld\n", position);
    
    fclose(file);
    
    return 0;
}

3. rewind() – Reset to Beginning

C
void rewind(FILE *stream);

Sets the file position indicator to the beginning of the file:

C
#include <stdio.h>

int main() {
    FILE *file;
    
    file = fopen("data.txt", "r");
    
    if (file == NULL) {
        printf("Error opening file\n");
        return 1;
    }
    
    // Read some data
    char buffer[100];
    fgets(buffer, sizeof(buffer), file);
    printf("First read: %s", buffer);
    
    // Go back to the beginning
    rewind(file);
    
    // Read again from the beginning
    fgets(buffer, sizeof(buffer), file);
    printf("After rewind: %s", buffer);
    
    fclose(file);
    
    return 0;
}

Error Handling in File Operations

File operations can fail for various reasons, such as permission issues, disk space limitations, or non-existent files. Proper error handling is crucial:

FunctionPurposeChecks/Clears
fopen()Check return value (NULL indicates error)File open status
perror()Prints system error message based on errnoLast system error
feof()Checks if the End-Of-File indicator is setEOF indicator
ferror()Checks if the error indicator is set for a streamStream error indicator
clearerr()Clears EOF and error indicators for a streamEOF & error indicators

1. Checking File Open Errors

C
FILE *file = fopen("nonexistent.txt", "r");
if (file == NULL) {
    perror("Error opening file");
    return 1;
}

The perror() function prints a descriptive error message based on the value of errno.

2. Checking Read/Write Errors

C
#include <stdio.h>

int main() {
    FILE *file;
    char buffer[100];
    
    file = fopen("data.txt", "r");
    
    if (file == NULL) {
        perror("Error opening file");
        return 1;
    }
    
    // Check for read errors
    if (fgets(buffer, sizeof(buffer), file) == NULL) {
        if (feof(file)) {
            printf("End of file reached\n");
        } else {
            perror("Error reading from file");
        }
    } else {
        printf("Read successful: %s\n", buffer);
    }
    
    fclose(file);
    
    return 0;
}

Binary File Handling


While text files are human-readable, binary files store data in its native binary format. This is more efficient for structured data like records:

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

struct Person {
    int id;
    char name[50];
    float salary;
};

void writeBinaryFile() {
    FILE *file = fopen("employees.bin", "wb");
    
    if (file == NULL) {
        perror("Error opening file for writing");
        return;
    }
    
    struct Person people[3] = {
        {1, "John Doe", 50000.0},
        {2, "Jane Smith", 60000.0},
        {3, "Bob Johnson", 45000.0}
    };
    
    // Write the entire array at once
    fwrite(people, sizeof(struct Person), 3, file);
    
    fclose(file);
    printf("Binary file written successfully\n");
}

void readBinaryFile() {
    FILE *file = fopen("employees.bin", "rb");
    
    if (file == NULL) {
        perror("Error opening file for reading");
        return;
    }
    
    struct Person person;
    
    printf("\nEmployee Records:\n");
    printf("------------------\n");
    
    // Read records one by one
    while (fread(&person, sizeof(struct Person), 1, file) == 1) {
        printf("ID: %d\n", person.id);
        printf("Name: %s\n", person.name);
        printf("Salary: $%.2f\n\n", person.salary);
    }
    
    fclose(file);
}

int main() {
    writeBinaryFile();
    readBinaryFile();
    
    return 0;
}

Benefits of binary files:

  • Efficient storage (no conversion to text)
  • Fast reading and writing
  • Maintains exact binary representation of data
  • Suitable for complex data structures

Drawbacks:

  • Not human-readable
  • May not be portable across different architectures
  • Cannot be easily edited with text editors

Binary vs. Text Files

FeatureBinary FilesText Files
ReadabilityNot human-readableHuman-readable
StorageEfficient (native format)Less efficient (requires text conversion)
SpeedFaster reading/writingSlower (due to conversions)
Data TypesMaintains exact binary representationRepresents data as characters
PortabilityMay not be portable across architecturesGenerally more portable (newline issues)
EditingRequires special tools or programsCan be edited with standard text editors
Use CaseStructured data, images, executablesConfig files, source code, logs, plain text

File Handling Best Practices

Follow these best practices to make your file handling code more robust:

1. Always Check for Errors

Never assume file operations will succeed:

C
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
    perror("Error opening file");
    return 1;
}

2. Always Close Files

Files should be closed when you’re done with them:

C
fclose(file);

Use a proper control flow to ensure files are closed even if errors occur:

C
FILE *file = fopen("data.txt", "r");
if (file == NULL) {
    perror("Error opening file");
    return 1;
}

// File operations...

// Check for errors during operations
if (ferror(file)) {
    perror("Error during file operation");
    fclose(file);  // Still close the file
    return 1;
}

fclose(file);

3. Handle Binary and Text Mode Correctly

Use the appropriate mode for the type of file you’re working with:

  • Text mode: "r""w""a", etc.
  • Binary mode: "rb""wb""ab", etc.

4. Use the Right Functions for the Job

Choose the appropriate function based on what you’re doing:

  • Character I/O: fgetc()fputc()
  • String I/O: fgets()fputs()
  • Binary I/O: fread()fwrite()
  • Formatted I/O: fscanf()fprintf()

5. Be Careful with Path Names

Consider portability when specifying file paths:

C
// Windows-specific (backslashes)
FILE *file = fopen("C:\\Documents\\data.txt", "r");

// Unix/Linux-specific (forward slashes)
FILE *file = fopen("/home/user/data.txt", "r");

// Portable across most systems
FILE *file = fopen("data.txt", "r");  // Relative path

6. Check Available Disk Space

For large writes, consider checking if there’s enough disk space:

C
#include <stdio.h>
#include <sys/statvfs.h>

int main() {
    const char *path = "/";  // Check the root filesystem
    struct statvfs stat;
    
    if (statvfs(path, &stat) != 0) {
        perror("statvfs");
        return 1;
    }
    
    // Available space in bytes
    unsigned long available = stat.f_bsize * stat.f_bavail;
    printf("Available disk space: %lu bytes\n", available);
    
    // Check if we have enough space
    unsigned long required = 1024 * 1024 * 10;  // 10 MB
    if (available < required) {
        printf("Not enough disk space for operation\n");
    }
    
    return 0;
}

7. Handle End-of-File Correctly

Distinguish between EOF and errors:

C
int c;
while ((c = fgetc(file)) != EOF) {
    // Process character
}

if (feof(file)) {
    printf("End of file reached normally\n");
} else if (ferror(file)) {
    perror("Error reading file");
}

Conclusion

File handling is an essential skill for C programmers, allowing your applications to store and retrieve data permanently. In this article, we’ve covered:

  • Basic file operations (opening, reading, writing, closing)
  • Different ways to read and write data
  • Random access to files
  • Error handling in file operations
  • File information and manipulation
  • Binary file handling
  • Best practices for robust file operations

With these skills, you can build applications that work with configuration files, store user data, process large datasets, or handle any other file-related tasks.

In the next part of our series, we’ll explore preprocessor directives and macros, which allow you to extend C’s capabilities and make your code more flexible.

Practice Exercises

  1. Write a program that counts the number of characters, words, and lines in a text file.
  2. Create a program that merges two sorted text files into a third sorted file.
  3. Implement a simple database program that stores and retrieves records of students (roll number, name, and grades) using binary files.
  4. Write a program that encrypts a text file by shifting each character by a given value (Caesar cipher) and decrypts it back.
  5. Create a program that compares two files and reports if they are identical or where they first differ.
  6. Implement a logging system that appends timestamps and messages to a log file.
  7. Write a program that splits a large file into smaller parts and another program that can reassemble them.

Happy coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top