Binary file input/output in C allows programs to read and write data in its raw, binary format—exactly as it exists in memory. Unlike text mode, which performs translations (like newline conversion), binary mode provides direct, uninterpreted access to file contents. This makes binary I/O essential for applications requiring efficient storage, precise data preservation, and high-performance file operations.
What is Binary File I/O?
Binary file I/O involves reading and writing data in its native binary representation, without any text transformations. When you write an integer to a binary file, it stores the exact byte representation of that integer. When you read it back, you get the exact same integer value, preserving precision and avoiding parsing overhead.
Why Binary I/O is Essential in C
- Precision Preservation: Floating-point numbers maintain exact values without rounding during text conversion.
- Storage Efficiency: Binary formats typically use less space than text representations.
- Performance: No parsing or formatting overhead—direct memory transfers.
- Data Structure Persistence: Save entire structures in one operation.
- Random Access: Directly seek to any position and read specific data.
- Platform-Specific Data: Work with file formats that require exact binary layouts.
Opening Files in Binary Mode
#include <stdio.h>
#include <stdlib.h>
// Binary mode flags:
// "rb" - read binary
// "wb" - write binary (creates new file/truncates existing)
// "ab" - append binary
// "rb+" - read and write binary (file must exist)
// "wb+" - read and write binary (creates new/truncates)
// "ab+" - read and append binary
int main() {
// Opening a file for binary writing
FILE *file = fopen("data.bin", "wb");
if (file == NULL) {
perror("Error opening file");
return 1;
}
printf("File opened successfully in binary mode\n");
fclose(file);
return 0;
}
Basic Binary I/O Functions
#include <stdio.h>
#include <stdlib.h>
// fread() - read binary data from file
// fwrite() - write binary data to file
// fseek() - move file position indicator
// ftell() - get current file position
// rewind() - reset to beginning of file
int main() {
// ============================================================
// WRITING BINARY DATA
// ============================================================
FILE *file = fopen("numbers.bin", "wb");
if (file == NULL) {
perror("Error creating file");
return 1;
}
int numbers[] = {10, 20, 30, 40, 50};
size_t count = sizeof(numbers) / sizeof(numbers[0]);
// Write entire array in one operation
size_t elements_written = fwrite(numbers, sizeof(int), count, file);
if (elements_written != count) {
printf("Error: Only wrote %zu of %zu elements\n",
elements_written, count);
} else {
printf("Successfully wrote %zu integers\n", count);
}
fclose(file);
// ============================================================
// READING BINARY DATA
// ============================================================
file = fopen("numbers.bin", "rb");
if (file == NULL) {
perror("Error opening file");
return 1;
}
int read_numbers[5];
size_t elements_read = fread(read_numbers, sizeof(int), 5, file);
if (elements_read != 5) {
printf("Error: Only read %zu elements\n", elements_read);
} else {
printf("Read numbers: ");
for (int i = 0; i < 5; i++) {
printf("%d ", read_numbers[i]);
}
printf("\n");
}
fclose(file);
return 0;
}
Reading and Writing Different Data Types
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
FILE *file = fopen("mixed_data.bin", "wb");
if (file == NULL) {
perror("Error opening file");
return 1;
}
// ============================================================
// WRITING DIFFERENT DATA TYPES
// ============================================================
int int_val = 12345;
float float_val = 3.14159f;
double double_val = 2.718281828459045;
char char_val = 'A';
char string_val[] = "Hello Binary World!";
// Write each value individually
fwrite(&int_val, sizeof(int), 1, file);
fwrite(&float_val, sizeof(float), 1, file);
fwrite(&double_val, sizeof(double), 1, file);
fwrite(&char_val, sizeof(char), 1, file);
fwrite(string_val, sizeof(char), strlen(string_val) + 1, file);
fclose(file);
// ============================================================
// READING DIFFERENT DATA TYPES
// ============================================================
file = fopen("mixed_data.bin", "rb");
if (file == NULL) {
perror("Error opening file");
return 1;
}
int read_int;
float read_float;
double read_double;
char read_char;
char read_string[100];
fread(&read_int, sizeof(int), 1, file);
fread(&read_float, sizeof(float), 1, file);
fread(&read_double, sizeof(double), 1, file);
fread(&read_char, sizeof(char), 1, file);
fread(read_string, sizeof(char), 100, file);
fclose(file);
printf("=== Read Values ===\n");
printf("int: %d\n", read_int);
printf("float: %f\n", read_float);
printf("double: %.15f\n", read_double);
printf("char: %c\n", read_char);
printf("string: %s\n", read_string);
return 0;
}
Writing and Reading Structures
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Structure to store in binary file
typedef struct {
int id;
char name[50];
float salary;
int age;
char department[30];
} Employee;
// Function to print employee
void printEmployee(Employee *emp, const char *title) {
printf("%s:\n", title);
printf(" ID: %d\n", emp->id);
printf(" Name: %s\n", emp->name);
printf(" Age: %d\n", emp->age);
printf(" Department: %s\n", emp->department);
printf(" Salary: $%.2f\n\n", emp->salary);
}
int main() {
// ============================================================
// WRITING STRUCTURES TO BINARY FILE
// ============================================================
// Create employee records
Employee employees[] = {
{101, "Alice Johnson", 75000.50, 28, "Engineering"},
{102, "Bob Smith", 82000.75, 35, "Marketing"},
{103, "Charlie Brown", 65000.25, 42, "Sales"},
{104, "Diana Prince", 91000.00, 31, "Management"}
};
int num_employees = sizeof(employees) / sizeof(employees[0]);
FILE *file = fopen("employees.bin", "wb");
if (file == NULL) {
perror("Error creating file");
return 1;
}
// Write number of employees first (useful for reading)
fwrite(&num_employees, sizeof(int), 1, file);
// Write all employee structures
size_t written = fwrite(employees, sizeof(Employee), num_employees, file);
if (written != num_employees) {
printf("Error: Only wrote %zu of %d employees\n",
written, num_employees);
} else {
printf("Successfully wrote %d employees to file\n", num_employees);
}
fclose(file);
// ============================================================
// READING STRUCTURES FROM BINARY FILE
// ============================================================
file = fopen("employees.bin", "rb");
if (file == NULL) {
perror("Error opening file");
return 1;
}
// Read number of employees
int read_count;
fread(&read_count, sizeof(int), 1, file);
printf("File contains %d employee records\n\n", read_count);
// Allocate array to hold employees
Employee *read_employees = malloc(read_count * sizeof(Employee));
if (read_employees == NULL) {
perror("Memory allocation failed");
fclose(file);
return 1;
}
// Read all employees
size_t read = fread(read_employees, sizeof(Employee), read_count, file);
if (read != read_count) {
printf("Error: Only read %zu of %d employees\n", read, read_count);
} else {
printf("Successfully read %d employees\n\n", read_count);
// Print all employees
for (int i = 0; i < read_count; i++) {
char title[50];
sprintf(title, "Employee %d", i + 1);
printEmployee(&read_employees[i], title);
}
}
fclose(file);
free(read_employees);
return 0;
}
Random Access in Binary Files
#include <stdio.h>
#include <stdlib.h>
// fseek() - move to specific position
// ftell() - get current position
// rewind() - go to beginning
typedef struct {
int id;
char name[50];
int score;
} StudentRecord;
void printRecord(StudentRecord *rec, const char *label) {
printf("%s: ID=%d, Name=%s, Score=%d\n",
label, rec->id, rec->name, rec->score);
}
int main() {
// Create sample records
StudentRecord records[] = {
{1, "Alice", 95},
{2, "Bob", 87},
{3, "Charlie", 92},
{4, "Diana", 98},
{5, "Eve", 89}
};
int num_records = sizeof(records) / sizeof(records[0]);
// Write records to file
FILE *file = fopen("records.bin", "wb");
fwrite(records, sizeof(StudentRecord), num_records, file);
fclose(file);
printf("=== Random Access Binary File ===\n\n");
file = fopen("records.bin", "rb+"); // Read and write mode
// ============================================================
// READ SPECIFIC RECORD BY INDEX
// ============================================================
int record_index = 2; // Want third record (0-based)
// Calculate position: sizeof(record) * index
long position = record_index * sizeof(StudentRecord);
// Seek to that position
fseek(file, position, SEEK_SET);
StudentRecord rec;
fread(&rec, sizeof(StudentRecord), 1, file);
printf("Record at index %d:\n", record_index);
printRecord(&rec, " ");
// ============================================================
// MODIFY SPECIFIC RECORD
// ============================================================
// Go back to same position
fseek(file, position, SEEK_SET);
// Modify the record
rec.score = 100; // Perfect score!
strcpy(rec.name, "Charlie (Updated)");
// Write it back
fwrite(&rec, sizeof(StudentRecord), 1, file);
// ============================================================
// READ ALL RECORDS TO VERIFY CHANGE
// ============================================================
printf("\nAll records after modification:\n");
rewind(file); // Go to beginning
for (int i = 0; i < num_records; i++) {
fread(&rec, sizeof(StudentRecord), 1, file);
printf(" [%d] ID=%d, %s, Score=%d\n",
i, rec.id, rec.name, rec.score);
}
// ============================================================
// DEMONSTRATE ftell() and SEEK_END
// ============================================================
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
printf("\nTotal file size: %ld bytes\n", file_size);
printf("Record size: %zu bytes\n", sizeof(StudentRecord));
printf("Number of records: %ld\n", file_size / sizeof(StudentRecord));
// Read last record
fseek(file, -sizeof(StudentRecord), SEEK_END);
fread(&rec, sizeof(StudentRecord), 1, file);
printf("\nLast record:\n");
printRecord(&rec, " ");
fclose(file);
return 0;
}
Binary File Database Example
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// ============================================================
// SIMPLE BINARY FILE DATABASE
// ============================================================
typedef struct {
int id;
char name[50];
char email[100];
int age;
double balance;
char active; // 'Y' or 'N'
} Customer;
const char *DB_FILE = "customers.db";
// Initialize empty database
int initDatabase() {
FILE *file = fopen(DB_FILE, "wb");
if (file == NULL) return 0;
// Write header with record count (initially 0)
int count = 0;
fwrite(&count, sizeof(int), 1, file);
fclose(file);
return 1;
}
// Add new customer
int addCustomer(Customer *cust) {
FILE *file = fopen(DB_FILE, "rb+");
if (file == NULL) return 0;
// Read current count
int count;
fread(&count, sizeof(int), 1, file);
// Move to end of file
fseek(file, 0, SEEK_END);
// Assign new ID
cust->id = count + 1;
cust->active = 'Y';
// Write customer
fwrite(cust, sizeof(Customer), 1, file);
// Update count at beginning of file
count++;
fseek(file, 0, SEEK_SET);
fwrite(&count, sizeof(int), 1, file);
fclose(file);
return cust->id;
}
// Find customer by ID
Customer* findCustomer(int id) {
FILE *file = fopen(DB_FILE, "rb");
if (file == NULL) return NULL;
// Read count
int count;
fread(&count, sizeof(int), 1, file);
// Calculate position (after header)
long pos = sizeof(int) + (id - 1) * sizeof(Customer);
if (id < 1 || id > count) {
fclose(file);
return NULL;
}
fseek(file, pos, SEEK_SET);
Customer *cust = malloc(sizeof(Customer));
fread(cust, sizeof(Customer), 1, file);
fclose(file);
if (cust->active != 'Y') {
free(cust);
return NULL;
}
return cust;
}
// Update customer
int updateCustomer(int id, Customer *newData) {
FILE *file = fopen(DB_FILE, "rb+");
if (file == NULL) return 0;
// Read count
int count;
fread(&count, sizeof(int), 1, file);
if (id < 1 || id > count) {
fclose(file);
return 0;
}
// Position to customer record
long pos = sizeof(int) + (id - 1) * sizeof(Customer);
fseek(file, pos, SEEK_SET);
// Preserve ID and active status
newData->id = id;
newData->active = 'Y';
fwrite(newData, sizeof(Customer), 1, file);
fclose(file);
return 1;
}
// Delete customer (soft delete)
int deleteCustomer(int id) {
FILE *file = fopen(DB_FILE, "rb+");
if (file == NULL) return 0;
// Read count
int count;
fread(&count, sizeof(int), 1, file);
if (id < 1 || id > count) {
fclose(file);
return 0;
}
// Position to customer record
long pos = sizeof(int) + (id - 1) * sizeof(Customer);
fseek(file, pos, SEEK_SET);
// Read customer
Customer cust;
fread(&cust, sizeof(Customer), 1, file);
// Mark as inactive
cust.active = 'N';
// Write back
fseek(file, pos, SEEK_SET);
fwrite(&cust, sizeof(Customer), 1, file);
fclose(file);
return 1;
}
// List all active customers
int listAllCustomers() {
FILE *file = fopen(DB_FILE, "rb");
if (file == NULL) return 0;
int count;
fread(&count, sizeof(int), 1, file);
printf("=== Customer List ===\n");
printf("%-4s %-20s %-30s %-4s %-10s\n",
"ID", "Name", "Email", "Age", "Balance");
printf("--------------------------------------------------------\n");
int active_count = 0;
for (int i = 0; i < count; i++) {
Customer cust;
fread(&cust, sizeof(Customer), 1, file);
if (cust.active == 'Y') {
printf("%-4d %-20s %-30s %-4d $%-9.2f\n",
cust.id, cust.name, cust.email, cust.age, cust.balance);
active_count++;
}
}
printf("--------------------------------------------------------\n");
printf("Total active customers: %d\n\n", active_count);
fclose(file);
return active_count;
}
int main() {
printf("=== Binary File Database ===\n\n");
// Initialize database
if (!initDatabase()) {
printf("Failed to initialize database\n");
return 1;
}
// Add some customers
Customer customers[] = {
{0, "John Doe", "[email protected]", 30, 1500.50, 'Y'},
{0, "Jane Smith", "[email protected]", 28, 2500.75, 'Y'},
{0, "Bob Wilson", "[email protected]", 35, 3200.00, 'Y'},
{0, "Alice Brown", "[email protected]", 42, 5000.25, 'Y'},
{0, "Charlie Davis", "[email protected]", 25, 800.30, 'Y'}
};
printf("Adding customers:\n");
for (int i = 0; i < 5; i++) {
int id = addCustomer(&customers[i]);
printf(" Added: %s (ID: %d)\n", customers[i].name, id);
}
// List all customers
printf("\n");
listAllCustomers();
// Find customer by ID
printf("Finding customer ID 3:\n");
Customer *cust = findCustomer(3);
if (cust) {
printf(" Found: %s, Balance: $%.2f\n", cust->name, cust->balance);
free(cust);
}
// Update customer
printf("\nUpdating customer ID 3:\n");
Customer updated = {0, "Bob Wilson Jr", "[email protected]", 36, 4200.00, 'Y'};
if (updateCustomer(3, &updated)) {
printf(" Customer updated successfully\n");
}
// Delete customer
printf("\nDeleting customer ID 2:\n");
if (deleteCustomer(2)) {
printf(" Customer deleted\n");
}
// List again
printf("\n");
listAllCustomers();
return 0;
}
Working with Binary File Headers
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// ============================================================
// BINARY FILE WITH CUSTOM HEADER
// ============================================================
typedef struct {
char signature[4]; // File identifier (e.g., "CUST")
int version; // File format version
time_t creation_time; // File creation timestamp
int record_count; // Number of records
int record_size; // Size of each record
char description[64]; // File description
} FileHeader;
typedef struct {
int id;
char name[50];
double data[5];
} DataRecord;
void writeHeader(FILE *file, FileHeader *header) {
fwrite(header, sizeof(FileHeader), 1, file);
}
void readHeader(FILE *file, FileHeader *header) {
fread(header, sizeof(FileHeader), 1, file);
}
int createDataFile(const char *filename, const char *description) {
FILE *file = fopen(filename, "wb");
if (file == NULL) return 0;
FileHeader header;
memcpy(header.signature, "DATA", 4);
header.version = 1;
header.creation_time = time(NULL);
header.record_count = 0;
header.record_size = sizeof(DataRecord);
strncpy(header.description, description, 63);
writeHeader(file, &header);
fclose(file);
return 1;
}
int addRecord(const char *filename, DataRecord *record) {
FILE *file = fopen(filename, "rb+");
if (file == NULL) return 0;
// Read header
FileHeader header;
readHeader(file, &header);
// Verify signature
if (memcmp(header.signature, "DATA", 4) != 0) {
fclose(file);
return 0;
}
// Move to end of file
fseek(file, 0, SEEK_END);
// Assign ID
record->id = header.record_count + 1;
// Write record
fwrite(record, sizeof(DataRecord), 1, file);
// Update header
header.record_count++;
fseek(file, 0, SEEK_SET);
writeHeader(file, &header);
fclose(file);
return record->id;
}
int readAllRecords(const char *filename) {
FILE *file = fopen(filename, "rb");
if (file == NULL) return 0;
// Read and validate header
FileHeader header;
readHeader(file, &header);
if (memcmp(header.signature, "DATA", 4) != 0) {
printf("Invalid file format!\n");
fclose(file);
return 0;
}
printf("=== File Information ===\n");
printf("Signature: %.4s\n", header.signature);
printf("Version: %d\n", header.version);
printf("Created: %s", ctime(&header.creation_time));
printf("Description: %s\n", header.description);
printf("Records: %d\n", header.record_count);
printf("Record size: %d bytes\n\n", header.record_size);
// Read and display records
if (header.record_count > 0) {
printf("=== Records ===\n");
for (int i = 0; i < header.record_count; i++) {
DataRecord record;
fread(&record, sizeof(DataRecord), 1, file);
printf("Record %d (ID: %d)\n", i + 1, record.id);
printf(" Name: %s\n", record.name);
printf(" Data: ");
for (int j = 0; j < 5; j++) {
printf("%.2f ", record.data[j]);
}
printf("\n\n");
}
}
fclose(file);
return header.record_count;
}
int main() {
printf("=== Binary File with Header ===\n\n");
const char *filename = "datafile.bin";
// Create new data file
if (createDataFile(filename, "Sample data records")) {
printf("Created data file: %s\n", filename);
}
// Add some records
for (int i = 0; i < 3; i++) {
DataRecord record;
sprintf(record.name, "Record %d", i + 1);
for (int j = 0; j < 5; j++) {
record.data[j] = (i + 1) * 10.0 + j;
}
int id = addRecord(filename, &record);
printf("Added record with ID: %d\n", id);
}
printf("\n");
// Read all records
readAllRecords(filename);
return 0;
}
Error Handling in Binary I/O
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
// Comprehensive error handling for binary file operations
typedef enum {
BIN_SUCCESS,
BIN_ERR_OPEN,
BIN_ERR_READ,
BIN_ERR_WRITE,
BIN_ERR_SEEK,
BIN_ERR_CLOSE,
BIN_ERR_MEMORY,
BIN_ERR_INVALID
} BinaryError;
const char* binaryErrorString(BinaryError err) {
switch (err) {
case BIN_SUCCESS: return "Success";
case BIN_ERR_OPEN: return "Failed to open file";
case BIN_ERR_READ: return "Failed to read from file";
case BIN_ERR_WRITE: return "Failed to write to file";
case BIN_ERR_SEEK: return "Failed to seek in file";
case BIN_ERR_CLOSE: return "Failed to close file";
case BIN_ERR_MEMORY: return "Memory allocation failed";
case BIN_ERR_INVALID: return "Invalid operation";
default: return "Unknown error";
}
}
BinaryError safeWrite(const char *filename, void *data, size_t size, size_t count) {
if (filename == NULL || data == NULL) {
return BIN_ERR_INVALID;
}
FILE *file = fopen(filename, "wb");
if (file == NULL) {
perror("fopen error");
return BIN_ERR_OPEN;
}
size_t written = fwrite(data, size, count, file);
if (written != count) {
if (ferror(file)) {
perror("fwrite error");
}
fclose(file);
return BIN_ERR_WRITE;
}
if (fclose(file) != 0) {
perror("fclose error");
return BIN_ERR_CLOSE;
}
return BIN_SUCCESS;
}
BinaryError safeRead(const char *filename, void *data, size_t size, size_t count) {
if (filename == NULL || data == NULL) {
return BIN_ERR_INVALID;
}
FILE *file = fopen(filename, "rb");
if (file == NULL) {
perror("fopen error");
return BIN_ERR_OPEN;
}
size_t read = fread(data, size, count, file);
if (read != count) {
if (feof(file)) {
printf("Unexpected end of file\n");
}
if (ferror(file)) {
perror("fread error");
}
fclose(file);
return BIN_ERR_READ;
}
if (fclose(file) != 0) {
perror("fclose error");
return BIN_ERR_CLOSE;
}
return BIN_SUCCESS;
}
BinaryError safeSeek(const char *filename, long offset, int whence) {
FILE *file = fopen(filename, "rb+");
if (file == NULL) {
return BIN_ERR_OPEN;
}
if (fseek(file, offset, whence) != 0) {
perror("fseek error");
fclose(file);
return BIN_ERR_SEEK;
}
fclose(file);
return BIN_SUCCESS;
}
int main() {
printf("=== Error Handling in Binary I/O ===\n\n");
// Test successful write
int data[] = {1, 2, 3, 4, 5};
BinaryError err = safeWrite("test.bin", data, sizeof(int), 5);
printf("Write result: %s\n", binaryErrorString(err));
// Test successful read
int read_data[5];
err = safeRead("test.bin", read_data, sizeof(int), 5);
printf("Read result: %s\n", binaryErrorString(err));
if (err == BIN_SUCCESS) {
printf("Read data: ");
for (int i = 0; i < 5; i++) {
printf("%d ", read_data[i]);
}
printf("\n");
}
// Test error cases
printf("\n--- Error Cases ---\n");
// File doesn't exist
err = safeRead("nonexistent.bin", data, sizeof(int), 5);
printf("Reading nonexistent file: %s\n", binaryErrorString(err));
// Invalid parameters
err = safeWrite(NULL, data, sizeof(int), 5);
printf("Invalid filename: %s\n", binaryErrorString(err));
return 0;
}
Performance Comparison: Binary vs Text I/O
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define NUM_RECORDS 100000
typedef struct {
int id;
double value;
char name[20];
} TestRecord;
void generateRecords(TestRecord *records, int count) {
for (int i = 0; i < count; i++) {
records[i].id = i + 1;
records[i].value = (double)rand() / RAND_MAX * 1000.0;
sprintf(records[i].name, "Item_%d", i + 1);
}
}
void writeText(const char *filename, TestRecord *records, int count) {
FILE *file = fopen(filename, "w");
for (int i = 0; i < count; i++) {
fprintf(file, "%d,%.6f,%s\n",
records[i].id, records[i].value, records[i].name);
}
fclose(file);
}
void readText(const char *filename, TestRecord *records, int count) {
FILE *file = fopen(filename, "r");
for (int i = 0; i < count; i++) {
fscanf(file, "%d,%lf,%s\n",
&records[i].id, &records[i].value, records[i].name);
}
fclose(file);
}
void writeBinary(const char *filename, TestRecord *records, int count) {
FILE *file = fopen(filename, "wb");
fwrite(records, sizeof(TestRecord), count, file);
fclose(file);
}
void readBinary(const char *filename, TestRecord *records, int count) {
FILE *file = fopen(filename, "rb");
fread(records, sizeof(TestRecord), count, file);
fclose(file);
}
int main() {
printf("=== Performance: Binary vs Text I/O ===\n\n");
TestRecord *records = malloc(NUM_RECORDS * sizeof(TestRecord));
TestRecord *read_records = malloc(NUM_RECORDS * sizeof(TestRecord));
generateRecords(records, NUM_RECORDS);
clock_t start, end;
// Test binary write
start = clock();
writeBinary("test.bin", records, NUM_RECORDS);
end = clock();
double binary_write_time = (double)(end - start) / CLOCKS_PER_SEC;
// Test binary read
start = clock();
readBinary("test.bin", read_records, NUM_RECORDS);
end = clock();
double binary_read_time = (double)(end - start) / CLOCKS_PER_SEC;
// Test text write
start = clock();
writeText("test.txt", records, NUM_RECORDS);
end = clock();
double text_write_time = (double)(end - start) / CLOCKS_PER_SEC;
// Test text read
start = clock();
readText("test.txt", read_records, NUM_RECORDS);
end = clock();
double text_read_time = (double)(end - start) / CLOCKS_PER_SEC;
// Get file sizes
FILE *bin = fopen("test.bin", "rb");
fseek(bin, 0, SEEK_END);
long bin_size = ftell(bin);
fclose(bin);
FILE *txt = fopen("test.txt", "rb");
fseek(txt, 0, SEEK_END);
long txt_size = ftell(txt);
fclose(txt);
printf("Records: %d\n", NUM_RECORDS);
printf("Record size: %zu bytes\n\n", sizeof(TestRecord));
printf("Binary I/O:\n");
printf(" Write time: %.3f seconds\n", binary_write_time);
printf(" Read time: %.3f seconds\n", binary_read_time);
printf(" File size: %ld bytes (%.2f KB)\n",
bin_size, bin_size / 1024.0);
printf("\nText I/O:\n");
printf(" Write time: %.3f seconds\n", text_write_time);
printf(" Read time: %.3f seconds\n", text_read_time);
printf(" File size: %ld bytes (%.2f KB)\n",
txt_size, txt_size / 1024.0);
printf("\nSpeed comparison:\n");
printf(" Binary write is %.2fx faster\n",
text_write_time / binary_write_time);
printf(" Binary read is %.2fx faster\n",
text_read_time / binary_read_time);
printf(" Binary file is %.2fx smaller\n",
(double)txt_size / bin_size);
free(records);
free(read_records);
return 0;
}
Best Practices and Common Pitfalls
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// ============================================================
// BEST PRACTICES AND COMMON PITFALLS
// ============================================================
// PITFALL 1: Not checking return values
// ALWAYS check fread/fwrite return values
// PITFALL 2: Assuming binary portability across platforms
// Different endianness, structure padding
// PITFALL 3: Not handling EOF properly
// Use feof() to check for end of file
// PITFALL 4: Buffer overflows
// Ensure buffers are large enough for reading
// PITFALL 5: Not flushing files
// Use fflush() or fclose() to ensure data is written
// Best Practice 1: Use sentinel values or headers
typedef struct {
char magic[4]; // File identifier
int version; // Format version
int record_count; // Number of records
// ... other metadata
} FileHeader;
// Best Practice 2: Handle endianness for cross-platform
void writeInt32LE(int value, FILE *file) {
unsigned char bytes[4];
bytes[0] = value & 0xFF;
bytes[1] = (value >> 8) & 0xFF;
bytes[2] = (value >> 16) & 0xFF;
bytes[3] = (value >> 24) & 0xFF;
fwrite(bytes, 1, 4, file);
}
int readInt32LE(FILE *file) {
unsigned char bytes[4];
fread(bytes, 1, 4, file);
return bytes[0] | (bytes[1] << 8) | (bytes[2] << 16) | (bytes[3] << 24);
}
// Best Practice 3: Use structure packing to control padding
#pragma pack(push, 1) // Pack structures on 1-byte boundary
typedef struct {
int id;
char name[50];
double salary;
} PackedEmployee;
#pragma pack(pop) // Restore default packing
// Best Practice 4: Version your file formats
typedef struct {
int version;
union {
struct { int x, y; } v1; // Version 1 format
struct { int x, y, z; } v2; // Version 2 format
} data;
} VersionedData;
// Best Practice 5: Use transaction-like writes
int safeWriteTransaction(const char *filename, void *data, size_t size) {
char tempfile[256];
sprintf(tempfile, "%s.tmp", filename);
// Write to temporary file
FILE *temp = fopen(tempfile, "wb");
if (temp == NULL) return 0;
if (fwrite(data, size, 1, temp) != 1) {
fclose(temp);
remove(tempfile);
return 0;
}
fclose(temp);
// Replace original with temporary
remove(filename);
rename(tempfile, filename);
return 1;
}
int main() {
printf("=== Best Practices and Pitfalls ===\n\n");
// Demonstrate endianness handling
FILE *file = fopen("endian_test.bin", "wb");
int test_value = 0x12345678;
writeInt32LE(test_value, file);
fclose(file);
file = fopen("endian_test.bin", "rb");
int read_value = readInt32LE(file);
fclose(file);
printf("Original: 0x%08X\n", test_value);
printf("Read: 0x%08X\n", read_value);
// Demonstrate structure packing
printf("\nStructure sizes:\n");
printf(" Normal Employee: %zu bytes\n", sizeof(PackedEmployee) + 3); // Approximate
printf(" Packed Employee: %zu bytes\n", sizeof(PackedEmployee));
return 0;
}
Summary Table
| Function | Purpose | Returns | Common Errors |
|---|---|---|---|
fopen() | Open file | FILE* or NULL | File not found, permissions |
fclose() | Close file | 0 on success | File not open |
fread() | Read binary data | Items read | EOF, read error |
fwrite() | Write binary data | Items written | Disk full, write error |
fseek() | Move file position | 0 on success | Invalid seek |
ftell() | Get file position | Position or -1L | Error |
feof() | Test for end-of-file | Non-zero if EOF | - |
ferror() | Test for error | Non-zero if error | - |
rewind() | Reset to beginning | void | - |
remove() | Delete file | 0 on success | File not found |
rename() | Rename file | 0 on success | File exists, permissions |
Conclusion
Binary file I/O in C provides direct, efficient access to data in its native format. Key takeaways:
- Performance: Binary I/O is significantly faster than text I/O for large datasets
- Precision: Exact representation of numeric values without conversion errors
- Storage Efficiency: Binary files are typically smaller than text equivalents
- Random Access: Direct positioning enables efficient data retrieval
- Complex Data: Structures can be read/written in single operations
- Portability: Consider endianness and padding for cross-platform compatibility
Best practices:
- Always check return values from I/O functions
- Use file headers to store metadata and validate format
- Consider structure packing for consistent layout
- Version your file formats for future compatibility
- Use temporary files for atomic write operations
- Handle errors gracefully with proper messages
Binary I/O is essential for applications dealing with large datasets, performance-critical operations, or requiring exact data preservation. From database systems to image processing, understanding binary file operations is fundamental to systems programming in C.