CoreTechED

Loading

Learn to read and write structures in C using fwrite and fread. Master binary file handling with error checking and practical examples.

Read/Write Structure From/to a File in C: Guide

Read/Write Structure From/to a File in C: A Comprehensive Tutorial

fwrite() and fread() are standard C library functions used to write and read blocks of binary data. They are commonly used to store and retrieve structures directly from files without converting them to text. This approach preserves the exact memory layout and improves performance for structured data.

This tutorial provides an in-depth exploration of reading and writing structures in C: binary file modes, fwrite syntax, fread syntax, practical applications, common pitfalls, and best practices for robust file handling.


Conceptual Introduction & Fundamentals

What is Binary File I/O with Structures?

Binary file I/O with structures involves writing the raw memory representation of a structure to a file and reading it back. Unlike text I/O (which converts data to human-readable strings), binary I/O stores data exactly as it exists in memory.

Example:

c

struct Student { int id; char name[20]; float marks; }; struct Student s = {101, "Rahul", 89.5f}; /* Write binary */ fwrite(&s, sizeof(struct Student), 1, fp); /* Read binary */ fread(&s, sizeof(struct Student), 1, fp);

Why Use Binary I/O for Structures?

  • Speed: Direct memory dump/load, no conversion overhead.
  • Exact representation: Preserves floating-point precision and all bytes.
  • Compact: No text formatting overhead.
  • Simple: Write/read entire structures in one call.
  • Preserves binary data: Characters, integers, floats stored natively.

Binary vs. Text File I/O

AspectBinary I/OText I/O
FormatRaw memory bytesHuman-readable text
ConversionNoneFormatting/parsing
SpeedFasterSlower
File sizeSmaller (no formatting)Larger (text representation)
PortabilityMay be non-portable across platformsGenerally portable
DebuggingHarder (not readable)Easier (readable text)

Binary File Modes

ModeDescription
"rb"Open for binary reading
"wb"Open for binary writing (create/overwrite)
"ab"Open for binary appending
"rb+"Open for binary reading and writing
"wb+"Open for binary reading and writing (create/overwrite)
"ab+"Open for binary reading and appending

Syntax & Core Mechanics

fwrite() — Write Binary Data

Syntax:

c

size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);

Parameters:

  • ptr: Pointer to the data to write
  • size: Size of each element (usually sizeof(struct_name))
  • count: Number of elements to write
  • stream: FILE pointer to the output stream

Return value: Number of elements successfully written.

Example:

c

#include <stdio.h> #include <stdlib.h> struct Person { int id; char fname[20]; char lname[20]; }; int main(void) { FILE *fp = fopen("person.bin", "wb"); if (fp == NULL) { fprintf(stderr, "Error opening file\n"); return EXIT_FAILURE; } struct Person person = {1, "Rohan", "Sharma"}; size_t written = fwrite(&person, sizeof(struct Person), 1, fp); if (written == 1) { printf("Structure written successfully\n"); } else { fprintf(stderr, "Error writing to file\n"); } fclose(fp); return EXIT_SUCCESS; }

Expected output:

text

Structure written successfully

fread() — Read Binary Data

Syntax:

c

size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

Parameters:

  • ptr: Pointer to the memory block to read into
  • size: Size of each element to read
  • count: Number of elements to read
  • stream: FILE pointer to the input stream

Return value: Number of elements successfully read.

Example:

c

#include <stdio.h> #include <stdlib.h> struct Person { int id; char fname[20]; char lname[20]; }; int main(void) { FILE *fp = fopen("person.bin", "rb"); if (fp == NULL) { fprintf(stderr, "Error opening file\n"); return EXIT_FAILURE; } struct Person person; size_t read = fread(&person, sizeof(struct Person), 1, fp); if (read == 1) { printf("ID: %d\n", person.id); printf("First Name: %s\n", person.fname); printf("Last Name: %s\n", person.lname); } fclose(fp); return EXIT_SUCCESS; }

Expected output (depends on file contents):

text

