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
| Aspect | Binary I/O | Text I/O |
|---|---|---|
| Format | Raw memory bytes | Human-readable text |
| Conversion | None | Formatting/parsing |
| Speed | Faster | Slower |
| File size | Smaller (no formatting) | Larger (text representation) |
| Portability | May be non-portable across platforms | Generally portable |
| Debugging | Harder (not readable) | Easier (readable text) |
Binary File Modes
| Mode | Description |
|---|---|
"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 writesize: Size of each element (usuallysizeof(struct_name))count: Number of elements to writestream: 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 intosize: Size of each element to readcount: Number of elements to readstream: 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
addEmployee: Appends structure to file usingfwritewith"ab"mode.readAllEmployees: Reads all structures usingfreadwith"rb"mode.findEmployeeById: Searches sequentially through file.updateEmployee: Usesfseekandftellto overwrite specific record.deleteEmployee: Rewrites entire file excluding deleted record.- Error handling: Checks
fopenreturn, usesstrerror.
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;
sizeofgives 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
- Always open binary files in binary mode (
"rb","wb"). - Check
fopenreturn value before using the file pointer. - Check
fwrite/freadreturn values for success. - Use
sizeof(struct_name)for element size. - Close files after use.
- Use
rewindorfseekto reset file position when needed. - Zero-initialize structures before writing to avoid garbage.
- Be aware of structure padding and its portability implications.
- Handle missing files gracefully on read operations.
- 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.



