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

为何调用GetIpAddrTable会触发栈错误与堆损坏异常?重启后首次运行正常后续失效问题咨询

解析GetIpAddrTable首次调用后堆损坏的问题

咱们来一步步拆解你遇到的堆损坏问题:

核心问题:未处理缓冲区大小动态变化的情况

你的代码逻辑参考了微软文档示例,但忽略了一个关键场景:在第一次获取缓冲区大小和第二次调用GetIpAddrTable之间,系统的IP地址表可能发生变化(比如新增网卡IP、虚拟机IP等),导致之前获取的dwSize已经不足以容纳最新的IP表数据。

当第二次调用GetIpAddrTable时,如果返回ERROR_INSUFFICIENT_BUFFER,你的代码没有做任何处理,直接去访问pIPAddrTable->dwNumEntries——这时候GetIpAddrTable已经尝试向缓冲区写入超出其大小的数据,直接破坏了堆结构,导致后续调用抛出堆损坏异常。

另外还有一个小细节可能加剧问题:你第一次调用GetIpAddrTable时,dwSize初始化为0,虽然API会正确返回需要的大小,但按照设计规范,第一次传入非NULL缓冲区时,应该把dwSize设置为缓冲区的实际大小(也就是sizeof(MIB_IPADDRTABLE)),这样API能更准确判断缓冲区是否足够。

修复方案

你需要把GetIpAddrTable的调用改成循环处理,直到成功获取数据或者遇到非缓冲区不足的错误:

BOOL GetMyIPAddress(LPVOID addressBuffer) {
    int i;
    BOOL result = FALSE;
    IN_ADDR loopbackSubnet = { 0 }, loopbackNetwork = { 0 };
    IN6_ADDR ipv6Loopback;
    ZeroMemory(&ipv6Loopback, sizeof(IN6_ADDR));
    inet_pton(AF_INET, "255.255.0.0", &loopbackSubnet);
    inet_pton(AF_INET, "127.0.0.1", &loopbackNetwork);
    inet_pton(AF_INET6, "::1", &ipv6Loopback);

    PMIB_IPADDRTABLE pIPAddrTable = NULL;
    DWORD dwSize = sizeof(MIB_IPADDRTABLE);
    DWORD dwRetVal = 0;
    IN_ADDR IPAddr = { 0 };
    LPVOID lpMsgBuf = NULL;

    // 先分配初始大小的缓冲区
    pIPAddrTable = (MIB_IPADDRTABLE*)MALLOC(dwSize);
    if (!pIPAddrTable) {
        printf("Memory allocation failed for GetIpAddrTable\n");
        return FALSE;
    }

    // 循环调用,直到成功获取数据或出现非缓冲区不足的错误
    while (TRUE) {
        ZeroMemory(pIPAddrTable, dwSize);
        dwRetVal = GetIpAddrTable(pIPAddrTable, &dwSize, FALSE);
        
        if (dwRetVal == NO_ERROR) {
            // 成功获取数据,处理逻辑
            for (i = 0; i < (int)pIPAddrTable->dwNumEntries; i++) {
                IPAddr.S_un.S_addr = (u_long)pIPAddrTable->table[i].dwAddr;
                if (((IPAddr.S_un.S_addr & loopbackSubnet.S_un.S_addr) ^ 
                     (loopbackNetwork.S_un.S_addr & loopbackSubnet.S_un.S_addr)) != 0) {
                    ((IN_ADDR*)addressBuffer)->S_un.S_addr = IPAddr.S_un.S_addr;
                    result = TRUE;
                    break;
                }
            }
            break;
        } else if (dwRetVal == ERROR_INSUFFICIENT_BUFFER) {
            // 缓冲区不够,重新分配
            FREE(pIPAddrTable);
            pIPAddrTable = (MIB_IPADDRTABLE*)MALLOC(dwSize);
            if (!pIPAddrTable) {
                printf("Memory allocation failed for GetIpAddrTable\n");
                result = FALSE;
                break;
            }
        } else {
            // 其他错误,打印信息后退出
            printf("GetIpAddrTable failed with error %d\n", dwRetVal);
            if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
                NULL,
                dwRetVal,
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                (LPTSTR)&lpMsgBuf,
                0,
                NULL)) {
                _RPT1(_CRT_WARN, "\tError: %s", lpMsgBuf);
                LocalFree(lpMsgBuf);
            }
            result = FALSE;
            break;
        }
    }

    // 确保所有路径下内存都会被释放
    if (pIPAddrTable) {
        FREE(pIPAddrTable);
        pIPAddrTable = NULL;
    }

    return result;
}

额外说明

  • 重启后第一次正常是因为此时IP表数据较少,第一次获取的dwSize刚好足够;后续调用时IP表可能新增了数据,导致dwSize不足触发堆损坏。
  • 修复后的代码加入循环处理,每次遇到缓冲区不足时都会重新分配足够的内存,彻底避免越界写入破坏堆结构。
  • 同时优化了内存释放逻辑,确保所有分支下都不会出现内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 18:39:07