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:
- 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). - They take up more space: The number
123456occupies 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);
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:
- Where the data starts in memory (a pointer to char).
- 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();
}
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
}
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));
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));
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:
- First, save the length of the string (an
int). - 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
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.
Continue with the following article:
”