STL icon indicating copy to clipboard operation
STL copied to clipboard

`<format>`: `_malloc_dbg` allocations freed with `delete` in `std::format` (debug builds)

Open friesofdoom opened this issue 3 months ago • 3 comments

Describe the bug

In MSVC STL debug builds, std::format appears to allocate temporary buffers using _malloc_dbg (or another internal heap allocation method) but frees them using the delete operator. This is a violation of C++ memory management rules, as memory allocated with _malloc_dbg must be freed with _free_dbg, and memory allocated with new must be freed with delete.

This behavior can lead to undefined behavior, including heap corruption or crashes, especially when using debug builds with CRT memory checks enabled.

Command-line test case

C:\Temp>type repro.cpp
#include <crtdbg.h>
#include <iostream>
#include <string>
#include <format>
#include <new>

// Custom new/delete to detect misuse
void* operator new(std::size_t size)
{
    void* p = std::malloc(size);
    if (!p) throw std::bad_alloc{};
    std::cout << "[new] size = " << size << "p = " << p << std::endl;
    return p;
}

void operator delete(void* p) noexcept
{
    std::cout << "[delete] ptr = " << p << std::endl;
    std::free(p);
}

static std::locale myLocale("en_US.UTF-8");

int main()
{
    int value = 123456;

    std::cout << "Calling std::format..." << std::endl;
    std::string formatted = std::format(myLocale, "{:L}", value); // simple format
    std::cout << "Result: " << formatted << std::endl;

    return 0;
}

C:\Temp>cl /std:c++20 /MDd /Zi /EHsc repro.cpp
<I dont know what the template wants from me here?>

Expected behavior

The output should have a delete line matching every new line, but there is an extra delete line, which was never newed. This is the output:

[new] size = 16p = 0000017AFF503990
[delete] ptr = 0000017AFF503990
Calling std::format...
[new] size = 16p = 0000017AFF521C10
[new] size = 16p = 0000017AFF522700
[delete] ptr = 0000017AFF521C60
[delete] ptr = 0000017AFF522700
Result: 123,456
[delete] ptr = 0000017AFF521C10

You can see there is one extra delete.

if you change the string formatting line to something like:

std::string formatted = std::string("") + std::to_string(value); // simple format

then the news and deletes match up

friesofdoom avatar Oct 01 '25 01:10 friesofdoom

Possibly duplicate of #160.

frederick-vs-ja avatar Oct 01 '25 05:10 frederick-vs-ja

Possibly duplicate of #160.

Nope. _malloc_dbg is a red herring. What's happening is that std::format calls std::numpunct<char>::do_grouping in the runtime DLL, which returns a string created using the DLL's operator new. The string is later destroyed inside std::format, using the replaced operator delete.

I think this is by design, albeit surprising.

cpplearner avatar Nov 12 '25 21:11 cpplearner

Ugh, separately compiled facets strike again.

I don't see a way around this before vNext.

StephanTLavavej avatar Nov 12 '25 23:11 StephanTLavavej