基于SDL2的C语言3D立方体程序内存泄漏及内存突增问题排查
SDL2 3D立方体程序内存泄漏与内存暴涨问题修复
核心问题分析
程序内存急剧暴涨至GB级的根本原因是透视投影计算出现超大坐标:当立方体靠近屏幕(相机平面)时,distance = fl + input.z + centerZ的绝对值趋近于0,导致perspective = fl / distance变成极大值,最终计算出的SDL_Point坐标会是远超屏幕范围的超大整数。SDL的SDL_RenderDrawLine在处理这种超大坐标时,会尝试绘制横跨巨大区域的线段,内部可能分配大量临时资源或陷入低效计算,直接导致内存占用飙升。
此外代码中还有几处逻辑bug会加剧问题或引发其他内存错误。
具体修复方案
1. 修复透视投影的超大坐标问题
修改projectPoint函数,添加距离阈值判断,当物体处于相机平面内或后方时标记为不可见,避免生成无效超大坐标:
// project 3D point onto the 2D screen SDL_Point projectPoint(struct Vector3 input, bool* isVisible) { *isVisible = true; float perspective = 1.0f; float distance = fl + input.z + centerZ; // 限制最小距离,避免透视系数过大 const float minDistance = 10.0f; if(fabs(distance) < minDistance) { // 点在相机后方或过近,标记为不可见 *isVisible = false; SDL_Point p = { -1, -1 }; return p; } perspective = fl / distance; SDL_Point p; p.x = input.x * perspective + centerX; p.y = input.y * perspective + centerY; return p; }
同时修改drawCube函数,只绘制可见的线段:
// connects 8 points to form a cube void drawCube(struct Vector3* input) { for(int i = 0; i < 4; i++) { bool visible0, visible1; SDL_Point p0 = projectPoint(input[i], &visible0); SDL_Point p1 = projectPoint(input[(i + 1) % 4], &visible1); if(visible0 && visible1) { SDL_RenderDrawLine(gRenderer, p0.x, p0.y, p1.x, p1.y); } } for(int i = 0; i < 4; i++) { bool visible0, visible1; SDL_Point p0 = projectPoint(input[i + 4], &visible0); SDL_Point p1 = projectPoint(input[(i + 1) % 4 + 4], &visible1); if(visible0 && visible1) { SDL_RenderDrawLine(gRenderer, p0.x, p0.y, p1.x, p1.y); } } for(int i = 0; i < 4; i++) { bool visible0, visible1; SDL_Point p0 = projectPoint(input[i], &visible0); SDL_Point p1 = projectPoint(input[i + 4], &visible1); if(visible0 && visible1) { SDL_RenderDrawLine(gRenderer, p0.x, p0.y, p1.x, p1.y); } } }
2. 修复初始化函数的错误检查bug
init()中创建Renderer后错误检查的是gWindow,改为检查gRenderer:
bool init() { if(SDL_Init(SDL_INIT_VIDEO) != 0) { printf("SDL Error: %s", SDL_GetError()); return false; } gWindow = SDL_CreateWindow("hi", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, gWidth, gHeight, 0); if(gWindow == NULL) { printf("SDL Error: %s", SDL_GetError()); return false; } gRenderer = SDL_CreateRenderer(gWindow, -1, SDL_RENDERER_PRESENTVSYNC); // 修复:检查gRenderer而非gWindow if(gRenderer == NULL) { printf("SDL Error: %s", SDL_GetError()); return false; } return true; }
3. 修复主循环的事件处理与退出逻辑
主循环中需循环处理所有待处理事件,且收到退出事件时应正常执行资源释放:
int main() { // initialize SDL stuff if(!init()) { return 1; } bool isRunning = true; SDL_Event e; const Uint8* gKeyStates = SDL_GetKeyboardState(NULL); struct Vector3 points[8] = { [0] = {-150, -150, 150}, [1] = {150, -150, 150}, [2] = {150, 150, 150}, [3] = {-150, 150, 150}, [4] = {-150, -150, -150}, [5] = {150, -150, -150}, [6] = {150, 150, -150}, [7] = {-150, 150, -150}, }; while(isRunning) { // 循环处理所有事件,避免队列堆积 while(SDL_PollEvent(&e)) { if(e.type == SDL_QUIT) { isRunning = false; } } if(gKeyStates[SDL_SCANCODE_LEFT]) { translateModel(points, -moveSpeed, 0, 0); } if(gKeyStates[SDL_SCANCODE_RIGHT]) { translateModel(points, moveSpeed, 0, 0); } if(gKeyStates[SDL_SCANCODE_UP]) { translateModel(points, 0, 0, -moveSpeed); } if(gKeyStates[SDL_SCANCODE_DOWN]) { translateModel(points, 0, 0, moveSpeed); } draw(points); } close(); return 0; }
关于Dr. Memory错误日志的说明
- Error #1:栈越界访问,大概率是程序异常导致的栈溢出,修复上述核心问题后该错误可能消失。
- Error #2:GDI相关的未初始化读取,属于SDL与系统交互的轻微问题,一般不会导致内存暴涨,可忽略或尝试更新SDL版本解决。
内容的提问来源于stack exchange,提问作者CopperLover
相关产品推荐
相关产品推荐

