Skip to content

58 Intermediate The Hundred

The Copy That Freed Your Memory Twice

one new[], one owner, and somehow two funerals

#include <cstring>
#include <iostream>

struct Buffer {
    char* data;
    Buffer(const char* s) : data(new char[std::strlen(s) + 1]) { std::strcpy(data, s); }
    ~Buffer() {
        std::cout << "freeing " << static_cast<void*>(data) << std::endl;
        delete[] data;
    }
};

void show(Buffer b) { std::cout << "show sees: " << b.data << std::endl; }

int main() {
    Buffer greeting("hello, world");
    show(greeting);   // just printing it, nothing gets modified
}

Run it. There is one new[] — how many frees do you expect?

Answer

Two, at the same address — and the second one kills the process. Typical output (GCC 11 / glibc on x86-64 Linux; the address varies run to run, the repeat does not):

show sees: hello, world
freeing 0x959eb0
freeing 0x959eb0
free(): double free detected in tcache 2
Aborted (core dumped)

Why

Buffer never says how it should be copied, so the compiler writes that constructor for it — memberwise: for a lone char* that copies the address, not the bytes. show's by-value parameter is a second Buffer aimed at the same block; it destructs first, then greeting frees the block again. That second delete[] is undefined behavior — glibc's tcache catches it here, but it could as easily corrupt the heap quietly or appear to work.

-Wall -Wextra say nothing here; GCC and Clang both warn under -Wdeprecated-copy-dtor.

The fix

struct Buffer {         // no destructor, no copy operations, nothing to get wrong
    std::string data;   // copies and frees itself (a std::vector member would too)
    Buffer(const char* s) : data(s) {}   // without it, C++17 needs Buffer{"hello, world"}
};

That's the Rule of Zero. Own raw memory and the Rule of Three binds: a destructor obliges a copy constructor and copy assignment too — plus the two moves since C++11, so Five.

Takeaway: if your destructor releases something, the default copy constructor is a bug.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry