2

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.

Bigfoot71
  • 63
  • 1
  • 6
  • Do the numbers increase the longer you run your program for? – AKX Aug 30 '22 at 18:11
  • 1
    I think opening a font and closing it are bad inside frame loop, open before loop then close it after the loop. – Mehran Aug 30 '22 at 18:20
  • 1
    Or open the font when the program starts and close it when it ends. You can then check if it succeeds to open it **one** time instead of every time you need to render text. – Ted Lyngmo Aug 30 '22 at 18:23
  • @AKX No, the result is constant, regardless of the execution time – Bigfoot71 Aug 30 '22 at 19:09
  • @Mehran There is still a small improvement but there are still 504 bytes "definitely lost" – Bigfoot71 Aug 30 '22 at 19:26
  • 1
    Just out of curiosity: How much does this program leak: `#include ` `int main(void) { SDL_Init(SDL_INIT_VIDEO); SDL_Quit(); }` ? It also leaks, right? It's built into `SDL` to leak. See if you can find valgrind suppression files for SDL. – Ted Lyngmo Aug 30 '22 at 19:49
  • Might want to check [this](https://stackoverflow.com/questions/1997171/why-does-valgrind-say-basic-sdl-program-is-leaking-memory) – aulven Aug 30 '22 at 19:57
  • @TedLyngmo Nothing: ```definitely lost: 0 bytes in 0 blocks``` ```indirectly lost: 0 bytes in 0 blocks``` – Bigfoot71 Aug 30 '22 at 20:02
  • @aulven So I have to tell myself that it's no big deal and there's nothing I can do about it ? (apart from rewriting SDL... lol) – Bigfoot71 Aug 30 '22 at 20:06
  • I don't know if you code actually leaks itself though. Do as @TedLyngmo suggested, surpress the SDL related outputs and check if anything is your own fault. Other than that, while I have not got deep into the subject, the SDL related leaks are said to be quite small and one time only, so, not much to worry about. – aulven Aug 30 '22 at 20:10
  • @Bigfoot71 Odd... Running valgrind on my little snippet on my machine says _"definitely lost: 8,100 bytes"_. If I only do `SDL_Init(0);` it only leaks 1,560 bytes. – Ted Lyngmo Aug 30 '22 at 20:24
  • @TedLyngmo I confirm it's very weird, wouldn't we have the same version maybe? In case I have 2.0.20 – Bigfoot71 Aug 30 '22 at 20:28
  • Could be. I have 2.24.0-1.fc36 – Ted Lyngmo Aug 30 '22 at 20:29
  • 1
    @Bigfoot71 If the result is constant, then it's not the printing function that's leaking (because you'd see the numbers increasing if it was). – AKX Aug 31 '22 at 05:03

1 Answers1

3

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.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85