为何调用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
相关产品推荐
相关产品推荐

