Search code examples
csdl-2sdl-ttf

Memory leak with SDL2


So here's the problem, I have a function that allows you to display text in a renderer, I implemented it in a project, I later wanted to test this project against a possible memory leak and bingo!

After some time of research it came from this famous function to display text...

So I made a small example:

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
 
void renderText(SDL_Renderer* renderer, const char* text, int const size, const SDL_Rect text_rect, const SDL_Color text_color)
{
    #ifdef __linux__
      const char* font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf";
    #elif _WIN32
      const char* font_path = "C:\\Windows\\Fonts\\Arial.ttf";
    #endif
 
    TTF_Font* font = TTF_OpenFont(font_path, size);
 
    if (!font) { SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_ttf - %s", TTF_GetError()); return; }
 
    SDL_Surface* text_surface = TTF_RenderText_Blended(font, text, text_color);
    SDL_Texture* text_texture = SDL_CreateTextureFromSurface(renderer, text_surface);
    if (text_color.a!=255)      SDL_SetTextureAlphaMod(text_texture, text_color.a);
 
    SDL_RenderCopy(renderer, text_texture, NULL, &text_rect);
 
    SDL_DestroyTexture(text_texture);
    SDL_FreeSurface(text_surface);
    TTF_CloseFont(font);
}
 
int main(int argc, char** argv)
{
    //(void)argc; (void)argv;
 
    SDL_Init(SDL_INIT_VIDEO);
 
    TTF_Init();
 
    SDL_Window* win = SDL_CreateWindow("Example for memory leak with SDL2_ttf", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, 0);
 
    SDL_Renderer* ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
 
    SDL_Event event;
 
    SDL_bool running = SDL_TRUE;
 
    while (running)
    {
        while (SDL_PollEvent(&event))
        {
            running = event.type != SDL_QUIT;
        }
 
        renderText(ren, "Hi devs !", 22, (SDL_Rect){ (640-128)/2, (480-64)/2, 128, 64 }, (SDL_Color){ 255, 255, 255, 255 });
 
        SDL_RenderPresent(ren);
    }
 
    SDL_DestroyRenderer(ren);
    SDL_DestroyWindow(win);
    TTF_Quit();
    SDL_Quit();
 
  return 0;
 
}

And Valgrind tells me this:

==79266== LEAK SUMMARY:
==79266==    definitely lost: 504 bytes in 1 blocks
==79266==    indirectly lost: 43,152 bytes in 235 blocks
==79266==      possibly lost: 8,496 bytes in 44 blocks
==79266==    still reachable: 274,261 bytes in 3,422 blocks
==79266==         suppressed: 0 bytes in 0 blocks

Where could it come from ?

UPDATE 1: I tried initializing the font outside the main loop but the result is exactly the same except for the mention "indirectly lost" however there are always 504 bytes of "definitively lost"

EDIT: Test redone and the result of 'UPDATE 1' is the same as before, I must have closed the window too soon.

#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
 
void renderText(SDL_Renderer* renderer, const char* text, TTF_Font* font, const int size, const SDL_Rect text_rect, const SDL_Color text_color)
{
    SDL_Surface* text_surface = TTF_RenderText_Blended(font, text, text_color);
    SDL_Texture* text_texture = SDL_CreateTextureFromSurface(renderer, text_surface);

    if (text_color.a!=255)
        SDL_SetTextureAlphaMod(text_texture, text_color.a);
 
    SDL_RenderCopy(renderer, text_texture, NULL, &text_rect);
 
    SDL_DestroyTexture(text_texture);
    SDL_FreeSurface(text_surface);
}
 
int main(int argc, char** argv)
{
    /* Init SDL */
 
    SDL_Init(SDL_INIT_VIDEO);
 
    SDL_Window* win = SDL_CreateWindow("Example for memory leak with SDL2_ttf", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480, 0);
 
    SDL_Renderer* ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
 
    SDL_Event event;

    /* Init TTF */

    TTF_Init();

    #ifdef __linux__
      const char* font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf";
    #elif _WIN32
      const char* font_path = "C:\\Windows\\Fonts\\Arial.ttf";
    #endif

    TTF_Font* font = TTF_OpenFont(font_path, 22);

    if (!font)
    {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_ttf - %s", TTF_GetError());
        return -1;
    }

    /* Main loop */
 
    SDL_bool running = SDL_TRUE;
 
    while (running)
    {
        while (SDL_PollEvent(&event))
        {
            running = event.type != SDL_QUIT;
        }
 
        renderText(ren, "Hi devs !", font, 22, (SDL_Rect){ (640-128)/2, (480-64)/2, 128, 64 }, (SDL_Color){ 255, 255, 255, 255 });
 
        SDL_RenderPresent(ren);
    }
 
    TTF_CloseFont(font);

    SDL_DestroyRenderer(ren);
    SDL_DestroyWindow(win);

    TTF_Quit();
    SDL_Quit();
 
  return 0;
 
}
==18309== LEAK SUMMARY:
==18309==    definitely lost: 504 bytes in 1 blocks
==18309==    indirectly lost: 160 bytes in 1 blocks
==18309==      possibly lost: 65,728 bytes in 367 blocks
==18309==    still reachable: 274,261 bytes in 3,422 blocks
==18309==         suppressed: 0 bytes in 0 blocks

Second test with Valgrind, same as before:

==22622==    definitely lost: 504 bytes in 1 blocks
==22622==    indirectly lost: 43,152 bytes in 235 blocks
==22622==      possibly lost: 21,136 bytes in 123 blocks
==22622==    still reachable: 274,261 bytes in 3,422 blocks
==22622==         suppressed: 0 bytes in 0 blocks

UPDATE 2: I specify that the same example program without the 'renderText' function and its display produces no memory leak and that the number of bytes does not change regardless of the time of use or what I do there. I tried in another project which only displays a text and the result is the same as for this example.


Solution

  • The failure to free isn't in your code. Digging further with valgrind --leak-check=full, you find the call from your program that generates the allocation is:

    ==5815==    by 0x400C81: main (ttf-valgrind.c:24)
    

    Which corresponds to:

        SDL_Init(SDL_INIT_VIDEO);
    

    There is nothing related to libSDL2_ttf implicated in the failure to free. The problem arises in libSDL2 and its interaction with libX11. Nothing you can do. You properly clean up:

        TTF_CloseFont(font);
    
        SDL_DestroyRenderer(ren);
        SDL_DestroyWindow(win);
    
        TTF_Quit();
        SDL_Quit();
    

    While it's not great practice on libSDL2's part, there technically isn't anything wrong with allowing the release of resources to be handled on return from main() and program exit. This appears to be one of those cases.

    There are a number of libraries that do this, Gtk included. So you have to take a failure to free when using and third-party library with a grain of salt. Go ahead and chase it down, and satisfy yourself that what is happening is the library is relying on program exit to handle the free. You've done all that you can at that point, unless you want to write and submit a patch to SDL2 that handles it in one of the xxx_Quit() functions.