File Operations in C: A Complete Guide

1. Why Use Files

When learning about structures, we created a contact management program. While the program runs, we can add and delete contacts, with all data stored in memory. However, once the program terminates, the data disappears. The next time we run the program, we must re-enter all the information, which makes the contact system impractical.

A proper contact system should preserve information between sessions—data should only be removed when we explicitly choose to delete it. This involves data persistence, commonly achieved by storing data in disk files or databases.

Using files allows us to store data directly on the computer's hard drive, achieving data persistence.

2. What Is a File

A file on disk is what we typically refer to as a file. In programming, however, files are generally categorized into two types: program files and data files (based on their function).

2.1 Program Files

These include source files (with .c extension), object files (with .obj extension on Windows), and executable files (with .exe extension on Windows).

2.2 Data Files

These files contain data rather than program code. They serve as sources of input data for programs or destinations for output data.

This chapter focuses on data files. In previous chapters, all input and output operations targeted the terminal—reading from the keyboard and displaying results on the monitor. Sometimes, however, we need to write information to disk for later use. This involves working with files stored on disk.

2.3 File Names

Each file requires a unique identifier for recognition and reference. A file name consists of three parts: file path, file name stem, and file extension.

Example: c:\code\test.txt

For convenience, this identifier is commonly referred to simply as the filename.

3. Opening and Closing Files

In buffered file systems, the key concept is the "file type pointer," commonly called the "file pointer."

Each opened file has a corresponding file information area allocated in memory, which stores relevant information such as the file name, file status, and current position. This information is stored in a structure variable. The structure type is declared by the system and named FILE.

For example, the stdio.h header in VS2013 provides the following structure declaration:

struct _iobuf {
    char *_ptr;
    int _cnt;
    char *_base;
    int _flag;
    int _file;
    int _charbuf;
    int _bufsiz;
    char *_tmpfname;
};
typedef struct _iobuf FILE;

Different C compilers may have FILE types with slightly different contents, but they are broadly similar. When a file is opened, the system automatically creates a FILE structure variable and populates it with relevant information. Users don't need to worry about the implementation details.

Typically, a FILE pointer is used to manage this structure, making it more convenient to work with files:

FILE *pf;

Here, pf is a pointer variable pointing to FILE type data. It can point to a file's informattion area (a structure variable). Through the information in this area, we can access the file. In other words, the file pointer allows us to find and manipulate its associated file.

3.1 Opening and Closing Files

Before reading or writing a file, it must be opened. After use, the file should be closed. When opening a file, the function returns a FILE pointer that references the file, establishing a connection between the pointer and the file.

The ANSI C standard uses the fopen function to open files and fclose to close them:

FILE *fopen(const char *filename, const char *mode);
int fclose(FILE *stream);

Example:

#include <stdio.h>

int main() {
    FILE *filePtr;
    
    filePtr = fopen("data.txt", "w");
    
    if (filePtr != NULL) {
        fprintf(filePtr, "File handling example");
        fclose(filePtr);
    }
    
    return 0;
}

4. Sequential File Read and Write

4.1 Sequential Read/Write Functions

Several functions facilitate sequential file operations:

  • fputc: Write a character to a file
  • fgetc: Read a character from a file
  • fputs: Write a string to a file
  • fgets: Read a string from a file
  • fprintf: Formatted output to a file
  • fscanf: Formatted input from a file
  • fwrite: Write blocks of data to a file
  • fread: Read blocks of data from a file

5. Random File Access

5.1 fseek

The fseek function positions the file pointer at a specific location based on the current position and an offset:

int fseek(FILE *stream, long int offset, int origin);

The origin can be SEEK_SET (beginning), SEEK_CUR (current position), or SEEK_END (end of file).

5.2 ftell

The ftell funcsion returns the current file position:

#include <stdio.h>

int main() {
    FILE *filePtr;
    long fileSize;
    
    filePtr = fopen("data.txt", "rb");
    
    if (filePtr == NULL) {
        perror("Error opening file");
    } else {
        fseek(filePtr, 0, SEEK_END);
        fileSize = ftell(filePtr);
        fclose(filePtr);
        printf("Size of data.txt: %ld bytes.\n", fileSize);
    }
    
    return 0;
}

5.3 rewind

The rewind function moves the file pointer back to the beginning of the file:

void rewind(FILE *stream);

Example:

#include <stdio.h>