ID: 1 First Name: Rohan Last Name: Sharma

Writing Multiple Structures

c

#include <stdio.h> #include <stdlib.h> struct Person { int id; char fname[20]; char lname[20]; }; int main(void) { FILE *fp = fopen("people.bin", "wb"); if (fp == NULL) { fprintf(stderr, "Error opening file\n"); return EXIT_FAILURE; } struct Person people[] = { {1, "Rohan", "Sharma"}, {2, "Amit", "Verma"}, {3, "Neha", "Singh"} }; int count = sizeof(people) / sizeof(people[0]); size_t written = fwrite(people, sizeof(struct Person), count, fp); printf("Written %zu records\n", written); fclose(fp); return EXIT_SUCCESS; }

Expected output:

text

Written 3 records

Reading Multiple Structures

c

#include <stdio.h> #include <stdlib.h> struct Person { int id; char fname[20]; char lname[20]; }; int main(void) { FILE *fp = fopen("people.bin", "rb"); if (fp == NULL) { fprintf(stderr, "Error opening file\n"); return EXIT_FAILURE; } struct Person person; int count = 0; while (fread(&person, sizeof(struct Person), 1, fp) == 1) { printf("ID: %d, Name: %s %s\n", person.id, person.fname, person.lname); count++; } printf("Total records read: %d\n", count); fclose(fp); return EXIT_SUCCESS; }

Expected output:

text

ID: 1, Name: Rohan Sharma ID: 2, Name: Amit Verma ID: 3, Name: Neha Singh Total records read: 3

Using rewind() to Reset File Position

c

#include <stdio.h> #include <stdlib.h> int main(void) { FILE *fp = fopen("people.bin", "wb+"); /* ... write data ... */ /* Move file pointer to beginning */ rewind(fp); /* Now read from start */ /* ... read data ... */ fclose(fp); return 0; }

Full Practical Code Example with Explanation

This program demonstrates reading and writing structures in a realistic scenario: an employee database manager.

Program: Employee Database Manager

c

