SDL2:一段时间后 TTF_RenderText 出现分段错误?

SDL2: getting segmentation fault with TTF_RenderText after some time?

嗯,我不确定这是为了代码审查还是为了这个网站,但它有问题,所以我想我来对地方了。我正在尝试使用 C 和 SDL 创建游戏。这是代码(下面的解释):

//Includes
#include <stdio.h>
#include <stdlib.h>
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_ttf.h>
#include <SDL2_gfxPrimitives.h>


//Create some global variables(is there some other way to do that?)
SDL_Window *screen;
SDL_Renderer *renderer;
SDL_Texture *tardis;
SDL_Surface *tardis_surface;
SDL_Texture *gallifrey;
SDL_Surface *gallifrey_surface;
SDL_Surface *pillar_surface;
SDL_Rect tardis_dest;


//Some variables I want to keep throughout the game
float speed = 0;
int pillars = 0;
int pause = 0;


//Those two could change in the future
int WIDTH = 640;
int HEIGHT = 480;


//Define a struct to express the pillars in a better way
typedef struct{
    SDL_Rect pillar_up;
    SDL_Rect pillar_down;
    SDL_Texture *pillar_texture;
}pillar;


//OK, that might be silly, but I create a list of pointers so
//that I can keep track of them afterwards. Is that bad?
pillar *list_dest[4];


//Generate a pillar
void create_pillar(int pillars_no, int xpos)
{
    //Generate random height
    int height = rand() % 200;
    if(height < 50)
        height += 50;
    pillar pillar_dest;

    //Set the coordinates for the upper pillar
    pillar_dest.pillar_up.x = xpos;
    pillar_dest.pillar_up.y = 0;
    pillar_dest.pillar_up.w = 70;
    pillar_dest.pillar_up.h = HEIGHT - (height + 180);

    //Set the coordinates for the pillar on the ground
    pillar_dest.pillar_down.x = xpos;
    pillar_dest.pillar_down.y = HEIGHT - height;
    pillar_dest.pillar_down.w = 70;
    pillar_dest.pillar_down.h = 200;

    //Set the pillar texture
    pillar_dest.pillar_texture = SDL_CreateTextureFromSurface(
                                renderer, pillar_surface);

    //Allocate space for the pillar and save the address
    //in the list_dest
    pillar *pillar_destp = malloc(sizeof(pillar_dest));
    *pillar_destp = pillar_dest;
    list_dest[pillars_no % 3] = pillar_destp;
}


//Check if the Tardis collides with a pillar
int check_collision(SDL_Rect A, SDL_Rect B)
{
    //The sides of the rectangles
    int leftA, leftB;
    int rightA, rightB;
    int topA, topB;
    int bottomA, bottomB;

    //Calculate the sides of rect A
    leftA = A.x;
    rightA = A.x + A.w;
    topA = A.y;
    bottomA = A.y + A.h;

    //Calculate the sides of rect B
    leftB = B.x;
    rightB = B.x + B.w;
    topB = B.y;
    bottomB = B.y + B.h;

    //If any of the sides from A are outside of B
    if(bottomA < topB)
    {
        return 0;
    }

    if(topA > bottomB)
    {
        return 0;
    }
    if(rightA < leftB)
    {
        return 0;
    }
    if(leftA > rightB)
    {
        return 0;
    }
    //Else
    return 1;
}


