You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C语言SDL2俄罗斯方块项目内存泄漏致文本显示异常排查

SDL俄罗斯方块运行数分钟后文本消失的内存问题排查

问题现象

  • 基于SDL2/SDL_ttf实现的俄罗斯方块程序,初始运行状态正常,运行数分钟后自动停止显示文本内容
  • 通过任务管理器观察到内存占用持续上涨,初步判定为内存管理问题,但逐行排查源码未定位到错误:自行检查时认为所有堆对象均已释放、所有纹理均调用了销毁接口

附:项目源码

main.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <stdbool.h>

#define SDL_MAIN_HANDLED
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>

SDL_Window * window;
SDL_Renderer * renderer;
SDL_Surface * text;
TTF_Font* font;

bool running = true;

#define ScreenWidth 60
#define ScreenHeight 30
#define CharWidth 14
#define CharHeight 24

char screen[ScreenHeight][ScreenWidth + 1];

SDL_Color color = {192, 192, 192};
SDL_Rect rect;

int score = 0;

char shapes[7][17];

typedef struct Block {
    int type;
    char shape[17];
    int x, y;
    int width, height;
} Block;

Block * newBlock(int type) {
    Block * block = malloc(sizeof(Block));
    return block;
}

void CheckEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_QUIT) {
            running = false;
        } else if (event.type == SDL_KEYDOWN) {
            switch (event.key.keysym.sym) {
                case SDLK_w:
                    ;
                    break;

                case SDLK_a:
                    ;
                    break;

                case SDLK_d:
                    ;
                    break;

                case SDLK_s:
                    ;
                    break;

                case SDLK_SPACE:
                    ;
                    break;

                default:
                    break;
            }
        }
    }

    free(&event);
}

void UpdateBoard() {
    for (int i = 0; i < ScreenHeight; ++i) {
        for (int j = 0; j < ScreenWidth; ++j) {
            screen[i][j] = ' ';
        }
    }

    for (int i = 0; i != 20; ++i) {
        screen[i + 2][1] = '#';
        screen[i + 2][1 + 10 + 1] = '#';
    }

    for (int i = 0; i != 10 + 2; ++i) {
        screen[22][i + 1] = '#';
    }

    sprintf(&(screen[0][1]), "Score: %d", score);

    for (int i = 0; i != ScreenHeight; i++) {
        for (int j = 0; j != ScreenWidth; j++) {
            if (screen[i][j] == '\0') {
                screen[i][j] = ' ';
            }
        }
    }
}

void UpdateScreen() {
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
    SDL_RenderClear(renderer);

    for (int i = 0; i < ScreenHeight; ++i) {
        text = TTF_RenderText_Solid(font, screen[i], color);
        SDL_Texture * texture;
        texture = SDL_CreateTextureFromSurface(renderer, text);
        rect.x = 0;
        rect.y = i * CharHeight;
        rect.w = strlen(screen[i]) * CharWidth;
        rect.h = CharHeight;
        SDL_RenderCopy(renderer, texture, NULL, &rect);
        free(texture);
        SDL_DestroyTexture(texture);
    }

    SDL_RenderPresent(renderer);
}

void Init() {
    SDL_Init(SDL_INIT_EVERYTHING);
    TTF_Init();

    window = SDL_CreateWindow("Tetris", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 
            ScreenWidth * CharWidth, ScreenHeight * CharHeight, 0);
    renderer = SDL_CreateRenderer(window, -1, 0);
    font = TTF_OpenFont("s8514oem.fon", 0);

    sprintf(shapes[0], "OOOO............");
    sprintf(shapes[1], "OO...OO.........");
    sprintf(shapes[2], ".OO.OO..........");
    sprintf(shapes[3], "O...OOO.........");
    sprintf(shapes[4], "..O.OOO.........");
    sprintf(shapes[5], "OO..OO..........");
    sprintf(shapes[6], "OOO..O..........");
}

int main(int argc, char ** argv) {

    Init();

    while (running) {
        CheckEvents();
        UpdateBoard();
        UpdateScreen();
        SDL_Delay(1000/16);
    }

    TTF_Quit();
    SDL_Quit();
    return 0;
}

Makefile配置

tetris:
    gcc -std=c99 -Wall -Isrc/include -Lsrc/lib -o Tetris main.c -lSDL2main -lSDL2 -lSDL2_ttf

故障根因

代码里存在5处明确的内存管理错误,直接导致内存泄漏、堆结构破坏,最终引发渲染失败:

  1. 非法释放栈内存:CheckEvents函数中的SDL_Event event是栈上分配的局部变量,函数返回时会自动回收内存,末尾的free(&event);属于对非堆内存执行释放操作,会直接破坏程序堆结构,触发未定义行为。
  2. TTF渲染Surface泄漏(核心泄漏点):UpdateScreen循环中每次调用TTF_RenderText_Solid都会在堆上分配新的SDL_Surface对象,使用完成后必须调用SDL_FreeSurface释放。现有代码既没有释放该Surface,还每次循环用新Surface的地址覆盖全局text指针,旧Surface的内存完全丢失无法回收——程序按60FPS运行时,每秒会泄漏1800个Surface的内存,运行数分钟后就会耗尽可用内存,导致TTF文本渲染失败。
  3. 纹理非法重复释放:SDL_CreateTextureFromSurface返回的纹理对象,必须且只能通过SDL_DestroyTexture释放。现有代码先对纹理指针执行free(texture),再调用SDL_DestroyTexture(texture),既非法释放了SDL内部管理的纹理内存,又触发了双重释放问题,同样会破坏堆结构。
  4. 堆对象未初始化:newBlock函数中通过malloc分配的Block结构体没有对任何成员做初始化就直接返回,后续使用该对象时会读取到随机垃圾值,极易触发内存访问错误。
  5. 退出时全局资源未释放:程序退出前没有显式销毁创建的窗口、渲染器、字体对象,虽然进程退出后系统会回收内存,但属于不符合SDL规范的资源泄漏写法。

修复方案

  1. 删除CheckEvents函数末尾的free(&event);语句,栈变量不需要手动释放。
  2. 修改UpdateScreen中的渲染循环,使用完Surface和纹理后按正确顺序释放:
    for (int i = 0; i < ScreenHeight; ++i) {
        text = TTF_RenderText_Solid(font, screen[i], color);
        SDL_Texture * texture = SDL_CreateTextureFromSurface(renderer, text);
        rect.x = 0;
        rect.y = i * CharHeight;
        rect.w = strlen(screen[i]) * CharWidth;
        rect.h = CharHeight;
        SDL_RenderCopy(renderer, texture, NULL, &rect);
        SDL_DestroyTexture(texture);
        SDL_FreeSurface(text); // 释放TTF生成的文本Surface
    }
    
    注意:必须先销毁纹理再释放Surface,因为纹理创建时会引用Surface的像素数据,提前释放Surface会导致纹理数据异常。
  3. 补全newBlock的初始化逻辑,避免使用未初始化的内存:
    Block * newBlock(int type) {
        Block * block = malloc(sizeof(Block));
        if (!block) return NULL;
        memset(block, 0, sizeof(Block));
        block->type = type;
        // 后续补充形状、坐标、宽高的赋值逻辑
        return block;
    }
    
  4. 在主循环退出后、SDL卸载前,补全全局资源的释放逻辑:
    // 主循环结束后
    TTF_CloseFont(font);
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    TTF_Quit();
    SDL_Quit();
    

修复后内存占用会稳定在固定值,不会随运行时间持续上涨,文本消失的问题会完全解决。


内容的提问来源于stack exchange,提问作者Sodium Hypochlorite

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 12:36:20