/** * employee_database.c * Demonstrates reading and writing structures to binary files. * Manages employee records with fwrite and fread. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_EMPLOYEES 100 #define MAX_NAME_LEN 50 #define MAX_LINE_LEN 100 #define DATABASE_FILE "employees.dat" /* Employee structure */ typedef struct { int id; char name[MAX_NAME_LEN]; int age; double salary; } Employee; /* Function prototypes */ int addEmployee(const char *filename, const Employee *emp); int readAllEmployees(const char *filename, Employee employees[], int maxCount); int findEmployeeById(const char *filename, int id, Employee *result); int updateEmployee(const char *filename, int id, const Employee *updated); int deleteEmployee(const char *filename, int id); void displayEmployee(const Employee *emp); void displayAllEmployees(const char *filename); int readInteger(const char *prompt, int *outValue); int main(void) { int choice; char buffer[MAX_LINE_LEN]; printf("=== Employee Database Manager ===\n"); while (1) { printf("\n--- Menu ---\n"); printf("1. Add Employee\n"); printf("2. Display All Employees\n"); printf("3. Search Employee by ID\n"); printf("4. Update Employee\n"); printf("5. Delete Employee\n"); printf("6. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: { Employee emp; printf("Enter employee ID: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) break; emp.id = atoi(buffer); printf("Enter name: "); fflush(stdout); if (fgets(emp.name, sizeof(emp.name), stdin) == NULL) break; emp.name[strcspn(emp.name, "\n")] = '\0'; if (readInteger("Enter age: ", &emp.age) != 0) break; printf("Enter salary: $"); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) break; emp.salary = strtod(buffer, NULL); if (addEmployee(DATABASE_FILE, &emp) == 0) { printf("Employee added successfully.\n"); } break; } case 2: displayAllEmployees(DATABASE_FILE); break; case 3: { int id; Employee result; if (readInteger("Enter employee ID to search: ", &id) != 0) break; if (findEmployeeById(DATABASE_FILE, id, &result) == 0) { printf("\nEmployee found:\n"); displayEmployee(&result); } else { printf("Employee with ID %d not found.\n", id); } break; } case 4: { int id; Employee updated; if (readInteger("Enter employee ID to update: ", &id) != 0) break; printf("Enter new name: "); fflush(stdout); if (fgets(updated.name, sizeof(updated.name), stdin) == NULL) break; updated.name[strcspn(updated.name, "\n")] = '\0'; if (readInteger("Enter new age: ", &updated.age) != 0) break; printf("Enter new salary: $"); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) break; updated.salary = strtod(buffer, NULL); updated.id = id; if (updateEmployee(DATABASE_FILE, id, &updated) == 0) { printf("Employee updated successfully.\n"); } break; } case 5: { int id; if (readInteger("Enter employee ID to delete: ", &id) != 0) break; if (deleteEmployee(DATABASE_FILE, id) == 0) { printf("Employee deleted successfully.\n"); } break; } case 6: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Adds an employee to the database (appends) */ int addEmployee(const char *filename, const Employee *emp) { FILE *fp = fopen(filename, "ab"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return -1; } size_t written = fwrite(emp, sizeof(Employee), 1, fp); fclose(fp); return (written == 1) ? 0 : -1; } /* Reads all employees from the database */ int readAllEmployees(const char *filename, Employee employees[], int maxCount) { FILE *fp = fopen(filename, "rb"); if (fp == NULL) { /* File may not exist yet */ return 0; } int count = 0; while (count < maxCount && fread(&employees[count], sizeof(Employee), 1, fp) == 1) { count++; } fclose(fp); return count; } /* Finds an employee by ID */ int findEmployeeById(const char *filename, int id, Employee *result) { FILE *fp = fopen(filename, "rb"); if (fp == NULL) { return -1; } Employee emp; while (fread(&emp, sizeof(Employee), 1, fp) == 1) { if (emp.id == id) { *result = emp; fclose(fp); return 0; } } fclose(fp); return -1; } /* Updates an employee by ID */ int updateEmployee(const char *filename, int id, const Employee *updated) { FILE *fp = fopen(filename, "rb+"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return -1; } Employee emp; long position; while (fread(&emp, sizeof(Employee), 1, fp) == 1) { if (emp.id == id) { position = ftell(fp) - sizeof(Employee); fseek(fp, position, SEEK_SET); fwrite(updated, sizeof(Employee), 1, fp); fclose(fp); return 0; } } fclose(fp); return -1; } /* Deletes an employee by ID */ int deleteEmployee(const char *filename, int id) { /* Read all employees, filter out the one to delete, rewrite */ Employee employees[MAX_EMPLOYEES]; int count = readAllEmployees(filename, employees, MAX_EMPLOYEES); FILE *fp = fopen(filename, "wb"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return -1; } int found = 0; for (int i = 0; i < count; i++) { if (employees[i].id == id) { found = 1; continue; /* Skip this employee */ } fwrite(&employees[i], sizeof(Employee), 1, fp); } fclose(fp); return found ? 0 : -1; } /* Displays a single employee */ void displayEmployee(const Employee *emp) { printf("ID: %d\n", emp->id); printf("Name: %s\n", emp->name); printf("Age: %d\n", emp->age); printf("Salary: $%.2f\n", emp->salary); } /* Displays all employees */ void displayAllEmployees(const char *filename) { Employee employees[MAX_EMPLOYEES]; int count = readAllEmployees(filename, employees, MAX_EMPLOYEES); if (count == 0) { printf("No employees in database.\n"); return; } printf("\n=== Employee List ===\n"); for (int i = 0; i < count; i++) { displayEmployee(&employees[i]); printf("---\n"); } } /* Reads an integer safely */ int readInteger(const char *prompt, int *outValue) { char buffer[MAX_LINE_LEN]; char *endPtr; long val; printf("%s", prompt); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { return -1; } errno = 0; val = strtol(buffer, &endPtr, 10); if (endPtr == buffer || (*endPtr != '\n' && *endPtr != '\0')) { return -1; } if (errno == ERANGE || val < -2147483647 - 1 || val > 2147483647) { return -1; } *outValue = (int)val; return 0; }

