Skip to Content
Course content

83: Reading and Writing Files with fread/fwrite

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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:

  1. Define a Coordinate struct containing three float values.
  2. Create an array of 5 Coordinate objects and initialize them with some sample data.
  3. Write the entire array to a file named path.bin using fwrite.
  4. Close the file, then immediately open it again in read mode.
  5. Read the data back into a different array using fread.
  6. Print the coordinates of the second and fourth elements to the console to verify the data survived the trip to the disk and back.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.