//Render the pillars(shitty function name, whatever...)
void draw_ground()
{
    //I always create 3 pillars, no more
    if(pillars < 3)
    {
        create_pillar(pillars, WIDTH + pillars * 240);
        pillars += 1;
    }
    else
    {
        int i;
        for(i = 0; i < 3; i++)
        {
            //Free the previous pillars
            pillar *current_pillar;
            current_pillar = list_dest[i];
            if(current_pillar->pillar_up.x < -70)
            {
                free(current_pillar->pillar_texture);
                free(current_pillar);
                create_pillar(pillars, WIDTH);
                pillars += 1;
            }
            else
            {
                //Check if the Tardis has collided with a pillar and
                //display a "game over" message.
                if(check_collision(current_pillar->pillar_up, tardis_dest) ||
                    check_collision(current_pillar->pillar_down, tardis_dest))
                {
                    SDL_Rect target;
                    target.x = (WIDTH / 2) - 100;
                    target.y = (HEIGHT / 2) - 50;
                    target.w = 200;
                    target.h = 100;
                    TTF_Font *font;
                    font = TTF_OpenFont("DALEK.ttf", 40);
                    SDL_Surface *text;
                    SDL_Color text_color = {255, 255, 0};
                    text = TTF_RenderText_Solid(font,
                    "GAME OVER",
                    text_color);
                    SDL_Texture *game_over;
                    game_over = SDL_CreateTextureFromSurface(renderer, text);
                    SDL_RenderCopy(renderer, game_over, NULL, &target);
                    pause = 1;
                    int j;
                    for(j = 0; j < 3; j++)
                    {
                        free(list_dest[j]);
                    }
                    pillars = 0;
                    break;

                }
                //Render the pillars
                double angle = 180.0;
                current_pillar->pillar_up.x -= 10;
                current_pillar->pillar_down.x -= 10;
                SDL_RenderCopyEx(renderer, current_pillar->pillar_texture,
                    NULL, ¤t_pillar->pillar_up, angle = 0.0,
                    NULL, SDL_FLIP_VERTICAL);
                SDL_RenderCopy(renderer, current_pillar->pillar_texture,
                    NULL, ¤t_pillar->pillar_down);
            }
        }
    }
}


//Render the pillar counter
void draw_counter()
{
    SDL_Rect counter;
    counter.x = WIDTH - 50;
    counter.y = 10;
    counter.w = 40;
    counter.h = 50;

    TTF_Font *font;
    font = TTF_OpenFont("DALEK.ttf", 40);

    SDL_Surface *count;
    SDL_Color text_color = {0, 255, 255};

    char count_text[3];
    if(pillars >= 3)
        sprintf(count_text, "%d", pillars - 3);
    else
        sprintf(count_text, "%d", pillars);

    count = TTF_RenderText_Solid(font,
    count_text,
    text_color);

    SDL_Texture *pillar_counter;
    pillar_counter = SDL_CreateTextureFromSurface(renderer, count);

    SDL_RenderCopy(renderer, pillar_counter, NULL, &counter);
    //This is the last function of the game loop, so I suppose it's a good
    //place for the SDL_RenderPresent
    SDL_RenderPresent(renderer);
}


//Load the images used in the game
void load_images()
{
    tardis_surface = IMG_Load("files/tardis.bmp");
    gallifrey_surface = IMG_Load("files/gallifrey.bmp");
    pillar_surface = IMG_Load("files/pipe.bmp");
}


//Create the background and the Tardis in it's initial place
void create_sprites()
{
    //Create area for the TARDIS
    tardis_dest.x=50;
    tardis_dest.y=100;
    tardis_dest.w=50;
    tardis_dest.h=80;

    //Color key the TARDIS and create texture
    Uint32 colorkey = SDL_MapRGB(tardis_surface->format,0,0,0);
    //SDL_DisplayFormat(tardis_surface);
    SDL_SetColorKey(tardis_surface, SDL_SRCCOLORKEY, colorkey); // That doesn't work!!!!!
    //Why? Any ideas?

    tardis = SDL_CreateTextureFromSurface(renderer, tardis_surface);

    //Create texture for the Gallifreyan background
    gallifrey = SDL_CreateTextureFromSurface(renderer, gallifrey_surface);

    //Clear the renderer
    SDL_RenderClear(renderer);

    //Add textures to renderer
    SDL_RenderCopy(renderer, gallifrey, NULL, NULL);
    SDL_RenderCopy(renderer, tardis, NULL, &tardis_dest);

    //Update renderer
    //SDL_RenderPresent(renderer);
}