Explanation of Structure File I/O

  1. addEmployee: Appends structure to file using fwrite with "ab" mode.
  2. readAllEmployees: Reads all structures using fread with "rb" mode.
  3. findEmployeeById: Searches sequentially through file.
  4. updateEmployee: Uses fseek and ftell to overwrite specific record.
  5. deleteEmployee: Rewrites entire file excluding deleted record.
  6. Error handling: Checks fopen return, uses strerror.

Expected Output (sample run)

text

=== Employee Database Manager === --- Menu --- 1. Add Employee 2. Display All Employees 3. Search Employee by ID 4. Update Employee 5. Delete Employee 6. Exit Enter choice: 1 Enter employee ID: 101 Enter name: Rahul Sharma Enter age: 28 Enter salary: $55000 Employee added successfully. --- Menu --- ... Enter choice: 1 Enter employee ID: 102 Enter name: Priya Patel Enter age: 32 Enter salary: $72000 Employee added successfully. --- Menu --- ... Enter choice: 2 === Employee List === ID: 101 Name: Rahul Sharma Age: 28 Salary: $55000.00 --- ID: 102 Name: Priya Patel Age: 32 Salary: $72000.00 --- --- Menu --- ... Enter choice: 3 Enter employee ID to search: 102 Employee found: ID: 102 Name: Priya Patel Age: 32 Salary: $72000.00 --- Menu --- ... Enter choice: 6 Exiting.

Common Pitfalls, Edge Cases & Memory Considerations

1. Using Text Mode for Binary Data

Using "r" or "w" instead of "rb" or "wb" can corrupt binary data on some systems (e.g., Windows newline translation).

c

FILE *fp = fopen("data.bin", "w"); // BUG: text mode for binary

Solution: Always use "rb" and "wb" for binary files.

2. Structure Padding Portability

Structure padding varies across compilers and platforms, making binary files non-portable.

c

struct Example { char c; // 1 byte int i; // 4 bytes (with 3 bytes padding) }; /* Binary layout may differ across compilers */

Solution: Use #pragma pack or write members individually for portability.

3. Not Checking fwrite Return

Assuming all data was written without checking the return value.

c

fwrite(&emp, sizeof(Employee), 1, fp); // No check

Solution: Check if (fwrite(...) != 1) { /* error */ }

4. Not Checking fread Return

Assuming data was read without verifying.

c

fread(&emp, sizeof(Employee), 1, fp); // No check

Solution: Check if (fread(...) == 1) { /* success */ }

5. Writing Strings with Padding

Fixed-size character arrays in structures may contain garbage in unused bytes.

c

struct Person { char name[50]; }; strcpy(person.name, "Alice"); // Rest of array is uninitialized fwrite(&person, sizeof(struct Person), 1, fp); // Writes garbage too

Solution: Zero-initialize strings or use calloc before writing.

6. Endianness Issues

Binary data written on a little-endian system may not be readable on a big-endian system.

Solution: Use text format or network byte order for cross-platform data.

7. Forgetting to Close Files

Leaving files open wastes resources and may lose buffered data.

c

fwrite(&emp, sizeof(Employee), 1, fp); /* Missing fclose(fp); */

Solution: Always fclose after file operations.

8. File Not Found on Read

Reading from a non-existent file returns NULL.

Solution: Check fopen return and handle gracefully.

9. Appending vs. Overwriting

Using "wb" overwrites existing file; use "ab" to append.

c

fopen("data.bin", "wb"); // Overwrites! fopen("data.bin", "ab"); // Appends

Solution: Choose the correct mode based on intent.

10. Memory Considerations

  • Structures may contain padding; sizeof gives actual size.
  • Large arrays of structures consume significant memory.
  • Reading large files into memory at once may fail; read in chunks.
  • Binary I/O is faster but less portable than text I/O.

Summary & Best Practices

Reading and writing structures to binary files using fwrite and fread is efficient and straightforward. Proper error handling and awareness of portability issues are essential.

Key Takeaways

