cpp-archivos-binarios

Reading and Writing Binary Files in C++

  • 4 min

A binary file is a file that stores bytes exactly as the program handles them, without converting them to readable text.

So far we have worked with text files (.txt). They are great because if you open them with Notepad, you can understand what they say. “Luis”, “35”, “100”.

But text files have two major problems:

  1. They are slow: To store the number 123456, the computer has to convert that binary integer into ASCII characters ‘1’, ‘2’, ‘3’… and when reading it, perform the reverse process (parsing).
  2. They take up more space: The number 123456 occupies 4 bytes in memory (int), but 6 bytes as text (one byte per character).

For high-performance applications, game saves, or image processing, we use Binary Files.

In the simplest examples, we copy the in-memory representation to the file without converting it to text. This representation is not always portable across architectures or compilers.

The std::ios::binary Mode

To work in binary, we use the same ofstream and ifstream classes, but we must specify a crucial flag in the constructor: std::ios::binary.

// Open for binary writing
std::ofstream file("data.bin", std::ios::binary);
Copied!

If you forget the std::ios::binary flag, the operating system might try to “fix” line endings (changing \n to \r\n on Windows), corrupting your binary data.

Writing with the .write() Method

In binary mode we do not use the << operator. We use the .write() method.

This method expects two things:

  1. Where the data starts in memory (a pointer to char).
  2. How many bytes we want to copy.

For this, we use the reinterpret_cast, which can be a bit intimidating at first.

Since .write() expects a char* (because it works with bytes), if we want to store an int or a double, we have to “trick” the function by saying: “Treat the address of this integer as if it were a bunch of bytes.”

int number = 12345;

std::ofstream file("integer.bin", std::ios::binary);

if (file.is_open()) {
    // reinterpret_cast<const char*>(&variable) -> Give me the address as bytes
    // sizeof(variable) -> How much it occupies
    file.write(reinterpret_cast<const char*>(&number), sizeof(number));
    
    file.close();
}
Copied!

Reading with the .read() Method

Reading is symmetric. We use .read(), passing it the memory address where we want to dump the data and how many bytes to read.

int readNumber = 0;

std::ifstream file("integer.bin", std::ios::binary);

if (file.is_open()) {
    file.read(reinterpret_cast<char*>(&readNumber), sizeof(readNumber));
    
    std::cout << "Read: " << readNumber << "\n"; // 12345
}
Copied!

Saving Plain Data Structures

The power of binary files is that we can store entire structures at once. Imagine a configuration structure:

struct Configuration {
    int id;
    double speed;
    bool fullscreen;
};

// ... in main ...
Configuration config = { 1, 99.5, true };

std::ofstream output("config.dat", std::ios::binary);
output.write(reinterpret_cast<const char*>(&config), sizeof(Configuration));
Copied!

Boom! We saved the entire structure in a single line of code. No loops, no text formatting.

The Danger of Pointers and Complex Objects

You should only directly dump trivially copyable types. For other types you need to define serialization.

If your class or structure contains pointers or dynamic containers like std::string or std::vector, YOU CANNOT use .write() directly on the object.

struct Player {
    std::string name; // DANGER! This manages dynamic memory
    int level;
};

Player p; p.name = "Luis";

// ❌ FATAL ERROR
file.write((char*)&p, sizeof(p)); 
Copied!

Why does it fail? A std::string manages its own internal representation, which may include pointers, size, capacity, or local storage depending on the implementation. If you dump the p object, you save that internal representation, not a stable format containing the text “Luis”.

When you load that file tomorrow, that memory address will be invalid or point to garbage. Your program will crash.

How to Save a std::string or vector?

You have to serialize it manually:

  1. First, save the length of the string (an int).
  2. Then, save the characters of the string.
// Save std::string
size_t len = name.size();
file.write((char*)&len, sizeof(len)); // 1. Length
file.write(name.c_str(), len);      // 2. Data
Copied!

To close this section on files, there is one important detail left. We have been using filenames as text strings, but C++ has a much more elegant and safer way to handle paths and directories.