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):
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:
- Opening a file: Establishes a connection between your program and the file
- Reading from a file: Retrieves data from the file
- Writing to a file: Stores data in the file
- 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:
Mode | Description | File Existence | Operation |
---|---|---|---|
"r" | Open for reading | Must exist | Read |
"w" | Open for writing (truncates if exists) | Creates if not exist | Write |
"a" | Open for appending (writing at the end) | Creates if not exist | Append |
"r+" | Open for reading and writing | Must exist | Read/Write |
"w+" | Open for reading and writing (truncates if exists) | Creates if not exist | Read/Write |
"a+" | Open for reading and appending | Creates if not exist | Read/Append |
b | Add ‘b’ for binary mode (e.g., "rb" , "wb+" ) – optional | N/A | Binary 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:
FILE *fopen(const char *filename, const char *mode);
Example:
#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:
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:
#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:
#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:
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:
clearerr(file);
Standard Streams
C provides three standard streams that are automatically opened when your program starts:
- stdin: Standard input (keyboard by default)
- stdout: Standard output (screen by default)
- stderr: Standard error (screen by default)
Stream | Default Source/Destination | Description |
---|---|---|
stdin | Keyboard | Standard Input |
stdout | Screen | Standard Output |
stderr | Screen | Standard Error |
These are file pointers that you can use with the file I/O functions:
#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:
# 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:
Function | Purpose | Key Parameter(s) |
---|---|---|
fopen() | Check existence (if opened in “r”) | filename , mode |
fseek/ftell | Get file size (seek to end) | filename |
remove() | Deletes a file | filename |
rename() | Renames or moves a file | oldname , newname |
tmpfile() | Creates a temporary binary file (“wb+”) | None |
1. Checking if a File Exists
#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
#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
#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
#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:
#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
- Fully buffered: Output is saved in the buffer until it’s full.
- Line buffered: Output is saved until a newline character is encountered.
- Unbuffered: Output is written as soon as possible.
Controlling Buffering
The setvbuf()
function lets you control buffering behavior:
#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:
#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:
#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:
Function | Reads | Input Type | Stops At | Return Value | Notes |
---|---|---|---|---|---|
fgetc() | Character | Text | EOF | Character (int) or EOF | Reads one character at a time. |
fgets() | String (line) | Text | Newline, EOF, or buffer full | Buffer pointer or NULL | Safer than gets() , includes newline. |
fscanf() | Formatted | Text | Whitespace or format specifier | Number of items read | Similar to scanf() but reads from file. |
fread() | Binary data | Binary | Specified count or EOF | Number of items read | Reads raw bytes, good for structures. |
1. Reading Characters – fgetc()
#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()
#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()
#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()
#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:
Function | Writes | Input Type | Adds Newline | Return Value | Notes |
---|---|---|---|---|---|
fputc() | Character | Text | No | Character (int) or EOF | Writes one character at a time. |
fputs() | String | Text | No | Non-negative or EOF | Writes a string (null terminator not written). |
fprintf() | Formatted | Text | Optional | Number of chars written | Similar to printf() but writes to file. |
fwrite() | Binary data | Binary | No | Number of items written | Writes raw bytes, good for structures. |
1. Writing Characters – fputc()
#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()
#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()
#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()
#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:
Function | Purpose | Key Parameters |
---|---|---|
fseek() | Moves the file position indicator | offset , whence (SEEK_SET , SEEK_CUR , SEEK_END ) |
ftell() | Gets the current file position indicator | None |
rewind() | Resets the file position indicator to beginning | None |
1. fseek()
– Repositioning the File Position Indicator
int fseek(FILE *stream, long offset, int whence);
The whence
parameter can be:
SEEK_SET
: Beginning of fileSEEK_CUR
: Current positionSEEK_END
: End of file
Example:
#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
long ftell(FILE *stream);
Returns the current position of the file position indicator:
#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
void rewind(FILE *stream);
Sets the file position indicator to the beginning of the file:
#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:
Function | Purpose | Checks/Clears |
---|---|---|
fopen() | Check return value (NULL indicates error) | File open status |
perror() | Prints system error message based on errno | Last system error |
feof() | Checks if the End-Of-File indicator is set | EOF indicator |
ferror() | Checks if the error indicator is set for a stream | Stream error indicator |
clearerr() | Clears EOF and error indicators for a stream | EOF & error indicators |
1. Checking File Open Errors
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
#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:
#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
Feature | Binary Files | Text Files |
---|---|---|
Readability | Not human-readable | Human-readable |
Storage | Efficient (native format) | Less efficient (requires text conversion) |
Speed | Faster reading/writing | Slower (due to conversions) |
Data Types | Maintains exact binary representation | Represents data as characters |
Portability | May not be portable across architectures | Generally more portable (newline issues) |
Editing | Requires special tools or programs | Can be edited with standard text editors |
Use Case | Structured data, images, executables | Config 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:
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:
fclose(file);
Use a proper control flow to ensure files are closed even if errors occur:
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:
// 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:
#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:
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
- Write a program that counts the number of characters, words, and lines in a text file.
- Create a program that merges two sorted text files into a third sorted file.
- Implement a simple database program that stores and retrieves records of students (roll number, name, and grades) using binary files.
- Write a program that encrypts a text file by shifting each character by a given value (Caesar cipher) and decrypts it back.
- Create a program that compares two files and reports if they are identical or where they first differ.
- Implement a logging system that appends timestamps and messages to a log file.
- Write a program that splits a large file into smaller parts and another program that can reassemble them.
Happy coding!