  • Use "wb" for writing, "rb" for reading, "ab" for appending.
  • fwrite(ptr, size, count, stream) writes binary data.
  • fread(ptr, size, count, stream) reads binary data.
  • Both return the number of elements successfully transferred.
  • Check return values for error detection.
  • Binary format preserves exact memory layout but may not be portable.

Best Practices

  1. Always open binary files in binary mode ("rb", "wb").
  2. Check fopen return value before using the file pointer.
  3. Check fwrite/fread return values for success.
  4. Use sizeof(struct_name) for element size.
  5. Close files after use.
  6. Use rewind or fseek to reset file position when needed.
  7. Zero-initialize structures before writing to avoid garbage.
  8. Be aware of structure padding and its portability implications.
  9. Handle missing files gracefully on read operations.
  10. Document binary file format for maintainability.

By mastering structure file I/O, you’ll efficiently store and retrieve complex data structures, enabling persistent storage in your C programs.

C Program to Read Content of a File: Complete Guide

C Program to Read Content of a File: A Comprehensive Tutorial

Reading a file in C involves opening the file, retrieving its contents, and closing it after use. The C Standard Library provides several functions for reading files depending on the type and format of the data. Understanding file reading is essential for data processing, configuration management, and persistent storage.

This tutorial provides an in-depth exploration of reading files in C: fopen, fgetc, fgets, fscanf, fread, fclose, practical examples, common pitfalls, and best practices for robust file handling.


Conceptual Introduction & Fundamentals

What is File Reading?

File reading is the process of opening a file, retrieving its contents into memory, and closing the file when done. C provides a standard I/O library (<stdio.h>) with functions for reading text and binary files.

Why File Reading Matters

  • Persistent data: Load saved data from disk.
  • Configuration files: Read program settings.
  • Data processing: Analyze logs, CSV files, and structured data.
  • Binary data: Load images, audio, and custom binary formats.
  • Interoperability: Exchange data between programs.

File Reading Functions Overview

FunctionReadsSuitable For
fgetc()Single characterCharacter-by-character processing
fgets()Line/StringReading text files line by line
fscanf()Formatted dataStructured text files
fread()Binary blocksBinary files

Steps to Read a File

  1. Open the file: fopen(filename, mode)
  2. Read the data: Use appropriate function
  3. Close the file: fclose(pointer)

Syntax & Core Mechanics

Opening a File

Syntax:

c

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

Common modes:

