Seg fault on TTF_CloseFont?

I have the following font object and when I declare some empty font objects in another object I get this seg fault. Do you see what I may be doing wrong with this and why it seg faults only when I create fonts that are unused? Other fonts that are declared and rendered don’t have the problem. The problem only happens when I add a Font object as part of a member that doesn’t get used at all. I would expect to be able to create an empty font object.

#ifndef FONT_H
#define FONT_H
#include <SDL2/SDL_ttf.h>

#include "Renderable.h"

namespace ley {

class Font : public Renderable {

private:
    std::string textMessage;
    SDL_Texture* Message;
    SDL_Rect Message_rect;
    TTF_Font* Classic;
    SDL_Color color;

protected:

public:
    Font();
    Font(int, int, int, int);
    ~Font();
    void init();
    Font& operator=(const ley::Font& other); //copy assignment
    Font operator()(Font& other); //copy constructor
    void updateMessage(std::string s); // TODO this should be setMessage instead of updateMessage.
    std::string getMessage();
    std::string* getMessagePtr();
    SDL_Texture* getTexturePtr();
    void preRender(SDL_Renderer* r);
    void render(SDL_Renderer * r, bool d);
    TTF_Font* getTTFFont();
    void setPos(SDL_Point p);
    std::pair<int, int> size();
    void setColor(SDL_Color c);
};

}
#endif
#include "Font.h"

ley::Font::Font()
: 
Message(nullptr), 
Classic(nullptr) {
    Message_rect.x = 0;
    Message_rect.y = 0;
    Message_rect.h = 0;
    Message_rect.w = 0;
    init();
}

ley::Font::Font(int x, int y, int w, int h)
:
Message(nullptr),
Classic(nullptr) {
    Message_rect.x = x;
    Message_rect.y = y;
    Message_rect.w = w;
    Message_rect.h = h;
    init();
}
void ley::Font::init() {

    if (TTF_Init() < 0) {
        SDL_Log("TTF_Init failed");
    }

    SDL_Log("Open font %s", textMessage.c_str());
    Classic = TTF_OpenFont("assets/fonts/MartianMono-Regular.ttf", 24);    
    if(!Classic) {
        printf("TTF_OpenFont: %s\n", TTF_GetError());
    }

    color = {255, 255, 255, 255};
}

ley::Font::~Font() {

    if(Message) {
        SDL_DestroyTexture(Message);
    }
    
    SDL_Log("Close Font %s , Inits, %d", textMessage.c_str(), TTF_WasInit());


    if(Classic) {
        TTF_CloseFont(Classic);
        Classic = nullptr;
    }

    TTF_Quit();
}
//copy assignment operator
/*
ley::Font& ley::Font::operator=(ley::Font other) {
    
    Message = nullptr;

    Message_rect.x = other.Message_rect.x;
    Message_rect.y = other.Message_rect.y;
    Message_rect.w = other.Message_rect.w;
    Message_rect.h = other.Message_rect.h;

    updateMessage("");

    return *this;
}
*/

ley::Font& ley::Font::operator=(const ley::Font& other) {
    if (this == &other) return *this; // Handle self-assignment

    // Free existing resources
    if (Message) {
        SDL_DestroyTexture(Message);
        Message = nullptr;
    }
    if (Classic) {
        TTF_CloseFont(Classic);
        Classic = nullptr;
    }

    // Copy the state
    Message_rect = other.Message_rect;
    color = other.color;
    textMessage = other.textMessage;

    // Reinitialize resources
    Classic = TTF_OpenFont("assets/fonts/MartianMono-Regular.ttf", 24);
    if (!Classic) {
        printf("TTF_OpenFont failed: %s\n", TTF_GetError());
    }
    return *this;
}


ley::Font ley::Font::operator()(ley::Font& other) {

    Message = nullptr;

    if (Classic) {
        TTF_CloseFont(Classic);
    }
    
    SDL_Log("Open font");

    Classic = TTF_OpenFont("assets/fonts/Montserrat-Regular.ttf", 24);
    if(!Classic) {
        printf("TTF_OpenFont: %s\n", TTF_GetError());
    }

    Message_rect.x = other.Message_rect.x;  //controls the rect's x coordinate
    Message_rect.y = other.Message_rect.y; // controls the rect's y coordinte
    Message_rect.w = other.Message_rect.w; // controls the width of the rect
    Message_rect.h = other.Message_rect.h; // controls the height of the rect

    return *this;
}

void ley::Font::setColor(SDL_Color c) {
    color = c;
}

void ley::Font::updateMessage(std::string s) {
    //Only update the texture if the message has changed.
    if(s != textMessage) {
        textMessage = s;

        //then invalidate the texture
        if(Message) {
            SDL_DestroyTexture(Message);
            Message = nullptr;
        }
    }
}

std::string ley::Font::getMessage() {
    return textMessage;
}

