Memory Leak - SDL_CreateWindow
Hi, I am using SDL3 with Delphi. I started using SDL_SetMemoryFunctions so that all allocations flow through Delphi and can take advantage of built-in leak detection. Ok, so If I do something like this:
function SDLMallocFunc(size: NativeUInt): Pointer; cdecl;
begin
GetMem(Result, size);
end;
function SDLCallocFunc(count, size: NativeUInt): Pointer; cdecl;
var
totalSize: NativeUInt;
begin
totalSize := count * size;
GetMem(Result, totalSize);
FillChar(Result^, totalSize, 0);
end;
function SDLReallocFunc(ptr: Pointer; size: NativeUInt): Pointer; cdecl;
begin
ReallocMem(ptr, size);
Result := ptr;
end;
procedure SDLFreeFunc(ptr: Pointer); cdecl;
begin
FreeMem(ptr);
end;
var
win: PSDL_Window;
begin
ReportMemoryLeaksOnShutdown := True;
SDL_SetMemoryFunctions(@SDLMallocFunc, @SDLCallocFunc, @SDLReallocFunc, @SDLFreeFunc);
win := SDL_CreateWindow('test', 640, 480, 0);
if Assigned(win) then
begin
SDL_DestroyWindow(win);
end;
SDL_Quit;
end;
On exit, I will get the following:
Unexpected Memory Leak
An unexpected memory leak has occurred. The unexpected small block leaks are:
217 - 232 bytes: Unknown x 1
I've been working on my project today, doing other SDL related things and memory management through those functions has been working (I set break points and I can see each being called). When I got to adding SDL_CreateWindow, I noticed the leak. Looked at the SDL_CreateWindow/SDL_DestroyWindow C code and on the surface, all seems ok (C/C++ is not my main language however, soooo yea).
Any ideas?
WinPro 11 v22H2 (latest build) 64bit Delphi 11.3 Patch 1 Target: win64
Thanks.
So, there seem to be 15 allocations after win := SDL_CreateWindow('test', 640, 480, 0); and immediately calling SDL_DestroyWindow(win) there are 4 calls to release memory, thus 11 allocations seem to be left dangling? I get these numbers by inc/dec a counter as they pass through the custom mem manager.
Opening and closing multiple windows using SDL_CreateWindow/SDL_DestroyWindow can cause memory leaks. However, when using SDL_CreateWindowFrom with a window created using the Win32 API, no leaks were observed. It is possible that all memory allocations are being processed through SDL, including the custom handler, and some deallocations are either missing or not being called through the SDL API, resulting in potential leaks. In cases where deallocations are handled by the C/C++ runtime and not the custom implementation, there may not be any memory leaks on the C/C++ side, and thus they may go unnoticed and unreported.
There are some allocations that are only cleaned up in SDL_Quit(), have you checked after calling that?
There are some allocations that are only cleaned up in SDL_Quit(), have you checked after calling that?
The memory leak detection mechanism in Delphi reports memory leaks at the end of program execution, which would be after the SDL_Quit function call. To test this mechanism, I added code to track memory allocation in custom callbacks and released any remaining memory after SDL_Quit. This approach worked initially, but when SDL_CreateWindow/SDL_DestroyWindow were called multiple times, the problem became more severe. I then removed the tracking code and tried using SDL_CreateWindowFrom to create windows from a HWND created via CreateWindowEx. This resolved the issue.
There seems to be something going on that warrants an investigation. I use Delphi, so I'm not familiar with any leak detection for c/c++. But you could set up the custom callbacks and simply track the allocations to get an idea of the leaks on the C side?
Upon conducting a reevaluation of the existing issue, I can confirm that it persists. I have refined the test code to monitor memory allocations and deallocate any lingering resources. The system identifies two unresolved allocations: one consuming 88 bytes and the other 220 bytes.
var
mem: TDictionary<Pointer, NativeUInt>;
function SDLMallocFunc(size: NativeUInt): Pointer; cdecl;
begin
GetMem(Result, size);
mem.Add(Result, size);
end;
function SDLCallocFunc(count, size: NativeUInt): Pointer; cdecl;
var
totalSize: NativeUInt;
begin
totalSize := count * size;
GetMem(Result, totalSize);
FillChar(Result^, totalSize, 0);
mem.Add(Result, size);
end;
function SDLReallocFunc(ptr: Pointer; size: NativeUInt): Pointer; cdecl;
begin
mem.Remove(ptr);
ReallocMem(ptr, size);
Result := ptr;
mem.Add(ptr, size);
end;
procedure SDLFreeFunc(ptr: Pointer); cdecl;
begin
FreeMem(ptr);
mem.Remove(ptr);
end;
procedure initmem;
begin
mem := TDictionary<Pointer, NativeUInt>.Create;
end;
procedure donemem;
var
item: TPair<Pointer, NativeUint>;
begin
writeln('Dangling allocations: ', mem.Count);
for item in mem do
begin
FreeMem(item.Key);
WriteLn('Size: ', item.Value, ' bytes');
end;
mem.Free;
end;
procedure Test01;
var
win: PSDL_Window;
begin
ReportMemoryLeaksOnShutdown := True;
initmem;
SDL_SetMemoryFunctions(@SDLMallocFunc, @SDLCallocFunc, @SDLReallocFunc, @SDLFreeFunc);
SDL_Init(SDL_INIT_EVERYTHING);
win := SDL_CreateWindow('test1', 640, 480, 0);
SDL_Delay(1000);
SDL_DestroyWindow(win);
SDL_Quit;
donemem;
end;
Are you able to get a stack trace or stop in the debugger when you get allocations of those sizes?
Hi, sorry, just saw this message.
interestingly, today I just tried with the build I got from the repo on (12/15/2023), only get 1 dangling allocation. Just doing SDL_Init(SDL_INIT_EVERYTHING) and SDL_Quit(), leaves 88 bytes unallocated. There no longer seems to be any correlation with creating a window. Before, there would be leaks that would increase each time a window is created, this no longer seems to be the case. Which is fantastic. Just 88 bytes. It seems the 88 bytes were from a realloc operation as no other realloc was 88 bytes that I noticed during SDL_Init/SDL_Quit.
Great, can you get a call stack for those 88 bytes?
Great, can you get a call stack for those 88 bytes?
I'm using Delphi, 64bit SDL DLL. All I have access to is the call stack memory offsets leading to the custom realloc function, in the above image.
Ok, found out that whenever one of these flags are added, you will get the 88 byte leak:
SDL_INIT_JOYSTICK | SDL_INIT_GAMEPAD.
It looks like this is an SDL error buffer that's allocated from a WGI service thread. Unfortunately there isn't a good way to know when that thread is finished, so we can free the memory allocated there.
For future reference, this is the allocation callstack:
SDL3.dll!SDL_ClearError_REAL() Line 79 C
SDL3.dll!register_string_error_to_buffer(wchar_t * * error_buffer, const wchar_t * string_error) Line 356 C
SDL3.dll!register_global_error(const wchar_t * string_error) Line 382 C
SDL3.dll!PLATFORM_hid_init() Line 415 C
SDL3.dll!PLATFORM_hid_enumerate(unsigned short vendor_id, unsigned short product_id) Line 919 C
SDL3.dll!SDL_hid_enumerate_REAL(unsigned short vendor_id, unsigned short product_id) Line 1439 C
SDL3.dll!HIDAPI_UpdateDeviceList() Line 1103 C
SDL3.dll!HIDAPI_IsDevicePresent(unsigned short vendor_id, unsigned short product_id, unsigned short version, const char * name) Line 1254 C
SDL3.dll!IEventHandler_CRawGameControllerVtbl_InvokeAdded(__FIEventHandler_1_Windows__CGaming__CInput__CRawGameController * This, IInspectable * sender, __x_ABI_CWindows_CGaming_CInput_CIRawGameController * e) Line 423 C
Windows.Gaming.Input.dll!00007ffc243c4260() Unknown
Windows.Gaming.Input.dll!00007ffc243c4a82() Unknown
Windows.Gaming.Input.dll!00007ffc243c4d11() Unknown
Windows.Gaming.Input.dll!00007ffc2436b215() Unknown
SHCore.dll!WorkThreadManager::CThread::ThreadProc(void) Unknown
SHCore.dll!WorkThreadManager::CThread::s_ExecuteThreadProc(void *) Unknown
SHCore.dll!<lambda_9844335fc14345151eefcc3593dd6895>::<lambda_invoker_cdecl>(void *) Unknown
kernel32.dll!BaseThreadInitThunk() Unknown
ntdll.dll!RtlUserThreadStart() Unknown
Ok, understand. I d/l and recompiled with this fix, just so you know... those changes, results in 120 bytes leaking. It looks like (so far) it's just this area, so I will just let it be. My custom memory mapping cleans up any dangling memory, but it was bothering me to see it leaking. In fact, since it's a thread issue, maybe as a hack, you can do the same as I am doing, clean it up after SDL_Quit, plus have the added benefit of testing for future leaks. Or maybe it's not a hack and a reliable way to handle it in a situation like this?
I'm not sure how those changes would result in 120 bytes leaking. We're doing fewer allocations, not more... ?
I'm not sure how those changes would result in 120 bytes leaking. We're doing fewer allocations, not more... ?
Yea, that is what I had assumed too. I just d/l the repo, rebuilt the DLL and when I run my same code above, it now shows 120 bytes on shutdown. I dunno. If you set up and track the allocations like I am doing, you can see what I'm seeing.
if you run this code, you can see the leak.
#include <iostream>
#include <SDL3/SDL.h>
#define DEBUG_CRT
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>
static inline void DebugCrtInit(long break_alloc)
{
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_WNDW);
_CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG | _CRTDBG_MODE_WNDW);
if (break_alloc != 0)
_CrtSetBreakAlloc(break_alloc);
}
static inline void DebugCrtDumpLeaks()
{
_CrtDumpMemoryLeaks();
}
int main()
{
DebugCrtInit(0);
int flag = SDL_INIT_TIMER | SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMEPAD | SDL_INIT_EVENTS | SDL_INIT_SENSOR | SDL_INIT_CAMERA;
if(SDL_Init(flag) != 0)
{
std::cout << "Could not init SDL!\n";
return 1;
}
std::cout << "Succesfully init SDL!\n";
SDL_Quit();
//malloc(42);
DebugCrtDumpLeaks();
return 0;
}
You can combine flags up until SDL_INIT_HAPTIC, then the leak shows up.