  • "r": Read (text)
  • "rb": Read (binary)
  • "w": Write (text)
  • "a": Append (text)

Example:

c

FILE *fp = fopen("file.txt", "r"); if (fp == NULL) { fprintf(stderr, "Unable to open file\n"); return EXIT_FAILURE; }

Method 1: fgetc() — Character by Character

Reads one character at a time. Returns EOF at end of file.

c

#include <stdio.h> #include <stdlib.h> int main(void) { FILE *fp = fopen("file.txt", "r"); int ch; if (fp == NULL) { fprintf(stderr, "Unable to open file.\n"); return EXIT_FAILURE; } while ((ch = fgetc(fp)) != EOF) { putchar(ch); } fclose(fp); return EXIT_SUCCESS; }

Expected output (if file.txt contains “Hello, World!”):

text

Hello, World!

Important: Use int ch, not char ch, to correctly handle EOF.

Method 2: fgets() — Line by Line

Reads one line (up to size - 1 characters) into a buffer.

c

#include <stdio.h> #include <stdlib.h> int main(void) { FILE *fp = fopen("file.txt", "r"); char buffer[256]; if (fp == NULL) { fprintf(stderr, "Unable to open file.\n"); return EXIT_FAILURE; } while (fgets(buffer, sizeof(buffer), fp) != NULL) { printf("%s", buffer); } fclose(fp); return EXIT_SUCCESS; }

Expected output (file.txt with multiple lines):

text

Line 1 Line 2 Line 3

Method 3: fscanf() — Formatted Input

Reads formatted data according to a format string.

c

#include <stdio.h> #include <stdlib.h> int main(void) { FILE *fp = fopen("data.txt", "r"); char name[50]; int age; if (fp == NULL) { fprintf(stderr, "Unable to open file.\n"); return EXIT_FAILURE; } while (fscanf(fp, "%49s %d", name, &age) == 2) { printf("Name: %s, Age: %d\n", name, age); } fclose(fp); return EXIT_SUCCESS; }

Expected output (data.txt contains “Alice 25\nBob 30\n”):

text

Name: Alice, Age: 25 Name: Bob, Age: 30

Security note: Use width limits like %49s to prevent buffer overflow.

Method 4: fread() — Binary Data

Reads blocks of binary data.

c

#include <stdio.h> #include <stdlib.h> struct Student { int roll; char name[30]; float marks; }; int main(void) { FILE *fp = fopen("students.dat", "rb"); struct Student s; if (fp == NULL) { fprintf(stderr, "Unable to open file.\n"); return EXIT_FAILURE; } while (fread(&s, sizeof(struct Student), 1, fp) == 1) { printf("Roll: %d, Name: %s, Marks: %.2f\n", s.roll, s.name, s.marks); } fclose(fp); return EXIT_SUCCESS; }

Expected output (depends on binary file contents):

text

Roll: 1, Name: Alice, Marks: 85.50 Roll: 2, Name: Bob, Marks: 92.00

Closing a File

Always close files after use.

c

fclose(fp);

Full Practical Code Example with Explanation

This program demonstrates all file reading methods in a comprehensive text and binary file reader.

Program: File Content Reader

c

/** * file_reader.c * Demonstrates reading files using fgetc, fgets, fscanf, and fread. * Includes error handling and multiple reading modes. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define MAX_LINE_LEN 256 #define MAX_NAME_LEN 50 /* Structure for binary data */ typedef struct { int id; char name[MAX_NAME_LEN]; double score; } Record; /* Function prototypes */ void readCharByChar(const char *filename); void readLineByLine(const char *filename); void readFormatted(const char *filename); void readBinary(const char *filename); void demonstrateAllMethods(const char *filename); void createSampleFile(const char *filename); void createBinaryFile(const char *filename); int main(void) { int choice; char buffer[MAX_LINE_LEN]; char filename[MAX_NAME_LEN] = "sample.txt"; printf("=== File Content Reader ===\n"); /* Create sample files for demonstration */ createSampleFile(filename); createBinaryFile("sample.dat"); while (1) { printf("\n--- Menu ---\n"); printf("1. Read Character by Character (fgetc)\n"); printf("2. Read Line by Line (fgets)\n"); printf("3. Read Formatted Data (fscanf)\n"); printf("4. Read Binary Data (fread)\n"); printf("5. Demonstrate All Methods\n"); printf("6. Exit\n"); printf("Enter choice: "); fflush(stdout); if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "Error reading choice.\n"); return EXIT_FAILURE; } choice = atoi(buffer); switch (choice) { case 1: readCharByChar(filename); break; case 2: readLineByLine(filename); break; case 3: readFormatted(filename); break; case 4: readBinary("sample.dat"); break; case 5: demonstrateAllMethods(filename); break; case 6: printf("Exiting.\n"); return EXIT_SUCCESS; default: printf("Invalid choice. Please try again.\n"); break; } } return EXIT_SUCCESS; } /* Reads file character by character using fgetc */ void readCharByChar(const char *filename) { printf("\n=== Reading with fgetc ===\n"); FILE *fp = fopen(filename, "r"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return; } int ch; int charCount = 0; while ((ch = fgetc(fp)) != EOF) { putchar(ch); charCount++; } printf("\n(Total characters: %d)\n", charCount); if (ferror(fp)) { fprintf(stderr, "Error reading file.\n"); } fclose(fp); } /* Reads file line by line using fgets */ void readLineByLine(const char *filename) { printf("\n=== Reading with fgets ===\n"); FILE *fp = fopen(filename, "r"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return; } char buffer[MAX_LINE_LEN]; int lineCount = 0; while (fgets(buffer, sizeof(buffer), fp) != NULL) { printf("Line %d: %s", ++lineCount, buffer); } printf("(Total lines: %d)\n", lineCount); if (ferror(fp)) { fprintf(stderr, "Error reading file.\n"); } fclose(fp); } /* Reads formatted data using fscanf */ void readFormatted(const char *filename) { printf("\n=== Reading with fscanf ===\n"); FILE *fp = fopen(filename, "r"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return; } char name[MAX_NAME_LEN]; int age; float score; int recordCount = 0; while (fscanf(fp, "%49s %d %f", name, &age, &score) == 3) { printf("Record %d: Name=%s, Age=%d, Score=%.2f\n", ++recordCount, name, age, score); } printf("(Total records: %d)\n", recordCount); if (ferror(fp)) { fprintf(stderr, "Error reading file.\n"); } fclose(fp); } /* Reads binary data using fread */ void readBinary(const char *filename) { printf("\n=== Reading with fread (Binary) ===\n"); FILE *fp = fopen(filename, "rb"); if (fp == NULL) { fprintf(stderr, "Error opening %s: %s\n", filename, strerror(errno)); return; } Record record; int recordCount = 0; while (fread(&record, sizeof(Record), 1, fp) == 1) { printf("Record %d: ID=%d, Name=%s, Score=%.2f\n", ++recordCount, record.id, record.name, record.score); } printf("(Total records: %d)\n", recordCount); if (ferror(fp)) { fprintf(stderr, "Error reading file.\n"); } fclose(fp); } /* Demonstrates all reading methods */ void demonstrateAllMethods(const char *filename) { readCharByChar(filename); readLineByLine(filename); readFormatted(filename); readBinary("sample.dat"); } /* Creates a sample text file */ void createSampleFile(const char *filename) { FILE *fp = fopen(filename, "w"); if (fp == NULL) { fprintf(stderr, "Error creating %s\n", filename); return; } fprintf(fp, "Alice 25 85.5\n"); fprintf(fp, "Bob 30 92.0\n"); fprintf(fp, "Charlie 22 78.5\n"); fclose(fp); printf("Created sample text file: %s\n", filename); } /* Creates a sample binary file */ void createBinaryFile(const char *filename) { FILE *fp = fopen(filename, "wb"); if (fp == NULL) { fprintf(stderr, "Error creating %s\n", filename); return; } Record records[] = { {1, "Alice", 85.5}, {2, "Bob", 92.0}, {3, "Charlie", 78.5} }; for (int i = 0; i < 3; i++) { fwrite(&records[i], sizeof(Record), 1, fp); } fclose(fp); printf("Created sample binary file: %s\n", filename); }

