Stating the obvious about debugging an invalid parameter error when freeing memory

Writing it down for posterity. The post Stating the obvious about debugging an invalid parameter error when freeing memory appeared first on The Old New Thing.

Jun 6, 2025 - 03:50
 0
Stating the obvious about debugging an invalid parameter error when freeing memory

For historical reasons, Windows has many functions for allocating and freeing memory. This is particularly common when the memory is allocated by one component but freed by another. In that case, the allocating component typically provides a function to the consumer for freeing the memory, and the implementation is to forward the call to whatever memory freeing function matches the function that the component had used to allocate it. For example, if the producer used HeapAlloc to allocate from a custom heap, then the custom memory-freeing function will use HeapFree with that same custom heap.

What does it mean when you get an invalid parameter (or a crash) when you try to free the memory using the custom free function?

One possible reason is that you are not freeing the same pointer that was returned by the allocator. This commonly happens when the allocator produced an array of elements, and you walked through the array by incrementing the pointer.

MPR_TRANSPORT_0* result;
DWORD actual;
DWORD total;
if (MprConfigTransportEnum(mprConfig, 0, (BYTE*)&result,
        (DWORD)-1, &actual, &total, nullptr) != NO_ERROR)
{
    ⟦ ... error handling ... ⟧
}
// Process each of the transports
for (DWORD index = 0; index < actual; ++index, ++result) {
    switch (result->dwTransportId) {
    ⟦ ... ⟧
    }
}

// Free the buffer
MprConfigBufferFree(result); // oops

The problem here is that we received a pointer to the results in result, but then as we processed each result, we also modified the result variable. This means that the value passed to Mpr­Config­Buffer­Free is not the same value that was originally received, and it is therefore an invalid parameter.

To diagnose this type of bug, set a breakpoint at the return from Mpr­Config­Transport­Enum and inspect the returned pointer. Write it down somewhere safe, and then set another breakpoint at the Mpr­Config­Buffer­Free. Check the value you are passing to Mpr­Config­Buffer­Free and confirm that it matches the value you wrote down. If not, then you are freeing the wrong pointer.

One solution is simply never to modify the received pointer at all.

// Process each of the transports
for (DWORD index = 0; index < actual; ++index) {
    auto transport = result + index;
    switch (transport->dwTransportId) {
    ⟦ ... ⟧
    }
}

// Free the buffer
MprConfigBufferFree(result);

Or you can use a ranged for loop.

// Process each of the transports
for (auto&& transport : wil::make_range(result, actual)) {
    switch (transport.dwTransportId) {
    ⟦ ... ⟧
    }
}

// Free the buffer
MprConfigBufferFree(result);

If you really like to use pointers, you just have to make a copy and modify the copy.

MPR_TRANSPORT_0* transport = result;
// Process each of the transports
for (DWORD index = 0; index < actual; ++index, ++transport) {
    switch (transport->dwTransportId) {
    ⟦ ... ⟧
    }
}

// Free the buffer
MprConfigBufferFree(result);

If you decide to go this way, you may as well tweak the declarations to avoid a somewhat disturbing cast when calling Mpr­Config­Transport­Enum.

BYTE* result;
DWORD actual;
DWORD total;
if (MprConfigTransportEnum(mprConfig, 0, &result,
        (DWORD)-1, &actual, &total, nullptr) != NO_ERROR)
{
    ⟦ ... error handling ... ⟧
}

auto transport = (MPR_TRANSPORT_0*)result;
// Process each of the transports
for (DWORD index = 0; index < actual; ++index, ++transport) {
    switch (transport->dwTransportId) {
    ⟦ ... ⟧
    }
}

// Free the buffer
MprConfigBufferFree(result); // oops

You might decide to try to protect yourself from accidentally messing up the saved pointer by making a const copy.

MPR_TRANSPORT_0* result;
DWORD actual;
DWORD total;
if (MprConfigTransportEnum(mprConfig, 0, (BYTE*)&result,
        (DWORD)-1, &actual, &total, nullptr) != NO_ERROR)
{
    ⟦ ... error handling ... ⟧
}

const auto resultToFree = result;

// Process each of the transports
for (DWORD index = 0; index < actual; ++index, ++result) {
    switch (result->dwTransportId) {
    ⟦ ... ⟧
    }
}

// Free the buffer
MprConfigBufferFree(resultToFree);

Another case where the “free” operation fails or crashes is if you somehow managed to corrupt memory, say by indexing off the end of the returned array, or indexing off the end of some other array that results in corruption of the heap or the variable that holds the pointer to free.

This is harder to diagnose because C++ memory corruption is hard to diagnose in general. If you have access to tools like valgrind, BoundsChecker, or Application Verifier, you can use those to help chase down memory corruption bugs.

The point of today’s article was to just state the obvious about where this problem comes from and provide some ideas for diagnosing them.

The post Stating the obvious about debugging an invalid parameter error when freeing memory appeared first on The Old New Thing.