Date: Fri, 11 Oct 2024 12:11:51 +0100
There are times when we allocate memory which we never intend to
deallocate. For example, I'm writing a program at the moment that
loads in plugins, and the plugins stay loaded until the end of the
program.
The only problem with this is that when you run a debugging tool to
detect memory leaks, you get a load of false positives. I had a
program before that linked with libpango, and I spent ages trying to
find a memory leak, wondering if I was misusing the library or if the
library internally had a bug, and I went the whole hog and joined the
libpango mailing list and shared all my debugging output. In the end
it turned out to be a benign memory leak -- i.e. the allocation
happened once in the program and was intended to last the entire
program.
But what if we could do the following:
void *p = malloc( 1024ul * 1024ul * 64ul );
std::deliberate_leak(p);
Or:
long unsigned *p = new long unsigned[2048u];
std::deliberate_leak(p);
It could work something like this: https://godbolt.org/z/Gx6Pc6ne5
And then when you compile in Release Mode, you don't bother deallocating.
And here it is copy-pasted:
#include <cstdlib> // abort, atexit, free
#include <mutex> // lock_guard, mutex
#define PRINT_TO_SCREEN // ------- You can comment this line out -------
#ifdef PRINT_TO_SCREEN
# include <cstdint> // uintptr_t
# include <iomanip> // hex
# include <iostream> // cout, endl
#endif
void DeliberateLeak(void const volatile *const arg) noexcept
{
constexpr unsigned capacity = 64u;
static void const volatile *leaks[capacity] = {}; // all nullptr's
static std::mutex m;
std::lock_guard mylock(m);
if ( nullptr == leaks[0u] )
{
std::atexit(
[](void) noexcept -> void
{
for ( unsigned i = 0u; i < capacity; ++i )
{
if ( nullptr == leaks[i] ) return;
std::free( const_cast<void*>(leaks[i]) );
#ifdef PRINT_TO_SCREEN
std::cout << "Freeing deliberately leaked memory
at address 0x"
<< std::hex
<< reinterpret_cast<std::uintptr_t>(leaks[i])
<< std::endl;
#endif
}
});
}
static unsigned index = 0u;
if ( index >= capacity ) std::abort(); // no more free slots available
leaks[index++] = arg;
}
int main(void)
{
void *p1 = std::malloc(1024ul * 1024ul * 64ul);
DeliberateLeak(p1);
void *p2 = std::malloc(1024ul * 1024ul * 64ul);
DeliberateLeak(p2);
}
deallocate. For example, I'm writing a program at the moment that
loads in plugins, and the plugins stay loaded until the end of the
program.
The only problem with this is that when you run a debugging tool to
detect memory leaks, you get a load of false positives. I had a
program before that linked with libpango, and I spent ages trying to
find a memory leak, wondering if I was misusing the library or if the
library internally had a bug, and I went the whole hog and joined the
libpango mailing list and shared all my debugging output. In the end
it turned out to be a benign memory leak -- i.e. the allocation
happened once in the program and was intended to last the entire
program.
But what if we could do the following:
void *p = malloc( 1024ul * 1024ul * 64ul );
std::deliberate_leak(p);
Or:
long unsigned *p = new long unsigned[2048u];
std::deliberate_leak(p);
It could work something like this: https://godbolt.org/z/Gx6Pc6ne5
And then when you compile in Release Mode, you don't bother deallocating.
And here it is copy-pasted:
#include <cstdlib> // abort, atexit, free
#include <mutex> // lock_guard, mutex
#define PRINT_TO_SCREEN // ------- You can comment this line out -------
#ifdef PRINT_TO_SCREEN
# include <cstdint> // uintptr_t
# include <iomanip> // hex
# include <iostream> // cout, endl
#endif
void DeliberateLeak(void const volatile *const arg) noexcept
{
constexpr unsigned capacity = 64u;
static void const volatile *leaks[capacity] = {}; // all nullptr's
static std::mutex m;
std::lock_guard mylock(m);
if ( nullptr == leaks[0u] )
{
std::atexit(
[](void) noexcept -> void
{
for ( unsigned i = 0u; i < capacity; ++i )
{
if ( nullptr == leaks[i] ) return;
std::free( const_cast<void*>(leaks[i]) );
#ifdef PRINT_TO_SCREEN
std::cout << "Freeing deliberately leaked memory
at address 0x"
<< std::hex
<< reinterpret_cast<std::uintptr_t>(leaks[i])
<< std::endl;
#endif
}
});
}
static unsigned index = 0u;
if ( index >= capacity ) std::abort(); // no more free slots available
leaks[index++] = arg;
}
int main(void)
{
void *p1 = std::malloc(1024ul * 1024ul * 64ul);
DeliberateLeak(p1);
void *p2 = std::malloc(1024ul * 1024ul * 64ul);
DeliberateLeak(p2);
}
Received on 2024-10-11 11:12:06