Explanation of File Reading Methods

  1. fgetc: Reads one character at a time; counts characters.
  2. fgets: Reads line by line; counts lines.
  3. fscanf: Reads formatted records; counts records.
  4. fread: Reads binary structures; counts records.
  5. Error handling: Checks fopen return, uses strerror, checks ferror.

Expected Output (sample run)

text

=== File Content Reader === Created sample text file: sample.txt Created sample binary file: sample.dat --- Menu --- 1. Read Character by Character (fgetc) 2. Read Line by Line (fgets) 3. Read Formatted Data (fscanf) 4. Read Binary Data (fread) 5. Demonstrate All Methods 6. Exit Enter choice: 1 === Reading with fgetc === Alice 25 85.5 Bob 30 92.0 Charlie 22 78.5 (Total characters: 51) --- Menu --- ... Enter choice: 2 === Reading with fgets === Line 1: Alice 25 85.5 Line 2: Bob 30 92.0 Line 3: Charlie 22 78.5 (Total lines: 3) --- Menu --- ... Enter choice: 3 === Reading with fscanf === Record 1: Name=Alice, Age=25, Score=85.50 Record 2: Name=Bob, Age=30, Score=92.00 Record 3: Name=Charlie, Age=22, Score=78.50 (Total records: 3) --- Menu --- ... Enter choice: 4 === Reading with fread (Binary) === Record 1: ID=1, Name=Alice, Score=85.50 Record 2: ID=2, Name=Bob, Score=92.00 Record 3: ID=3, Name=Charlie, Score=78.50 (Total records: 3)

