Docs
README
Random Access in C File Handling
Table of Contents
- •Introduction to Random Access
- •Sequential vs Random Access
- •The fseek() Function
- •The ftell() Function
- •The rewind() Function
- •The fgetpos() and fsetpos() Functions
- •Random Access with Fixed-Size Records
- •Practical Applications
- •Error Handling
- •Best Practices
- •Common Pitfalls
Introduction to Random Access
Random access allows you to read from or write to any position in a file without reading all preceding data. This is essential for databases, indexed files, and any application where you need to jump to specific locations.
Why Random Access?
- •Efficiency: Jump directly to needed data
- •Update in place: Modify records without rewriting entire file
- •Large file handling: Work with files too big to load into memory
- •Database operations: Implement search, update, delete operations
Key Functions
| Function | Purpose |
|---|---|
fseek() | Move to specific position |
ftell() | Get current position |
rewind() | Move to beginning |
fgetpos() | Save position (portable) |
fsetpos() | Restore position (portable) |
Sequential vs Random Access
Sequential Access
// Must read all preceding data
while (!feof(fp)) {
fread(&record, sizeof(record), 1, fp);
if (record.id == target_id) break;
}
Characteristics:
- •Reads data in order from start to end
- •Simple to implement
- •Good for processing all records
- •Slow for finding specific records
Random Access
// Jump directly to record #5
fseek(fp, 5 * sizeof(record), SEEK_SET);
fread(&record, sizeof(record), 1, fp);
Characteristics:
- •Jump to any position instantly
- •More complex to implement
- •Essential for large files
- •Requires fixed-size records or index
The fseek() Function
Function Signature
int fseek(FILE *stream, long offset, int whence);
Parameters
| Parameter | Description |
|---|---|
stream | File pointer |
offset | Number of bytes to move (can be negative) |
whence | Starting point for offset |
Whence Values
| Constant | Value | Starting Point |
|---|---|---|
SEEK_SET | 0 | Beginning of file |
SEEK_CUR | 1 | Current position |
SEEK_END | 2 | End of file |
Return Value
- •0: Success
- •Non-zero: Error (check
errno)
Examples
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.bin", "rb");
if (fp == NULL) return 1;
// Move to beginning of file
fseek(fp, 0, SEEK_SET);
// Move to position 100
fseek(fp, 100, SEEK_SET);
// Move forward 50 bytes from current position
fseek(fp, 50, SEEK_CUR);
// Move backward 10 bytes from current position
fseek(fp, -10, SEEK_CUR);
// Move to end of file
fseek(fp, 0, SEEK_END);
// Move 100 bytes before end of file
fseek(fp, -100, SEEK_END);
fclose(fp);
return 0;
}
Getting File Size
long get_file_size(FILE *fp) {
long current = ftell(fp); // Save current position
fseek(fp, 0, SEEK_END); // Go to end
long size = ftell(fp); // Get position (= size)
fseek(fp, current, SEEK_SET); // Restore position
return size;
}
The ftell() Function
Function Signature
long ftell(FILE *stream);
Return Value
- •Success: Current position in bytes from beginning
- •Error: -1L (check
errno)
Examples
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) return 1;
// Get initial position
long pos = ftell(fp);
printf("Initial position: %ld\n", pos); // 0
// Read some data
char buffer[100];
fgets(buffer, sizeof(buffer), fp);
// Get new position
pos = ftell(fp);
printf("After reading: %ld\n", pos);
// Seek and check
fseek(fp, 50, SEEK_SET);
pos = ftell(fp);
printf("After seek: %ld\n", pos); // 50
fclose(fp);
return 0;
}
Counting Records
long count_records(const char *filename, size_t record_size) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) return -1;
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fclose(fp);
return size / record_size;
}
The rewind() Function
Function Signature
void rewind(FILE *stream);
Behavior
- •Moves file position to beginning
- •Clears error indicator
- •Equivalent to:
fseek(fp, 0, SEEK_SET)plus clearing errors
Example
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) return 1;
// Read first line
char line[100];
fgets(line, sizeof(line), fp);
printf("First read: %s", line);
// Go back to beginning
rewind(fp);
// Read first line again
fgets(line, sizeof(line), fp);
printf("After rewind: %s", line);
fclose(fp);
return 0;
}
rewind() vs fseek()
// These are similar but not identical:
rewind(fp); // Clears error indicator, no return value
int result = fseek(fp, 0, SEEK_SET); // Doesn't clear errors, returns status
Use rewind() when:
- •You want to start over from beginning
- •Error recovery is needed
Use fseek() when:
- •You need to check for errors
- •Moving to positions other than start
The fgetpos() and fsetpos() Functions
Why Use These?
- •Portability: Work with files larger than
longcan represent - •Opacity: Position is stored in
fpos_ttype - •Reliability: Better for very large files
Function Signatures
int fgetpos(FILE *stream, fpos_t *pos);
int fsetpos(FILE *stream, const fpos_t *pos);
Return Value
- •0: Success
- •Non-zero: Error (sets
errno)
Example
#include <stdio.h>
int main(void) {
FILE *fp = fopen("data.txt", "r");
if (fp == NULL) return 1;
fpos_t position;
// Read and skip some data
char buffer[100];
fgets(buffer, sizeof(buffer), fp);
// Save current position
if (fgetpos(fp, &position) != 0) {
perror("fgetpos failed");
fclose(fp);
return 1;
}
// Read more data
fgets(buffer, sizeof(buffer), fp);
printf("Second line: %s", buffer);
// Restore saved position
if (fsetpos(fp, &position) != 0) {
perror("fsetpos failed");
fclose(fp);
return 1;
}
// Read same line again
fgets(buffer, sizeof(buffer), fp);
printf("After restore: %s", buffer);
fclose(fp);
return 0;
}
When to Use fgetpos/fsetpos vs ftell/fseek
| Scenario | Recommended |
|---|---|
| Files < 2GB, arithmetic on position | ftell/fseek |
| Very large files | fgetpos/fsetpos |
| Maximum portability | fgetpos/fsetpos |
| Simple offset calculations | ftell/fseek |
Random Access with Fixed-Size Records
Record Structure
#define RECORD_SIZE 128 // Fixed size for each record
struct Record {
int id;
char name[50];
char data[RECORD_SIZE - sizeof(int) - 50]; // Padding to fixed size
};
Calculating Record Position
// Position of record N (0-indexed)
long position = N * sizeof(struct Record);
Reading Specific Record
#include <stdio.h>
struct Employee {
int id;
char name[50];
float salary;
};
int read_record(FILE *fp, int record_num, struct Employee *emp) {
// Calculate position
long pos = record_num * sizeof(struct Employee);
// Seek to position
if (fseek(fp, pos, SEEK_SET) != 0) {
return -1; // Seek failed
}
// Read record
if (fread(emp, sizeof(struct Employee), 1, fp) != 1) {
return -1; // Read failed
}
return 0; // Success
}
Writing Specific Record
int write_record(FILE *fp, int record_num, const struct Employee *emp) {
long pos = record_num * sizeof(struct Employee);
if (fseek(fp, pos, SEEK_SET) != 0) {
return -1;
}
if (fwrite(emp, sizeof(struct Employee), 1, fp) != 1) {
return -1;
}
return 0;
}
Complete CRUD Example
#include <stdio.h>
#include <string.h>
struct Record {
int id;
char name[50];
int active; // 0 = deleted, 1 = active
};
// Create/Update record
int save_record(const char *filename, int index, const struct Record *rec) {
FILE *fp = fopen(filename, "r+b");
if (fp == NULL) {
fp = fopen(filename, "wb");
if (fp == NULL) return -1;
}
fseek(fp, index * sizeof(struct Record), SEEK_SET);
size_t written = fwrite(rec, sizeof(struct Record), 1, fp);
fclose(fp);
return (written == 1) ? 0 : -1;
}
// Read record
int load_record(const char *filename, int index, struct Record *rec) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) return -1;
fseek(fp, index * sizeof(struct Record), SEEK_SET);
size_t read = fread(rec, sizeof(struct Record), 1, fp);
fclose(fp);
return (read == 1) ? 0 : -1;
}
// Delete record (mark as inactive)
int delete_record(const char *filename, int index) {
struct Record rec;
if (load_record(filename, index, &rec) != 0) return -1;
rec.active = 0; // Mark as deleted
return save_record(filename, index, &rec);
}
// Count total records
int count_records(const char *filename) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) return 0;
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fclose(fp);
return size / sizeof(struct Record);
}
Practical Applications
Application 1: Simple Database
#include <stdio.h>
#include <string.h>
#define MAX_NAME 50
#define DB_FILE "database.bin"
struct Student {
int id;
char name[MAX_NAME];
float gpa;
int valid; // 1 = valid, 0 = deleted
};
// Find student by ID
int find_student(int id, struct Student *result) {
FILE *fp = fopen(DB_FILE, "rb");
if (fp == NULL) return -1;
struct Student s;
int index = 0;
while (fread(&s, sizeof(struct Student), 1, fp) == 1) {
if (s.valid && s.id == id) {
*result = s;
fclose(fp);
return index;
}
index++;
}
fclose(fp);
return -1; // Not found
}
// Update student at specific index
int update_student(int index, const struct Student *s) {
FILE *fp = fopen(DB_FILE, "r+b");
if (fp == NULL) return -1;
if (fseek(fp, index * sizeof(struct Student), SEEK_SET) != 0) {
fclose(fp);
return -1;
}
if (fwrite(s, sizeof(struct Student), 1, fp) != 1) {
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
Application 2: Index-Based File
#include <stdio.h>
#include <string.h>
// Index entry
struct IndexEntry {
int key;
long offset; // Position in data file
};
// Data record (variable conceptually, but fixed in this example)
struct DataRecord {
int id;
char content[256];
};
// Build index for existing data file
int build_index(const char *data_file, const char *index_file) {
FILE *data = fopen(data_file, "rb");
FILE *idx = fopen(index_file, "wb");
if (data == NULL || idx == NULL) {
if (data) fclose(data);
if (idx) fclose(idx);
return -1;
}
struct DataRecord rec;
struct IndexEntry entry;
while (1) {
entry.offset = ftell(data); // Save position before reading
if (fread(&rec, sizeof(struct DataRecord), 1, data) != 1) {
break;
}
entry.key = rec.id;
fwrite(&entry, sizeof(struct IndexEntry), 1, idx);
}
fclose(data);
fclose(idx);
return 0;
}
// Find record using index (binary search)
int find_by_index(const char *data_file, const char *index_file,
int key, struct DataRecord *result) {
FILE *idx = fopen(index_file, "rb");
if (idx == NULL) return -1;
// Get number of index entries
fseek(idx, 0, SEEK_END);
long count = ftell(idx) / sizeof(struct IndexEntry);
// Binary search
long left = 0, right = count - 1;
struct IndexEntry entry;
while (left <= right) {
long mid = (left + right) / 2;
fseek(idx, mid * sizeof(struct IndexEntry), SEEK_SET);
fread(&entry, sizeof(struct IndexEntry), 1, idx);
if (entry.key == key) {
fclose(idx);
// Found! Now read data record
FILE *data = fopen(data_file, "rb");
if (data == NULL) return -1;
fseek(data, entry.offset, SEEK_SET);
fread(result, sizeof(struct DataRecord), 1, data);
fclose(data);
return 0;
}
if (entry.key < key) {
left = mid + 1;
} else {
right = mid - 1;
}
}
fclose(idx);
return -1; // Not found
}
Application 3: Log File with Rotation
#include <stdio.h>
#include <time.h>
#include <string.h>
#define MAX_LOG_SIZE (1024 * 1024) // 1MB
#define LOG_FILE "app.log"
void rotate_log(void) {
// Rename current log to backup
rename(LOG_FILE, LOG_FILE ".bak");
}
void write_log(const char *message) {
FILE *fp = fopen(LOG_FILE, "a+b");
if (fp == NULL) return;
// Check file size
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
if (size > MAX_LOG_SIZE) {
fclose(fp);
rotate_log();
fp = fopen(LOG_FILE, "wb");
if (fp == NULL) return;
}
// Write timestamp and message
time_t now = time(NULL);
struct tm *tm = localtime(&now);
char timestamp[32];
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm);
fprintf(fp, "[%s] %s\n", timestamp, message);
fclose(fp);
}
Error Handling
Checking fseek Errors
if (fseek(fp, offset, SEEK_SET) != 0) {
perror("fseek failed");
// Handle error
}
Checking ftell Errors
long pos = ftell(fp);
if (pos == -1L) {
perror("ftell failed");
// Handle error
}
Validating Seek Position
int seek_to_record(FILE *fp, int record_num, size_t record_size) {
// First, get file size
long current = ftell(fp);
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
// Calculate target position
long target = record_num * record_size;
// Validate
if (target < 0 || target >= file_size) {
fseek(fp, current, SEEK_SET); // Restore position
return -1; // Invalid position
}
// Seek to target
if (fseek(fp, target, SEEK_SET) != 0) {
return -1;
}
return 0;
}
Complete Error-Checked Read
int safe_read_record(const char *filename, int record_num,
void *buffer, size_t record_size) {
FILE *fp = fopen(filename, "rb");
if (fp == NULL) {
perror("Cannot open file");
return -1;
}
// Get file size
if (fseek(fp, 0, SEEK_END) != 0) {
perror("Seek to end failed");
fclose(fp);
return -1;
}
long file_size = ftell(fp);
if (file_size == -1L) {
perror("ftell failed");
fclose(fp);
return -1;
}
// Calculate and validate position
long position = record_num * record_size;
if (position < 0 || position + record_size > file_size) {
fprintf(stderr, "Record %d out of range\n", record_num);
fclose(fp);
return -1;
}
// Seek to record
if (fseek(fp, position, SEEK_SET) != 0) {
perror("Seek to record failed");
fclose(fp);
return -1;
}
// Read record
if (fread(buffer, record_size, 1, fp) != 1) {
if (feof(fp)) {
fprintf(stderr, "Unexpected EOF\n");
} else {
perror("Read failed");
}
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
Best Practices
1. Use Fixed-Size Records for Random Access
// Good: Fixed size, easy to calculate position
struct Record {
int id;
char name[50]; // Fixed size
char padding[6]; // Explicit padding if needed
};
// Avoid: Variable size, requires index
struct Variable {
int id;
char *name; // Pointer! Cannot be stored directly
};
2. Save and Restore Position When Needed
void peek_next_record(FILE *fp, struct Record *rec) {
long saved_pos = ftell(fp);
fread(rec, sizeof(struct Record), 1, fp);
fseek(fp, saved_pos, SEEK_SET); // Restore position
}
3. Use r+b for Update Operations
// Read and update existing file
FILE *fp = fopen("data.bin", "r+b");
// Read record
struct Record rec;
fread(&rec, sizeof(rec), 1, fp);
// Go back to record start
fseek(fp, -sizeof(rec), SEEK_CUR);
// Update and write
rec.value *= 2;
fwrite(&rec, sizeof(rec), 1, fp);
4. Always Check Return Values
if (fseek(fp, offset, SEEK_SET) != 0) {
// Handle error
}
long pos = ftell(fp);
if (pos == -1L) {
// Handle error
}
5. Flush After Random Writes
// After updating records randomly
fwrite(&rec, sizeof(rec), 1, fp);
fflush(fp); // Ensure data is written
Common Pitfalls
Pitfall 1: Seeking in Text Mode
// PROBLEM: Seeking in text mode may not work as expected
FILE *fp = fopen("text.txt", "r");
fseek(fp, 100, SEEK_SET); // May not land at byte 100
// SOLUTION: Use binary mode for random access
FILE *fp = fopen("text.txt", "rb");
Pitfall 2: Seeking Past End of File
// PROBLEM: Seeking past EOF creates sparse file on some systems
fseek(fp, 1000000, SEEK_SET); // May create hole
// SOLUTION: Check file size first
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
if (desired_pos > size) {
// Handle error or extend file properly
}
Pitfall 3: Forgetting to Seek After Read/Write Mix
// PROBLEM: Reading then writing without seek
fread(&rec, sizeof(rec), 1, fp);
fwrite(&rec, sizeof(rec), 1, fp); // Writes at wrong position!
// SOLUTION: Seek before switching operations
fread(&rec, sizeof(rec), 1, fp);
fseek(fp, -sizeof(rec), SEEK_CUR); // Go back
fwrite(&rec, sizeof(rec), 1, fp); // Now correct
Pitfall 4: Integer Overflow in Position Calculation
// PROBLEM: Overflow with large files
int record_num = 1000000;
long pos = record_num * sizeof(struct Record); // May overflow!
// SOLUTION: Cast to long first
long pos = (long)record_num * sizeof(struct Record);
Pitfall 5: Not Handling Negative Seek
// PROBLEM: Seeking before beginning
fseek(fp, -100, SEEK_SET); // Undefined behavior!
// SOLUTION: Validate offset
long offset = -100;
if (offset < 0 && whence == SEEK_SET) {
// Error: cannot seek before beginning
}
Complete Example: Mini Database System
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define DB_FILE "minidb.dat"
#define MAX_RECORDS 1000
struct Record {
int id;
char name[50];
char email[50];
time_t created;
int deleted; // Soft delete flag
};
// Get total record count
int get_record_count(void) {
FILE *fp = fopen(DB_FILE, "rb");
if (fp == NULL) return 0;
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fclose(fp);
return size / sizeof(struct Record);
}
// Add new record
int add_record(const struct Record *rec) {
FILE *fp = fopen(DB_FILE, "ab");
if (fp == NULL) return -1;
if (fwrite(rec, sizeof(struct Record), 1, fp) != 1) {
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
// Get record by index
int get_record(int index, struct Record *rec) {
FILE *fp = fopen(DB_FILE, "rb");
if (fp == NULL) return -1;
long pos = index * sizeof(struct Record);
if (fseek(fp, pos, SEEK_SET) != 0) {
fclose(fp);
return -1;
}
if (fread(rec, sizeof(struct Record), 1, fp) != 1) {
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
// Update record by index
int update_record(int index, const struct Record *rec) {
FILE *fp = fopen(DB_FILE, "r+b");
if (fp == NULL) return -1;
long pos = index * sizeof(struct Record);
if (fseek(fp, pos, SEEK_SET) != 0) {
fclose(fp);
return -1;
}
if (fwrite(rec, sizeof(struct Record), 1, fp) != 1) {
fclose(fp);
return -1;
}
fclose(fp);
return 0;
}
// Delete record (soft delete)
int delete_record(int index) {
struct Record rec;
if (get_record(index, &rec) != 0) return -1;
rec.deleted = 1;
return update_record(index, &rec);
}
// Find record by ID
int find_by_id(int id, struct Record *rec) {
FILE *fp = fopen(DB_FILE, "rb");
if (fp == NULL) return -1;
int index = 0;
while (fread(rec, sizeof(struct Record), 1, fp) == 1) {
if (!rec->deleted && rec->id == id) {
fclose(fp);
return index;
}
index++;
}
fclose(fp);
return -1;
}
// List all records
void list_records(void) {
FILE *fp = fopen(DB_FILE, "rb");
if (fp == NULL) {
printf("No records found.\n");
return;
}
struct Record rec;
int index = 0;
printf("%-5s %-6s %-20s %-30s %-20s\n",
"Idx", "ID", "Name", "Email", "Created");
printf("%-5s %-6s %-20s %-30s %-20s\n",
"---", "--", "----", "-----", "-------");
while (fread(&rec, sizeof(struct Record), 1, fp) == 1) {
if (!rec.deleted) {
char time_str[20];
strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M",
localtime(&rec.created));
printf("%-5d %-6d %-20s %-30s %-20s\n",
index, rec.id, rec.name, rec.email, time_str);
}
index++;
}
fclose(fp);
}
Summary
Random access file operations allow efficient manipulation of file data:
| Function | Purpose | Key Usage |
|---|---|---|
fseek() | Move to position | fseek(fp, offset, SEEK_SET) |
ftell() | Get position | long pos = ftell(fp) |
rewind() | Go to start | rewind(fp) |
fgetpos() | Save position | fgetpos(fp, &pos) |
fsetpos() | Restore position | fsetpos(fp, &pos) |
Key Points:
- •Use binary mode for reliable random access
- •Fixed-size records simplify position calculation
- •Always validate seek positions
- •Check return values for errors
- •Save/restore position when peeking
- •Use
r+bmode for read-write operations
Random access is essential for implementing databases, file editors, and any application that needs to modify specific parts of large files efficiently.