//Update the Tardis' position
void update_sprites(float time)
{
    speed += time * 28;
    if(tardis_dest.y > 460)
    {
        tardis_dest.y = 460;
        speed = 0;
    }
    else if(tardis_dest.y < 0)
    {
        tardis_dest.y = 0;
        speed = 0;
    }
    else
    {
        tardis_dest.y += speed;
        SDL_RenderCopy(renderer, gallifrey, NULL, NULL);
        SDL_RenderCopy(renderer, tardis, NULL, &tardis_dest);
        //SDL_RenderPresent(renderer);
    }
}


//Change the speed when clicked
void ignite()
{
    if(speed > 0)
        speed = -10;
    else
        speed -= 10;
}


//The main function
int main(int argc, char *argv[])
{

    SDL_Event ev;
    int active = 1;

    if(SDL_Init(SDL_INIT_EVERYTHING) != 0)
        fprintf(stderr, "Could not initialize SDL. SDL Error: %s\n", SDL_GetError());
    else
        printf("SDL initialized.\n");

    if(TTF_Init() == 0)
        fprintf(stderr, "TTF initialized.\n");
    else
        fprintf(stderr, "Could not initialize TTF.\n");

    //Open main window
    screen = SDL_CreateWindow
    (
        "First Game", SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
        WIDTH,
        HEIGHT,
        SDL_WINDOW_SHOWN
    );

    if(!screen)
        fprintf(stderr, "Could not set video mode: SDL Error: %s\n", SDL_GetError());

    renderer = SDL_CreateRenderer(screen, 0, SDL_RENDERER_ACCELERATED);

    //Draw the environment
    load_images();
    create_sprites();

    //Set time
    Uint32 current_time, old_time;
    float time_passed;
    current_time = SDL_GetTicks();

    while(active)
    {
        //Update time
        old_time = current_time;
        current_time = SDL_GetTicks();
        time_passed = (current_time - old_time) / 1000.0f;

        //Handle Events
        while(SDL_PollEvent(&ev))
        {

            if(ev.type == SDL_QUIT)
                active = 0;
            if(ev.type == SDL_MOUSEBUTTONDOWN)
            {
                if(pause)
                {
                    pause = 0;
                }
                else
                    ignite();
            }
        }
        if(pause)
            continue;
        update_sprites(time_passed);
        draw_ground();
        draw_counter();
        SDL_Delay(40);
    }

    //Exit
    SDL_FreeSurface(tardis_surface);
    SDL_FreeSurface(gallifrey_surface);
    SDL_Quit();
    printf("SDL terminated. Have a nice day...\n");
    return 0;
}

读起来不错吧?它可能有很多缺陷,因为这是我第一次尝试游戏编程,但我的主要问题是玩了一段时间后我遇到了分段错误。使用 gdb,我发现是以下命令导致了问题:

count = TTF_RenderText_Solid(font,
    count_text,
    text_color);

位于 draw_counter() 函数的第 214 行(中间某处)。问题是:为什么?我想这与我正在做的内存分配有关(我还在学习 C,这是一个非常棘手的部分,来自 Python)。如果有人可以提供一些有用的信息?谢谢 顺便说一句,请不要用通常的 "don't use C, learn C++ or Java" 甚至 "just use Unity" 来回应,我想学习 C,并可能在此过程中学习一些游戏编程。

一定是font = TTF_OpenFont("DALEK.ttf", 40);的不断调用导致一段时间后失败

SDL 为每个打开的字体分配内存,有时你会 运行 出来。 (也许文件保持打开状态并且您的系统 运行 没有文件句柄。)您需要 关闭 字体句柄:添加

TTF_CloseFont( font );

在打开它的例程结束时。或者,只打开字体一次(在你的main)例程中并将它们的句柄保存在全局变量中。

顺便说一句,我也注意到你对你的表面很自由。完成一个后,使用类似的函数 SDL_FreeSurface,再次防止只使用一次资源但仍然分配资源。 (这是我习惯的 SDL 1.0。查看 SDL 2 文档。)