Common Pitfalls, Edge Cases & Memory Considerations

1. Not Checking fopen Return

Using a NULL file pointer causes crashes.

c

FILE *fp = fopen("missing.txt", "r"); fgetc(fp); // BUG: fp is NULL

Solution: Always check if (fp != NULL) after opening.

2. Using char Instead of int for fgetc

char may not represent EOF correctly.

c

char ch; // BUG: should be int while ((ch = fgetc(fp)) != EOF) { ... }

Solution: Use int ch; to correctly handle EOF (which is typically -1).

3. Forgetting to Close Files

Leaving files open wastes resources and may cause data loss.

c

FILE *fp = fopen("file.txt", "r"); /* ... reading ... */ /* Missing fclose(fp); */

Solution: Always fclose files after use.

4. Buffer Overflow with fscanf

Using %s without width limit can overflow the buffer.

c

char name[50]; fscanf(fp, "%s", name); // BUG: no width limit

Solution: Use %49s to limit input.

5. Assuming fgets Always Removes Newline

fgets includes the newline character if space allows.

c

fgets(buffer, sizeof(buffer), fp); /* buffer includes '\n' */

Solution: Remove newline if not needed: buffer[strcspn(buffer, "\n")] = '\0';

6. Not Checking ferror

Read errors may not be detected without checking ferror.

c

while (fgets(buffer, sizeof(buffer), fp) != NULL) { ... } /* Missing if (ferror(fp)) check */

Solution: Check ferror(fp) after the loop.

7. Reading Binary with Text Functions

Using text functions on binary files corrupts data.

c

fgets(buffer, sizeof(buffer), binaryFile); // BUG: binary file

Solution: Use fread for binary files with "rb" mode.

8. Assuming File Exists

Opening a non-existent file in read mode returns NULL.

Solution: Check fopen return and handle gracefully.

9. Not Handling EOF Correctly

EOF is not a character; it’s a special value indicating end of file.

c

while (!feof(fp)) { // Anti-pattern fgets(buffer, sizeof(buffer), fp); printf("%s", buffer); // May print last line twice }

Solution: Use the return value of the read function:

c

while (fgets(buffer, sizeof(buffer), fp) != NULL) { printf("%s", buffer); }

10. Memory Considerations

  • File reading functions use buffers; large buffers consume stack space.
  • fread reads directly into user-provided memory.
  • For very large files, read in chunks rather than all at once.
  • Binary structures may have padding; ensure consistent structure layout.

Summary & Best Practices

Reading files in C involves opening with fopen, reading with appropriate functions (fgetc, fgets, fscanf, fread), and closing with fclose. Proper error handling is essential for robust file operations.

Key Takeaways

  • fgetc: Character by character.
  • fgets: Line by line.
  • fscanf: Formatted data.
  • fread: Binary data.
  • Always check fopen return.
  • Use int for fgetc return value.
  • Always close files with fclose.
  • Check ferror for read errors.

Best Practices

  1. Always check fopen return value before using the file pointer.
  2. Use int for fgetc to handle EOF correctly.
  3. Use width limits in fscanf to prevent buffer overflow.
  4. Use the return value of read functions (not feof) for loop control.
  5. Always close files with fclose after use.
  6. Check ferror after reading to detect errors.
  7. Use the correct mode ("r" for text, "rb" for binary).
  8. Remove trailing newline from fgets if not needed.
  9. Handle missing files gracefully with error messages.
  10. Read large files in chunks rather than all at once.

By mastering file reading in C, you’ll write robust programs that effectively process both text and binary data from files.