C
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Arrays and Strings
-
Section 5: Pointers
-
Section 6: Memory Management
-
Section 7: Structures and Unions
-
Section 8: The Preprocessor and Build Process
-
Section 9: Standard Library: stdio.h
-
Section 10: Standard Library: stdlib.h
-
Section 11: Standard Library: string.h
-
Section 12: Standard Library: ctype.h and wctype.h
-
Section 13: Standard Library: math.h, complex.h, fenv.h, tgmath.h
-
Section 14: Standard Library: Type and Limit Headers
-
Section 15: Standard Library: Error Handling and Debugging
-
Section 16: Standard Library: Localization and Encoding
-
Section 17: Standard Library: time.h
-
Section 18: Standard Library: Concurrency (C11)
-
Section 19: POSIX and System Programming (unistd.h)
-
Section 20: More Data Structures
-
Section 21: Algorithms in C
-
Section 22: Bitwise Operations
-
Section 23: Command-Line Programs
-
Section 24: Debugging and Best Practices
-
Section 25: Compiler and Language Internals
-
Section 26: Embedded and Cross-Platform Considerations
-
Section 27: Networking Basics
-
Section 28: Practical Projects
-
Section 29: Interview Practice
-
Section 30: C23 Modern Features
-
Section 31: More Practice and Review
83: Reading and Writing Files with fread/fwrite
I see this mistake all the time when developers transition from fprintf to fwrite. They think of fwrite as a magic function that knows how to "serialize" an object. It doesn't. It is a blunt instrument: it copies a raw chunk of memory from your RAM directly onto the disk. If that memory contains a pointer, fwrite will happily save the memory address—a number that will be completely meaningless the next time you run your program.
// THE BROKEN CODE
typedef struct {
char *name;
int score;
} Player;
void save_player(Player *p) {
FILE *f = fopen("save.bin", "wb");
// BUG: This saves the address of the 'name' string, not the string itself!
fwrite(p, sizeof(Player), 1, f);
fclose(f);
}
The Pointer Persistence Trap
If you run the code above, it will compile and run without errors. But when you try to fread that data back into a Player struct, your program will likely segfault. Why? Because you saved the memory address where the name "Alice" lived during the first execution. When you restart the program, that address is either empty or owned by another process. You've saved a map to a house that has since been demolished.
Fixing the Memory Layout
To fix this, you have to ensure the data you are writing is "contiguous." This means the actual data—the characters of the name—must be stored inside the struct, not pointed to from outside it. By changing the pointer to a fixed-size array, the name becomes part of the struct's memory block.
// THE FIX
typedef struct {
char name[32]; // Fixed size: the data is now INSIDE the struct
int score;
} Player;
void save_player(Player *p) {
FILE *f = fopen("save.bin", "wb");
if (f == NULL) return;
// Now fwrite copies the 32 bytes of the name and the 4 bytes of the score
fwrite(p, sizeof(Player), 1, f);
fclose(f);
}
The Mechanics of fread and fwrite
Both functions follow a similar signature: (pointer_to_data, size_of_element, number_of_elements, file_pointer). I find it easiest to think of it as: "Start here, take this many bytes, do it this many times."
- fwrite: Grabs a block of memory and dumps it. It's incredibly fast because there is no formatting (no converting numbers to text).
- fread: Pulls a block of memory from the disk and shoves it directly into a variable.
Why the "b" in "rb" and "wb" Matters
You'll notice I used "wb" instead of just "w". On Linux, this doesn't do much. But on Windows, it's critical. In text mode, Windows will automatically change \n (newline) characters to \r\n. If you are writing a binary file (like an image or a struct) and a byte happens to have the value 10 (the ASCII for \n), Windows will "helpfully" insert an extra byte, corrupting your binary data entirely. Always use the b flag for fread and fwrite.
Handling Arrays of Structs
One of the biggest advantages of these functions is how they handle arrays. You don't need a loop to save a list of 1,000 items. Since an array is just a contiguous block of memory, you can write the whole thing in one call.
Player party[10];
// Fill party with data...
FILE *f = fopen("party.bin", "wb");
// Write all 10 players at once
fwrite(party, sizeof(Player), 10, f);
fclose(f);
When reading them back, just make sure your destination array is large enough to hold the data, or you'll be staring at a memory corruption nightmare.
📋 Practical Task
Build a Binary Coordinate Logger
Create a program that records 3D coordinates (x, y, z) for a camera path. Your task is to:
- Define a
Coordinatestruct containing threefloatvalues. - Create an array of 5
Coordinateobjects and initialize them with some sample data. - Write the entire array to a file named
path.binusingfwrite. - Close the file, then immediately open it again in read mode.
- Read the data back into a different array using
fread. - Print the coordinates of the second and fourth elements to the console to verify the data survived the trip to the disk and back.
There are no comments for now.