microsoft / microsoft/STL

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

Open
#5,747 3 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
C++
Stars
11.1k
Forks
1.7k
Avg merge
4d 15h
Merged PRs (30d)
22

Description

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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by building the supplied repro.cpp command-line test with MSVC using /std:c++20 and /MDd, then trace the std::format path involved in localized integer formatting. Compare each allocation and deallocation in the output, including the pointer reported as deleted without a matching new. Done means the debug build no longer performs the mismatched deallocation and the repro's allocation and deletion records match.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.