Introduction to File Handling
File handling is a fundamental skill in C programming that allows your programs to persist data beyond their execution time. Whether you're saving user preferences, logging application events, or processing large datasets, understanding how to work with files is essential for building real-world applications.
Why Do We Need Files?
Variables in C exist only in memory (RAM) and are lost when your program terminates. Files provide persistent storage on disk, allowing data to survive program restarts and even system reboots. This makes files indispensable for practical applications.
RAM (Volatile)
Variables exist here during execution. When power goes off or program ends, everything is lost!
int score = 100; // Gone after exit!
Disk (Non-volatile)
Files stored here survive restarts, crashes, and power outages. Data is safe!
save.txt: "score=100" // Always there!
Common Use Cases for Files
Data Persistence
Save game progress, user settings, application state between sessions
Logging
Record events, errors, and debugging information for later analysis
Configuration
Store application settings in config files (INI, CSV, JSON)
Data Exchange
Share data between programs or import/export from databases
Types of Files in C
C recognizes two fundamental types of files based on how data is stored and accessed:
| Aspect | Text Files | Binary Files |
|---|---|---|
| Content | Human-readable characters | Raw bytes (machine-readable) |
| Extensions | .txt, .csv, .c, .log | .bin, .dat, .exe, .jpg |
| Newlines | Translated by OS | Stored exactly as-is |
| Size | Generally larger | More compact |
| Use Case | Config files, logs, source code | Images, audio, compiled programs |
The File Handling Workflow
Every file operation in C follows a predictable three-step pattern:
// Step 1: Open the file
FILE *fp = fopen("data.txt", "r");
// Step 2: Perform operations (read, write, etc.)
// ... your code here ...
// Step 3: Close the file
fclose(fp);
Important
Always close files when done. Failing to close files can lead to data loss (buffered data not written) and resource leaks (file handles exhausted).
Practice Questions
Task: Write a program that opens a file called "hello.txt" for writing, writes "Hello, Files!" to it, and closes it properly.
Show Solution
#include <stdio.h>
int main() {
// Open file for writing
FILE *fp = fopen("hello.txt", "w");
// Check if file opened successfully
if (fp == NULL) {
printf("Error opening file!\n");
return 1;
}
// Write to file
fprintf(fp, "Hello, Files!");
// Close file
fclose(fp);
printf("File written successfully!\n");
return 0;
}
Task: Write a function that checks if a file exists by attempting to open it for reading. Return 1 if it exists, 0 otherwise.
Show Solution
#include <stdio.h>
int fileExists(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp != NULL) {
fclose(fp);
return 1; // File exists
}
return 0; // File does not exist
}
int main() {
if (fileExists("test.txt")) {
printf("test.txt exists\n");
} else {
printf("test.txt does not exist\n");
}
return 0;
}
The FILE Pointer Structure
In C, all file operations revolve around the FILE pointer, a special data type defined in stdio.h that serves as a handle to access and manipulate files. Understanding the FILE structure is the foundation of all file operations in C programming.
What is FILE?
FILE is an opaque structure defined in <stdio.h>. You never access its
internal fields directly. Instead, you use a pointer to FILE (FILE *) and pass it to
library functions that know how to manipulate it.
#include <stdio.h>
int main() {
FILE *fp; // Declare a file pointer
// fp will hold the address of a FILE structure
// returned by fopen()
return 0;
}
Why Opaque?
The FILE structure is implementation-defined and varies between compilers and operating systems. Keeping it opaque ensures portability because your code works regardless of the underlying structure.
Inside the FILE Structure
While you should not access these directly, understanding what the FILE structure contains helps you appreciate what happens during file operations:
Buffer
A temporary memory area that stores data before writing to disk or after reading from disk. Instead of accessing the hard drive for every single byte, C collects data in this buffer and performs I/O in larger chunks.
Position Indicator
A cursor that tracks exactly where you are in the file. When you read or write data,
this position automatically moves forward. You can also manually move it using functions
like fseek().
Error Flag
A boolean indicator that gets set to true if something goes wrong during
a file operation (like trying to write to a read-only file). You can check this flag
using ferror() function.
EOF Flag
End-Of-File indicator that becomes true when you have read past the last
byte of the file. This tells your program there is no more data to read. Check it
using feof() function.
Declaring and Initializing FILE Pointers
#include <stdio.h>
int main() {
// Declaration only (uninitialized - dangerous!)
FILE *fp1;
// Declaration with NULL initialization (safe)
FILE *fp2 = NULL;
// Declaration and assignment from fopen()
FILE *fp3 = fopen("data.txt", "r");
// Always check if fopen succeeded
if (fp3 == NULL) {
printf("Failed to open file\n");
return 1;
}
// Use the file...
fclose(fp3);
return 0;
}
Best Practice
Always initialize FILE pointers to NULL if you are not assigning them immediately.
This prevents accidental use of garbage pointer values.
Standard Streams
C automatically opens three standard FILE streams when your program starts. These are pre-connected to the terminal and are available without calling fopen():
| Stream | Purpose | Typical Use |
|---|---|---|
stdin |
Standard input | Keyboard input, piped data |
stdout |
Standard output | Normal program output (printf uses this) |
stderr |
Standard error | Error messages (unbuffered) |
#include <stdio.h>
int main() {
// These are equivalent:
printf("Hello\n");
fprintf(stdout, "Hello\n");
// Print to stderr (for errors)
fprintf(stderr, "Error: something went wrong\n");
return 0;
}
Practice Questions
Task: Declare three FILE pointers for input, output, and log files. Initialize them to NULL and then assign them using fopen().
Show Solution
#include <stdio.h>
int main() {
// Initialize all pointers to NULL
FILE *inputFile = NULL;
FILE *outputFile = NULL;
FILE *logFile = NULL;
// Open files
inputFile = fopen("input.txt", "r");
outputFile = fopen("output.txt", "w");
logFile = fopen("log.txt", "a");
// Check each file
if (inputFile == NULL) {
fprintf(stderr, "Cannot open input.txt\n");
}
if (outputFile == NULL) {
fprintf(stderr, "Cannot open output.txt\n");
}
if (logFile == NULL) {
fprintf(stderr, "Cannot open log.txt\n");
}
// Close files that were opened
if (inputFile) fclose(inputFile);
if (outputFile) fclose(outputFile);
if (logFile) fclose(logFile);
return 0;
}
Task: Write a program that writes multiple lines to both stdout and a file simultaneously using fprintf().
Show Solution
#include <stdio.h>
void logMessage(FILE *logFile, const char *message) {
// Print to console
fprintf(stdout, "[CONSOLE] %s\n", message);
// Also write to log file
if (logFile != NULL) {
fprintf(logFile, "[LOG] %s\n", message);
}
}
int main() {
FILE *logFile = fopen("messages.log", "w");
logMessage(logFile, "Program started");
logMessage(logFile, "Processing data...");
logMessage(logFile, "Program completed");
if (logFile) fclose(logFile);
return 0;
}
Task: Create an array of FILE pointers to manage multiple log files (info.log, warning.log, error.log). Open all, write test messages, and close all properly.
Show Solution
#include <stdio.h>
#define NUM_LOGS 3
int main() {
const char *filenames[NUM_LOGS] = {
"info.log", "warning.log", "error.log"
};
const char *labels[NUM_LOGS] = {
"INFO", "WARNING", "ERROR"
};
FILE *logs[NUM_LOGS] = {NULL, NULL, NULL};
int i;
// Open all log files
for (i = 0; i < NUM_LOGS; i++) {
logs[i] = fopen(filenames[i], "w");
if (logs[i] == NULL) {
fprintf(stderr, "Failed: %s\n", filenames[i]);
}
}
// Write to each log
for (i = 0; i < NUM_LOGS; i++) {
if (logs[i] != NULL) {
fprintf(logs[i], "[%s] Test message\n", labels[i]);
}
}
// Close all log files
for (i = 0; i < NUM_LOGS; i++) {
if (logs[i] != NULL) {
fclose(logs[i]);
}
}
printf("All logs written successfully!\n");
return 0;
}
Opening Files with fopen()
The fopen() function is your gateway to file operations in C. It establishes a connection between your program and a file on disk, returning a FILE pointer that you'll use for all subsequent operations. Mastering fopen() and its various modes is crucial for effective file handling.
The fopen() Function
fopen() takes two string arguments: the file path and the access mode. It returns a
FILE pointer on success or NULL on failure.
FILE *fopen(const char *filename, const char *mode);
// Example usage:
FILE *fp = fopen("data.txt", "r"); // Open for reading
File Path Options
- Relative path:
"data.txt"- relative to current working directory - Absolute path:
"C:\\Users\\data.txt"(Windows) or"/home/user/data.txt"(Linux) - Note: Use double backslashes
\\on Windows or forward slashes/
Basic Usage Pattern
#include <stdio.h>
int main() {
FILE *fp = fopen("example.txt", "w");
// ALWAYS check for NULL!
if (fp == NULL) {
printf("Error: Cannot open file\n");
return 1;
}
// File operations here...
fprintf(fp, "Writing to file!\n");
// Close when done
fclose(fp);
return 0;
}
Closing Files with fclose()
Every file opened with fopen() should be closed with fclose(). This is critical because:
Flushes Buffers
Ensures all buffered data is written to disk
Releases Resources
Frees the file handle for use by other processes
Prevents Data Loss
Unflushed buffers may lose data on program crash
int fclose(FILE *stream);
// Returns 0 on success, EOF on failure
if (fclose(fp) == EOF) {
printf("Error closing file\n");
}
Common Mistakes
- Not checking if
fopen()returned NULL before using the pointer - Forgetting to close files (resource leak)
- Closing a file twice (undefined behavior)
- Using a file pointer after closing it
Complete Example: Read and Write
#include <stdio.h>
int main() {
FILE *writeFile, *readFile;
char buffer[100];
// Write to a file
writeFile = fopen("greeting.txt", "w");
if (writeFile == NULL) {
perror("Error opening for write");
return 1;
}
fprintf(writeFile, "Hello from C!\n");
fclose(writeFile);
// Read from the same file
readFile = fopen("greeting.txt", "r");
if (readFile == NULL) {
perror("Error opening for read");
return 1;
}
while (fgets(buffer, sizeof(buffer), readFile)) {
printf("Read: %s", buffer);
}
fclose(readFile);
return 0;
}
Practice Questions
Task: Write a function safeOpen() that opens a file, prints an error message if it fails, and returns the FILE pointer (or NULL on failure).
Show Solution
#include <stdio.h>
FILE* safeOpen(const char *filename, const char *mode) {
FILE *fp = fopen(filename, mode);
if (fp == NULL) {
fprintf(stderr, "Error: Cannot open '%s' ", filename);
fprintf(stderr, "with mode '%s'\n", mode);
perror("System error");
}
return fp;
}
int main() {
FILE *fp = safeOpen("test.txt", "r");
if (fp != NULL) {
printf("File opened successfully!\n");
fclose(fp);
}
// Try opening non-existent file
FILE *fp2 = safeOpen("nonexistent.xyz", "r");
// Will print error message
return 0;
}
Task: Write a program that copies the contents of one text file to another. Handle errors for both files.
Show Solution
#include <stdio.h>
int copyFile(const char *src, const char *dest) {
FILE *srcFile = fopen(src, "r");
if (srcFile == NULL) {
perror("Cannot open source");
return -1;
}
FILE *destFile = fopen(dest, "w");
if (destFile == NULL) {
perror("Cannot open destination");
fclose(srcFile);
return -1;
}
int ch;
while ((ch = fgetc(srcFile)) != EOF) {
fputc(ch, destFile);
}
fclose(srcFile);
fclose(destFile);
return 0;
}
int main() {
if (copyFile("source.txt", "copy.txt") == 0) {
printf("File copied successfully!\n");
} else {
printf("Copy failed!\n");
}
return 0;
}
Task: Write a program that opens multiple files, processes them, and guarantees all files are closed even if an error occurs midway (using goto for cleanup).
Show Solution
#include <stdio.h>
int processFiles(void) {
FILE *f1 = NULL, *f2 = NULL, *f3 = NULL;
int result = -1;
f1 = fopen("file1.txt", "r");
if (!f1) { perror("file1.txt"); goto cleanup; }
f2 = fopen("file2.txt", "r");
if (!f2) { perror("file2.txt"); goto cleanup; }
f3 = fopen("output.txt", "w");
if (!f3) { perror("output.txt"); goto cleanup; }
// Process files here...
fprintf(f3, "Processing complete\n");
result = 0; // Success
cleanup:
if (f3) fclose(f3);
if (f2) fclose(f2);
if (f1) fclose(f1);
return result;
}
int main() {
if (processFiles() == 0) {
printf("All files processed!\n");
} else {
printf("Processing failed!\n");
}
return 0;
}
File Modes and Access Types
File modes determine how your program interacts with a file - whether it can read, write, or both. Choosing the correct mode is critical because it affects what operations are allowed and what happens to existing file content. C provides several modes for text and binary file access.
Basic File Modes
There are six fundamental modes for opening files in C:
| Mode | Description | If File Exists | If File Missing |
|---|---|---|---|
"r" |
Read only | Opens at beginning | Returns NULL |
"w" |
Write only | Truncates (erases) | Creates new file |
"a" |
Append only | Opens at end | Creates new file |
"r+" |
Read and write | Opens at beginning | Returns NULL |
"w+" |
Read and write | Truncates (erases) | Creates new file |
"a+" |
Read and append | Opens, writes at end | Creates new file |
Binary Mode
Adding b to any mode opens the file in binary mode. This is critical
on Windows where text mode performs newline translation (CR+LF to LF).
// Binary mode examples
FILE *fp1 = fopen("image.png", "rb"); // Read binary
FILE *fp2 = fopen("data.bin", "wb"); // Write binary
FILE *fp3 = fopen("log.bin", "ab"); // Append binary
FILE *fp4 = fopen("file.dat", "rb+"); // Read/write binary
Windows vs Linux: Newline Handling
Understanding how different operating systems handle newlines is crucial for writing portable C code that works correctly across platforms.
Windows (Text Mode)
- Newline:
CR+LF(\r\n) - 2 bytes - On Write:
\n→\r\n(auto-converted) - On Read:
\r\n→\n(auto-converted) - Ctrl+Z: Treated as End-of-File in text mode
Linux/macOS (Text Mode)
- Newline:
LF(\n) - 1 byte - On Write: No conversion needed
- On Read: No conversion needed
- Binary = Text: No difference between modes
"rb" and "wb" for non-text files (images, audio, executables).
Text mode can corrupt binary data on Windows by inserting/removing bytes during newline translation!
Mode Comparison Examples
#include <stdio.h>
void demonstrateModes() {
FILE *fp;
// "w" - Destroys existing content!
fp = fopen("test.txt", "w");
fprintf(fp, "Fresh start\n");
fclose(fp);
// "a" - Preserves existing content
fp = fopen("test.txt", "a");
fprintf(fp, "Added line\n");
fclose(fp);
// "r+" - Read and modify existing file
fp = fopen("test.txt", "r+");
if (fp) {
fprintf(fp, "OVERWRITE"); // Overwrites from start
fclose(fp);
}
}
Choosing the Right Mode
Selecting the appropriate file mode is crucial for your program's behavior. The wrong mode can accidentally destroy data or cause errors. Use this guide to pick the perfect mode for your use case:
Reading Only
Use when you need to read existing data without making changes. File must already exist or fopen() will fail.
"r" (text) or "rb" (binary)
Creating New File
Creates a new file or destroys existing content. Use carefully - no undo possible!
"w" (text) or "wb" (binary)
Adding to Existing
Preserves existing content and adds new data at the end. Creates file if it doesn't exist.
"a" (text) or "ab" (binary)
Modifying Existing
Read and write to existing file. Can overwrite specific parts using fseek() positioning.
"r+" (text) or "rb+" (binary)
File Mode Decision Tree
Follow the flowchart to find the perfect mode for your use case:
Read Only
"r"
"rb"
Create/Overwrite
"w"
"w+"
Append
"a"
"a+"
Read + Write
"r+"
b to any mode!
Append Mode Behavior
Append mode is special because writes always go to the end of the file, even if you use fseek() to move the position indicator. This protects log files from accidental overwrites.
#include <stdio.h>
int main() {
FILE *log = fopen("app.log", "a");
if (log == NULL) return 1;
// Even with fseek, writes go to end in append mode
fseek(log, 0, SEEK_SET); // Move to beginning
fprintf(log, "[LOG] This still goes to end!\n");
fclose(log);
return 0;
}
Practice Questions
Task: Write a function that returns the appropriate file mode string based on user requirements (read/write/append and text/binary).
Show Solution
#include <stdio.h>
#include <string.h>
const char* getFileMode(int readAccess, int writeAccess,
int append, int binary) {
static char mode[4];
int i = 0;
if (append) {
mode[i++] = 'a';
if (readAccess) mode[i++] = '+';
} else if (writeAccess && !readAccess) {
mode[i++] = 'w';
} else if (readAccess && writeAccess) {
mode[i++] = 'r';
mode[i++] = '+';
} else {
mode[i++] = 'r';
}
if (binary) mode[i++] = 'b';
mode[i] = '\0';
return mode;
}
int main() {
printf("Read only text: %s\n", getFileMode(1,0,0,0));
printf("Write binary: %s\n", getFileMode(0,1,0,1));
printf("Append text: %s\n", getFileMode(0,1,1,0));
return 0;
}
Task: Create a logger that appends timestamped messages to a log file. Use append mode to preserve previous entries.
Show Solution
#include <stdio.h>
#include <time.h>
void logMessage(const char *filename, const char *msg) {
FILE *log = fopen(filename, "a");
if (log == NULL) {
perror("Cannot open log");
return;
}
time_t now = time(NULL);
char *timestamp = ctime(&now);
timestamp[24] = '\0'; // Remove newline
fprintf(log, "[%s] %s\n", timestamp, msg);
fclose(log);
}
int main() {
logMessage("app.log", "Application started");
logMessage("app.log", "Processing data");
logMessage("app.log", "Application ended");
printf("Check app.log for entries!\n");
return 0;
}
Task: Write a program that reads an array of integers from a text file and saves them in binary format, then reads back and verifies.
Show Solution
#include <stdio.h>
int main() {
int numbers[] = {10, 20, 30, 40, 50};
int count = 5;
int readBack[5];
int i;
// Write to binary file
FILE *binFile = fopen("numbers.bin", "wb");
if (binFile == NULL) return 1;
fwrite(&count, sizeof(int), 1, binFile);
fwrite(numbers, sizeof(int), count, binFile);
fclose(binFile);
printf("Written %d integers to binary file\n", count);
// Read from binary file
int readCount;
binFile = fopen("numbers.bin", "rb");
if (binFile == NULL) return 1;
fread(&readCount, sizeof(int), 1, binFile);
fread(readBack, sizeof(int), readCount, binFile);
fclose(binFile);
// Verify
printf("Read back %d integers: ", readCount);
for (i = 0; i < readCount; i++) {
printf("%d ", readBack[i]);
}
printf("\n");
return 0;
}
Error Handling and File Status
Robust error handling is what separates production-quality code from amateur scripts. File operations can fail for many reasons - missing files, permission issues, disk full, etc. Learning to detect, handle, and recover from these errors is essential for writing reliable C programs.
Why File Operations Fail
Understanding why file operations fail is crucial for writing robust programs. Here are the most common reasons your file operations might not work as expected:
Common File Operation Failures
When fopen() returns NULL, it means something went wrong.
Here are the typical culprits you'll encounter:
File Not Found
Trying to open a file with "r" mode that doesn't exist on disk.
fopen("missing.txt", "r"); // Returns NULL!
Permission Denied
No read/write permission for the file or directory. Common on Linux/Mac systems.
fopen("/etc/passwd", "w"); // No permission!
ls -l or run as admin
Disk Full
No space left on the storage device. Writing will fail silently or corrupt data.
fprintf(fp, data); // May fail if disk full!
Invalid Path
Parent directory doesn't exist or path contains invalid characters.
fopen("fake/dir/file.txt", "w"); // Dir missing!
mkdir()
File Locked
Another process has exclusive access to the file (common on Windows).
// Excel has file open → fopen fails!
Too Many Open Files
System limit reached. Usually caused by forgetting to call fclose().
// Opened 1000 files without closing any!
fclose()!
Quick Debugging Checklist
Checking fopen() Results
The most critical check is verifying that fopen() succeeded. A NULL return indicates failure.
#include <stdio.h>
int main() {
FILE *fp = fopen("config.txt", "r");
if (fp == NULL) {
// Handle the error
printf("Error: Cannot open config.txt\n");
return 1; // Exit with error code
}
// Proceed with file operations
fclose(fp);
return 0;
}
Using perror() for Detailed Errors
perror() prints a descriptive error message based on the value of errno
(set by the failed operation). It appends the system error message to your custom prefix.
#include <stdio.h>
#include <errno.h>
int main() {
FILE *fp = fopen("nonexistent.txt", "r");
if (fp == NULL) {
perror("Failed to open file");
// Output: "Failed to open file: No such file or directory"
printf("Error code: %d\n", errno);
return 1;
}
fclose(fp);
return 0;
}
ferror() - Checking for Stream Errors
After performing read or write operations, use ferror() to check if an error occurred
on the stream. It returns non-zero if the error indicator is set.
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "r");
if (!fp) return 1;
char buffer[100];
while (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("%s", buffer);
}
// Check why loop ended
if (ferror(fp)) {
printf("Error reading file!\n");
} else if (feof(fp)) {
printf("Reached end of file.\n");
}
fclose(fp);
return 0;
}
feof() - Detecting End-of-File
feof() returns non-zero when the end-of-file indicator is set. Note that it only becomes
true after a read operation attempts to read past the end.
Common Mistake: The feof() Trap
Do NOT use while (!feof(fp)) as your loop condition!
This is a classic beginner mistake that causes subtle bugs.
Why Does It Fail?
feof() only returns true after a read operation has already failed. This means
your loop runs one extra time, processing garbage data or causing undefined behavior.
while (!feof(fp)) {
char c = fgetc(fp);
printf("%c", c); // Prints EOF garbage!
}
int c;
while ((c = fgetc(fp)) != EOF) {
printf("%c", c); // Safe!
}
fgetc(), fgets(), or fread() instead of relying on feof().
clearerr() - Resetting Error Indicators
After handling an error or reaching EOF, use clearerr() to reset both the error
and EOF indicators if you want to continue using the stream.
#include <stdio.h>
int main() {
FILE *fp = fopen("data.txt", "r+");
if (!fp) return 1;
// Read until EOF
int c;
while ((c = fgetc(fp)) != EOF) {
putchar(c);
}
// Now EOF is set - cannot read more
printf("EOF reached: %d\n", feof(fp));
// Reset indicators
clearerr(fp);
// Now we can reposition and continue
rewind(fp); // Go back to start
c = fgetc(fp); // This works now
fclose(fp);
return 0;
}
Complete Error Handling Example
#include <stdio.h>
#include <errno.h>
#include <string.h>
int processFile(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
fprintf(stderr, "Error opening %s: %s\n",
filename, strerror(errno));
return -1;
}
char line[256];
int lineNum = 0;
while (fgets(line, sizeof(line), fp) != NULL) {
lineNum++;
printf("%d: %s", lineNum, line);
}
if (ferror(fp)) {
fprintf(stderr, "Error reading at line %d\n", lineNum);
fclose(fp);
return -1;
}
if (fclose(fp) == EOF) {
perror("Error closing file");
return -1;
}
return lineNum;
}
Practice Questions
Task: Write a function that attempts to open a file and returns an error code indicating what went wrong (0=success, 1=not found, 2=permission, 3=other).
Show Solution
#include <stdio.h>
#include <errno.h>
int tryOpen(const char *filename, const char *mode) {
FILE *fp = fopen(filename, mode);
if (fp != NULL) {
fclose(fp);
return 0; // Success
}
switch (errno) {
case ENOENT:
return 1; // File not found
case EACCES:
return 2; // Permission denied
default:
return 3; // Other error
}
}
int main() {
const char *msgs[] = {
"Success", "Not found", "Permission denied", "Other"
};
int result = tryOpen("test.txt", "r");
printf("Result: %s\n", msgs[result]);
return 0;
}
Task: Write a function that reads a file and returns the number of lines, or a negative error code. Use ferror() and feof() properly.
Show Solution
#include <stdio.h>
int countLines(const char *filename) {
FILE *fp = fopen(filename, "r");
if (fp == NULL) {
return -1; // Cannot open
}
int lines = 0;
int ch, prevCh = '\n';
while ((ch = fgetc(fp)) != EOF) {
if (prevCh == '\n') {
lines++;
}
prevCh = ch;
}
if (ferror(fp)) {
fclose(fp);
return -2; // Read error
}
fclose(fp);
return lines;
}
int main() {
int result = countLines("test.txt");
if (result == -1) {
printf("Cannot open file\n");
} else if (result == -2) {
printf("Error reading file\n");
} else {
printf("File has %d lines\n", result);
}
return 0;
}
Task: Write a function that attempts to open a file up to 3 times with increasing delays between attempts (useful for network files or locked files).
Show Solution
#include <stdio.h>
#include <errno.h>
#ifdef _WIN32
#include <windows.h>
#define SLEEP(ms) Sleep(ms)
#else
#include <unistd.h>
#define SLEEP(ms) usleep((ms) * 1000)
#endif
FILE* openWithRetry(const char *filename, const char *mode,
int maxAttempts) {
FILE *fp = NULL;
int attempt;
int delays[] = {100, 500, 1000}; // ms
for (attempt = 0; attempt < maxAttempts; attempt++) {
fp = fopen(filename, mode);
if (fp != NULL) {
printf("Opened on attempt %d\n", attempt + 1);
return fp;
}
printf("Attempt %d failed: %s\n",
attempt + 1, strerror(errno));
if (attempt < maxAttempts - 1) {
printf("Waiting %d ms...\n", delays[attempt]);
SLEEP(delays[attempt]);
}
}
return NULL;
}
int main() {
FILE *fp = openWithRetry("data.txt", "r", 3);
if (fp != NULL) {
printf("File opened successfully!\n");
fclose(fp);
} else {
printf("Failed after all attempts\n");
}
return 0;
}
Key Takeaways
FILE Pointer is Essential
All file operations in C use the FILE pointer from stdio.h as a handle to access files
Always Check fopen()
fopen() returns NULL on failure - always verify before using the returned pointer
Choose Correct Mode
File modes (r, w, a, r+, w+, a+, b) control read/write permissions and file creation behavior
Always Close Files
Use fclose() to release resources and ensure data is written to disk properly
Handle Errors Gracefully
Use ferror(), feof(), and perror() to detect and report file operation failures
Text vs Binary Modes
Use "b" suffix for binary files to prevent newline translation on Windows systems
Knowledge Check
Quick Quiz
Test what you've learned about C file handling basics