SDL3:如何通过appstate传递指针数组?代码错误排查
我先写了一个能正常渲染“Hello, World!”的SDL3程序:
#define SDL_MAIN_USE_CALLBACKS 1 // NOLINT #include <SDL3/SDL.h> #include <SDL3/SDL_main.h> const int static WIDTH = 600; const int static HEIGHT = 600; const int static MAX = 255; const float static SCALE = 4.0F; const char static *MESSAGE = "Hello, World!"; // NOLINT /* This function runs once at startup. */ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { // NOLINT SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; /* Create the window */ if (!SDL_CreateWindowAndRenderer(MESSAGE, WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window and renderer: %s", // NOLINT SDL_GetError()); // NOLINT return SDL_APP_FAILURE; } *appstate = renderer; return SDL_APP_CONTINUE; } /* This function runs once per frame, and is the heart of the program. */ SDL_AppResult SDL_AppIterate(void *appstate) { // NOLINT auto *local_renderer = static_cast<SDL_Renderer *>(appstate); int width = 0; int height = 0; /* Center the message and scale it up */ SDL_GetRenderOutputSize(local_renderer, &width, &height); SDL_SetRenderScale(local_renderer, SCALE, SCALE); const float xpos = ((static_cast<float>(width) / SCALE) - (static_cast<float>(SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) * SDL_strlen(MESSAGE))) / 2; const float ypos = ((static_cast<float>(height) / SCALE) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2; /* Draw the message */ SDL_SetRenderDrawColor(local_renderer, 0, 0, 0, MAX); SDL_RenderClear(local_renderer); SDL_SetRenderDrawColor(local_renderer, MAX, MAX, MAX, MAX); SDL_RenderDebugText(local_renderer, xpos, ypos, MESSAGE); SDL_RenderPresent(local_renderer); return SDL_APP_CONTINUE; } /* This function runs when a new event (mouse input, keypresses, etc) occurs. */ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { // NOLINT if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ } return SDL_APP_CONTINUE; } /* This function runs once at shutdown. */ void SDL_AppQuit(void *appstate, SDL_AppResult result) { } // NOLINT misc-use-anonymous-namespace
我原本以为SDL_AppInit的appstate参数是指向数组的指针,通过*appstate = renderer;把renderer指针赋值给数组第一个元素,SDL_AppIterate能正常接收并转换为局部renderer指针。
之后我想把window指针也加入appstate,于是在SDL_AppInit中创建了void *state[2];,把state[0]设为renderer、state[1]设为window,然后尝试用appstate = (void **)&state;传递数组指针,但SDL_AppIterate中无法接收到这个数组指针(调试发现appstate是空的)。修改后的代码如下:
#define SDL_MAIN_USE_CALLBACKS 1 // NOLINT #include <SDL3/SDL.h> #include <SDL3/SDL_main.h> const int static WIDTH = 600; const int static HEIGHT = 600; const int static MAX = 255; const float static SCALE = 4.0F; const char static *MESSAGE = "Hello, World!"; // NOLINT /* This function runs once at startup. */ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { // NOLINT SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; void *state[2]; /* Create the window */ if (!SDL_CreateWindowAndRenderer(MESSAGE, WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window and renderer: %s", // NOLINT SDL_GetError()); // NOLINT return SDL_APP_FAILURE; } state[0] = renderer; state[1] = window; appstate = (void **)&state; return SDL_APP_CONTINUE; } /* This function runs once per frame, and is the heart of the program. */ SDL_AppResult SDL_AppIterate(void *appstate) { // NOLINT // 调试发现appstate是空的,它应该是SDL_AppInit末尾传入的数组指针 auto **state = static_cast<void **>(appstate); auto *local_renderer = static_cast<SDL_Renderer *>(state[0]); int width = 0; int height = 0; /* Center the message and scale it up */ SDL_GetRenderOutputSize(local_renderer, &width, &height); SDL_SetRenderScale(local_renderer, SCALE, SCALE); const float xpos = ((static_cast<float>(width) / SCALE) - (static_cast<float>(SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) * SDL_strlen(MESSAGE))) / 2; const float ypos = ((static_cast<float>(height) / SCALE) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2; /* Draw the message */ SDL_SetRenderDrawColor(local_renderer, 0, 0, 0, MAX); SDL_RenderClear(local_renderer); SDL_SetRenderDrawColor(local_renderer, MAX, MAX, MAX, MAX); SDL_RenderDebugText(local_renderer, xpos, ypos, MESSAGE); SDL_RenderPresent(local_renderer); return SDL_APP_CONTINUE; } /* This function runs when a new event (mouse input, keypresses, etc) occurs. */ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { // NOLINT if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ } return SDL_APP_CONTINUE; } /* This function runs once at shutdown. */ void SDL_AppQuit(void *appstate, SDL_AppResult result) { } // NOLINT misc-use-anonymous-namespace
请问我的代码哪里出错了?
你的代码存在两个核心错误:
1. 局部栈变量的生命周期问题
你在SDL_AppInit中定义的void *state[2];是栈上的局部变量,当SDL_AppInit函数执行完毕返回后,栈上的这个数组会被销毁,内存会被系统回收或重新使用。后续SDL的回调函数(比如SDL_AppIterate)尝试访问这个已经失效的地址,会导致未定义行为(比如拿到空指针或垃圾数据)。
2. appstate参数的赋值方式错误
SDL_AppInit的appstate参数是void**类型,它的作用是让你把要保存的状态指针写入到*appstate指向的地址,而不是修改appstate本身。你写的appstate = (void **)&state;只是修改了函数形参的指针值(形参是值传递,修改形参不会影响SDL内部保存的指针),SDL根本没有接收到你的状态数组地址。
修复方案
推荐使用动态分配的结构体来存储多个状态变量,这样内存会在堆上,生命周期可控,而且结构更清晰:
修复后的代码
#define SDL_MAIN_USE_CALLBACKS 1 // NOLINT #include <SDL3/SDL.h> #include <SDL3/SDL_main.h> const int static WIDTH = 600; const int static HEIGHT = 600; const int static MAX = 255; const float static SCALE = 4.0F; const char static *MESSAGE = "Hello, World!"; // NOLINT // 定义状态结构体,存储需要跨回调传递的变量 typedef struct { SDL_Window *window; SDL_Renderer *renderer; } AppState; /* This function runs once at startup. */ SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[]) { // NOLINT SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; /* Create the window */ if (!SDL_CreateWindowAndRenderer(MESSAGE, WIDTH, HEIGHT, SDL_WINDOW_RESIZABLE, &window, &renderer)) { SDL_Log("Couldn't create window and renderer: %s", // NOLINT SDL_GetError()); // NOLINT return SDL_APP_FAILURE; } // 动态分配结构体内存 AppState *state = static_cast<AppState*>(SDL_malloc(sizeof(AppState))); if (!state) { SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Log("Failed to allocate app state"); return SDL_APP_FAILURE; } state->window = window; state->renderer = renderer; // 将结构体指针赋值给*appstate,SDL会保存这个指针并传递给后续回调 *appstate = state; return SDL_APP_CONTINUE; } /* This function runs once per frame, and is the heart of the program. */ SDL_AppResult SDL_AppIterate(void *appstate) { // NOLINT // 将appstate转换为我们定义的结构体指针 AppState *state = static_cast<AppState*>(appstate); SDL_Renderer *local_renderer = state->renderer; int width = 0; int height = 0; /* Center the message and scale it up */ SDL_GetRenderOutputSize(local_renderer, &width, &height); SDL_SetRenderScale(local_renderer, SCALE, SCALE); const float xpos = ((static_cast<float>(width) / SCALE) - (static_cast<float>(SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) * SDL_strlen(MESSAGE))) / 2; const float ypos = ((static_cast<float>(height) / SCALE) - SDL_DEBUG_TEXT_FONT_CHARACTER_SIZE) / 2; /* Draw the message */ SDL_SetRenderDrawColor(local_renderer, 0, 0, 0, MAX); SDL_RenderClear(local_renderer); SDL_SetRenderDrawColor(local_renderer, MAX, MAX, MAX, MAX); SDL_RenderDebugText(local_renderer, xpos, ypos, MESSAGE); SDL_RenderPresent(local_renderer); return SDL_APP_CONTINUE; } /* This function runs when a new event (mouse input, keypresses, etc) occurs. */ SDL_AppResult SDL_AppEvent(void *appstate, SDL_Event *event) { // NOLINT if (event->type == SDL_EVENT_KEY_DOWN || event->type == SDL_EVENT_QUIT) { return SDL_APP_SUCCESS; /* end the program, reporting success to the OS. */ } return SDL_APP_CONTINUE; } /* This function runs once at shutdown. */ void SDL_AppQuit(void *appstate, SDL_AppResult result) { // 释放动态分配的结构体,同时销毁SDL资源 if (appstate) { AppState *state = static_cast<AppState*>(appstate); SDL_DestroyRenderer(state->renderer); SDL_DestroyWindow(state->window); SDL_free(state); } } // NOLINT misc-use-anonymous-namespace
关键修复点
- 定义了
AppState结构体,清晰管理多个状态变量 - 使用
SDL_malloc动态分配结构体(也可以用new,但SDL提供的内存函数更符合跨平台习惯) - 将结构体指针赋值给
*appstate,确保SDL能正确接收并传递这个状态 - 在SDL_AppQuit中释放结构体内存并销毁SDL资源,避免内存泄漏
内容的提问来源于stack exchange,提问作者user2309803