std::string* ley::Font::getMessagePtr() {
    return &textMessage;
}

SDL_Texture* ley::Font::getTexturePtr() {
    return Message;
}

void ley::Font::preRender(SDL_Renderer* r)
{
    if(!Message) {
        SDL_Surface* surfaceMessage;
        surfaceMessage = TTF_RenderUTF8_Solid(Classic, textMessage.c_str(), color);
        Message = SDL_CreateTextureFromSurface(r, surfaceMessage);
        SDL_FreeSurface(surfaceMessage);
        surfaceMessage = nullptr;
    }
}

void ley::Font::render(SDL_Renderer * r, bool d) {

    preRender(r);
    
    // TODO this query texture should probably go in the setpos method and be called only when the position changes.
    int w;
    int h;
    SDL_QueryTexture(Message,
                     NULL, NULL,
                     &w, &h);
    Message_rect.h = h;
    Message_rect.w = w;

    SDL_RenderCopy(r, Message, NULL, &Message_rect);
}

TTF_Font* ley::Font::getTTFFont() {
    return Classic;
}

void ley::Font::setPos(SDL_Point p) {
    Message_rect.x = p.x;
    Message_rect.y = p.y;
}

std::pair<int, int> ley::Font::size() {
    int width = -1;
    int height = -1;

    if(Classic) {
        TTF_SizeText(Classic, textMessage.c_str(), &width, &height);
    }

    return std::make_pair(width, height);
}

It appears that I only have the problem when I create an empty member in my UIElement class, but when I create it in my Video class it works fine.

It appears to be a double free. I’m not sure how this happens though…

(gdb) backtrace
#0  __pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6, no_tid=no_tid@entry=0) at pthread_kill.c:44
#1  0x00007ffff7799163 in __pthread_kill_internal (threadid=<optimized out>, signo=6) at pthread_kill.c:78
#2  0x00007ffff773ffde in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#3  0x00007ffff7727942 in __GI_abort () at abort.c:79
#4  0x00007ffff77287a7 in __libc_message_impl (fmt=fmt@entry=0x7ffff78db3cd "%s\n") at ../sysdeps/posix/libc_fatal.c:132
#5  0x00007ffff77a3265 in malloc_printerr (str=str@entry=0x7ffff78de798 "double free or corruption (fasttop)") at malloc.c:5772
#6  0x00007ffff77a563a in _int_free (av=0x7ffff7910ac0 <main_arena>, p=p@entry=0x555555826c20, have_lock=have_lock@entry=0) at malloc.c:4607
#7  0x00007ffff77a7fce in __GI___libc_free (mem=0x555555826c30) at malloc.c:3398
#8  0x00007ffff7e3e033 in SDL_free_REAL (ptr=<optimized out>) at /usr/src/debug/SDL2-2.30.11-1.fc41.x86_64/src/stdlib/SDL_malloc.c:5339
#9  0x00007ffff7d99717 in TTF_CloseFont (font=0x555556e149d0) at /usr/src/debug/SDL2_ttf-2.22.0-3.fc41.x86_64/SDL_ttf.c:2754
#10 TTF_CloseFont (font=0x555556e149d0) at /usr/src/debug/SDL2_ttf-2.22.0-3.fc41.x86_64/SDL_ttf.c:2743
#11 0x000055555557c25b in ley::Font::~Font (this=0x7fffffffcb70, __in_chrg=<optimized out>) at Font.cpp:57
#12 0x000055555557d90e in ley::UIElement::~UIElement (this=0x7fffffffcb10, __in_chrg=<optimized out>) at UIElement.cpp:53
#13 0x000055555557e4e2 in ley::UIMenu::push (this=0x555555aed8b8, label="highscore", src=..., dest=..., b="menufonts-base", t="menufonts-white", th="menufonts-hot") at UIMenu.cpp:42
#14 0x000055555558ac72 in ley::MenuState::MenuState (this=0x555555aed640, v=0x7fffffffd570, gm=0x7fffffffd230) at src/State/MenuState.cpp:24
#15 0x000055555557731a in ley::GameController::processStates (this=0x7fffffffd140, inCommand=ley::Command::none) at GameController.cpp:143
#16 0x0000555555576fcd in ley::GameController::runGameLoop (this=0x7fffffffd140) at GameController.cpp:77
#17 0x000055555555dd48 in main (argv=1, args=0x7fffffffdd98) at sdl2-blocks.cpp:39

This is not a copy constructor.

Hi Peter87,

Thanks so much for the insights. I noticed this too last night and started cleaning it up. I’m learning more about the rule of three/five and how important they actually are. I’m also learning more about how and when the different constructors are called as well as the assignment operator, but still need to learn more about that. I cleaned up the Font object and added a proper copy constructor.

I think what may have been happening is that the compiler was creating the copy constructor and likely trying to clean up raw/uninitialized data and that’s why I was seeing a seg fault.

I sincerely appreciate the support,

Electrosys