int main() {
    int i;
    FILE *filePtr;
    char buffer[27];
    
    filePtr = fopen("data.txt", "w+");
    
    for (i = 'A'; i <= 'Z'; i++) {
        fputc(i, filePtr);
    }
    
    rewind(filePtr);
    fread(buffer, 1, 26, filePtr);
    fclose(filePtr);
    
    buffer[26] = '\0';
    puts(buffer);
    
    return 0;
}

6. Text Files and Binary Files

Based on how data is organized, files are classified as text files or binary files.

Data is stored in memory in binary form. If transferred to external storage without conversion, the file becomes a binary file.

If data must be stored as ASCII characters on disk, conversion is required during storage. Files stored as ASCII characters are called text files.

How is data stored in memory? Character data is always stored as ASCII, while numeric data can be stored either as ASCII or in binary form.

For example, the integer 10000 stored as ASCII requires 5 bytes (one per character), but only 4 bytes in binary form.

Example:

#include <stdio.h>

int main() {
    int value = 10000;
    FILE *outputFile = fopen("test.bin", "wb");
    
    fwrite(&value, 4, 1, outputFile);
    fclose(outputFile);
    
    return 0;
}

7. Determining End of File

7.1 Proper Use of feof

Important: During file reading, the return value of feof should not be used directly to determine if the file has ended.

The feof function is used to determine, after file reading has finished, whether the reason for termination was reaching the end of the file.

For text files, check if the return value is EOF (for fgetc) or NULL (for fgets):

  • fgetc returns EOF on failure or when reaching end of file
  • fgets returns NULL on failure or when reaching end of file

For binary files, check if the return value is less than the expected number of items to read:

  • fread returns the number of items read; if this is less than expected, the file has ended

Correct Usage - Text File Example:

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

int main(void) {
    int ch;
    FILE *inputFile = fopen("test.txt", "r");
    
    if (!inputFile) {
        perror("File opening failed");
        return EXIT_FAILURE;
    }
    
    while ((ch = fgetc(inputFile)) != EOF) {
        putchar(ch);
    }
    
    if (ferror(inputFile)) {
        puts("I/O error when reading");
    } else if (feof(inputFile)) {
        puts("End of file reached successfully");
    }
    
    fclose(inputFile);
    
    return 0;
}

Correct Usage - Binary File Example:

#include <stdio.h>

enum { SIZE = 5 };

int main(void) {
    double source[SIZE] = {1.0, 2.0, 3.0, 4.0, 5.0};
    FILE *filePtr = fopen("test.bin", "wb");
    
    fwrite(source, sizeof(double), SIZE, filePtr);
    fclose(filePtr);
    
    double destination[SIZE];
    filePtr = fopen("test.bin", "rb");
    
    size_t elementsRead = fread(destination, sizeof(double), SIZE, filePtr);
    
    if (elementsRead == SIZE) {
        puts("Array read successfully, contents:");
        for (int i = 0; i < SIZE; i++) {
            printf("%f ", destination[i]);
        }
        putchar('\n');
    } else {
        if (feof(filePtr)) {
            printf("Error reading test.bin: unexpected end of file\n");
        } else if (ferror(filePtr)) {
            perror("Error reading test.bin");
        }
    }
    
    fclose(filePtr);
    
    return 0;
}

8. File Buffering

The ANSI C standard uses a "buffered file system" for handling data files. In this approach, the system automatically allocates a "file buffer" in memory for each file currently in use. When data is written from memory to disk, it first goes to the buffer in memory. Once the buffer is full, the data is transferred to disk. Similarly, when reading data from disk into memory, data fills the buffer first, then is delivered to the program's data area (variables, etc.). The buffer size is determined by the C compiler implementation.

#include <stdio.h>
#include <windows.h>

int main() {
    FILE *outputFile = fopen("test.txt", "w");
    
    fprintf(outputFile, "Sample data");
    
    printf("Waiting 10 seconds - data written, open test.txt to see no content\n");
    Sleep(10000);
    
    printf("Flushing buffer\n");
    fflush(outputFile);
    
    printf("Waiting another 10 seconds - now test.txt contains the data\n");
    Sleep(10000);
    
    fclose(outputFile);
    
    return 0;
}

Conclusion:

Due to the existence of buffers, when working with files in C, it is necesary to either flush the buffer or close the file when operations are complete. Failing to do so may result in file read/write issues.

Tags: c programming file operations data persistence C Standard Library Buffered I/O

Posted on Wed, 13 May 2026 01:08:55 +0000 by violinrocker