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

使用Win32 API的C程序访问结构体非首字段时崩溃问题

Win32 API自定义结构体写入崩溃问题解决

问题描述

正在学习Win32 API,实现过程中遇到自定义STATEINFO结构体的访问问题:编译正常,但在窗口过程WindowProcess中写入非首字段lastMessage2时触发运行时崩溃,错误码0xC000041D。使用MinGW+CodeBlocks环境,调试发现仅写入该字段时出错,读取无异常。

问题代码示例

#include <stdlib.h>
#include <stdio.h>
#include <windows.h>

LRESULT CALLBACK WindowProcess(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

typedef struct {UINT lastMessage1; UINT lastMessage2;} STATEINFO;

STATEINFO* GetAppState(HWND hwnd);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow) {
    // 创建窗口类
    const wchar_t WindowClassName[] = L"Test Window";

    WNDCLASS wc = {};
    wc.lpfnWndProc      = WindowProcess;
    wc.hInstance        = hInstance;
    wc.lpszClassName    = WindowClassName;

    RegisterClass(&wc);

    STATEINFO stateInfo = {}, *pStateInfo = &stateInfo;

    // 创建窗口实例
    HWND hwnd = CreateWindowEx(
        0, WindowClassName, "Window Title", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, pStateInfo);
    ShowWindow(hwnd, nCmdShow);

    // 启动窗口消息循环
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

LRESULT CALLBACK WindowProcess(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

    STATEINFO *pStateInfo = malloc(sizeof(STATEINFO*));

    if (uMsg == WM_CREATE) {
        pStateInfo = (STATEINFO*)(*((CREATESTRUCT*)(lParam))).lpCreateParams;
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pStateInfo);
    } else {
        pStateInfo = GetAppState(hwnd);
    }
    switch (uMsg) {
        case WM_CLOSE: {
            DestroyWindow(hwnd);
            return 0;
        }
        case WM_DESTROY: {
            PostQuitMessage(0);
            return 0;
        }
        break;
    }
    // 问题所在:
    pStateInfo->lastMessage1 = 1; // 此句正常
    pStateInfo->lastMessage2 = 1; // 此句存在时程序崩溃
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

STATEINFO* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    STATEINFO *pStateInfo = (STATEINFO*)(ptr);
    return pStateInfo;
}

问题分析

  • 栈局部变量生命周期问题:WinMain中stateInfo是栈上的局部变量,编译器可能因代码中WinMain后续未直接使用该变量,优化回收其栈空间,导致窗口过程中访问该地址时,内存已失效或被覆盖。写入首字段时可能刚好未触发内存违规,写入第二个字段时越界或访问无效内存引发崩溃。
  • 多余的malloc操作:WindowProcess开头的malloc(sizeof(STATEINFO*))完全无效,分配的是指针大小的内存而非结构体大小,且后续未使用该内存,造成内存泄漏。
  • 未检查指针有效性:访问结构体字段前未判断指针是否为NULL,若GWLP_USERDATA未被正确设置,会引发空指针访问。

解决方法

  1. 将结构体分配到堆上:用malloc分配STATEINFO,确保内存生命周期覆盖整个窗口存在时间,避免栈变量被优化回收。
  2. 移除多余的malloc代码:删除WindowProcess开头的无效malloc语句,消除内存泄漏。
  3. 在窗口销毁时释放堆内存:在WM_DESTROY消息处理中,获取结构体指针并free,避免内存泄漏。
  4. 添加指针有效性检查:访问结构体字段前先判断指针是否为NULL,避免空指针引用。

修改后的代码示例

#include <stdlib.h>
#include <stdio.h>
#include <windows.h>

LRESULT CALLBACK WindowProcess(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

typedef struct {UINT lastMessage1; UINT lastMessage2;} STATEINFO;

STATEINFO* GetAppState(HWND hwnd);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pCmdLine, int nCmdShow) {
    const wchar_t WindowClassName[] = L"Test Window";

    WNDCLASS wc = {};
    wc.lpfnWndProc      = WindowProcess;
    wc.hInstance        = hInstance;
    wc.lpszClassName    = WindowClassName;

    RegisterClass(&wc);

    // 堆分配结构体,确保内存生命周期稳定
    STATEINFO* pStateInfo = malloc(sizeof(STATEINFO));
    if (pStateInfo == NULL) {
        return 1; // 处理内存分配失败
    }
    // 初始化结构体字段
    pStateInfo->lastMessage1 = 0;
    pStateInfo->lastMessage2 = 0;

    HWND hwnd = CreateWindowEx(
        0, WindowClassName, L"Window Title", WS_OVERLAPPEDWINDOW, // 修正为宽字符标题
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, pStateInfo);
    ShowWindow(hwnd, nCmdShow);

    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // 冗余保护:防止窗口未正常销毁导致内存泄漏
    free(pStateInfo);
    return (int)msg.wParam;
}

LRESULT CALLBACK WindowProcess(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    STATEINFO *pStateInfo = NULL;

    if (uMsg == WM_CREATE) {
        CREATESTRUCT* pCreateStruct = (CREATESTRUCT*)lParam;
        pStateInfo = (STATEINFO*)pCreateStruct->lpCreateParams;
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pStateInfo);
    } else {
        pStateInfo = GetAppState(hwnd);
    }

    switch (uMsg) {
        case WM_CLOSE: {
            DestroyWindow(hwnd);
            return 0;
        }
        case WM_DESTROY: {
            // 释放堆分配的结构体内存
            if (pStateInfo != NULL) {
                free(pStateInfo);
            }
            PostQuitMessage(0);
            return 0;
        }
    }

    // 确保指针有效再访问字段
    if (pStateInfo != NULL) {
        pStateInfo->lastMessage1 = 1;
        pStateInfo->lastMessage2 = 1;
    }
    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

STATEINFO* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    return (STATEINFO*)ptr;
}

新手建议

  • 避免将栈局部变量指针传递给长期回调:栈变量生命周期仅限于当前函数执行期间,回调函数可能在函数退出后仍访问该指针,导致内存访问违规。
  • 始终检查指针有效性:访问指针指向的内存前,先判断指针是否为NULL,避免空指针引用。
  • 注意Win32 API字符串编码:窗口类使用宽字符(L前缀)时,窗口标题等相关字符串也应使用宽字符,避免编码不匹配问题。
  • 及时释放堆内存:使用malloc分配的内存必须在不再使用时free,避免内存泄漏。

内容的提问来源于stack exchange,提问作者Leonardo Carofiglio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 20:05:57