C++结构体数组越界访问问题:如何规避野指针引发的程序崩溃
这个问题的核心是手动管理动态数组时没有设置可靠的终止标记,再加上C++动态数组分配后,超出有效元素范围的内存是未初始化的随机值——刚好你的环境里这个位置不是空指针,导致while循环越界访问到非法内存,最终崩溃。另外还有一个隐藏的坑:你的结构体成员name是const char*,但直接赋值exec_ssh_command返回的std::string会导致指针悬空,这也是潜在的崩溃隐患。
下面给你几种针对性的解决办法,按推荐程度排序:
1. 改用std::vector管理数组(最推荐的C++方案)
既然是C++代码,完全没必要手动折腾动态数组,std::vector会自动帮你管理内存、避免越界,还能彻底解决指针问题。
首先修改结构体(把易出问题的const char*换成std::string):
struct nicInfo { std::string name; std::string ipAddr; };
然后重写nicList函数:
std::vector<nicInfo> nicList() { std::vector<nicInfo> nicList; nicList.reserve(nic_numbers); // 预分配空间,提升效率 std::string nic_name_command; for (int i = 0; i < nic_numbers; i++) { nic_name_command = "ls /sys/class/net | sed -n -s " + std::to_string(i+1) + "p"; nicInfo info; info.name = sshObj->exec_ssh_command(nic_name_command); nicList.push_back(info); } return nicList; }
打印的时候直接用数组长度控制循环,不用依赖空指针:
// C++风格打印 auto nic_list = nicList(); for (const auto& nic : nic_list) { std::cout << "nic name : " << nic.name << std::endl; } // 如果非要用C风格循环 for (int i = 0; i < nic_list.size(); i++) { printf("nic name : %s\n", nic_list[i].name.c_str()); }
这种方式彻底避免了手动内存管理和越界问题,是C++开发的最佳实践。
2. 手动添加终止标记(兼容原C风格循环)
如果你一定要保留原有的动态数组和C风格打印逻辑,可以在分配数组时多申请一个元素,把最后一个元素的name设为nullptr,作为循环终止的信号:
nicInfo* nicList() { std::string num; std::string nic_name_command; // 多分配1个元素用于存放终止标记 nicInfo* nic = new nicInfo[nic_numbers + 1]; for (int i = 0; i < nic_numbers; i++) { nic_name_command = "ls /sys/class/net | sed -n -s " + std::to_string(i+1) + "p"; // 解决指针悬空问题:复制字符串到堆内存 std::string nameStr = sshObj->exec_ssh_command(nic_name_command); nic[i].name = strdup(nameStr.c_str()); } // 设置终止标记 nic[nic_numbers].name = nullptr; return nic; }
这样你的原while循环就能正常工作了,但要记得释放内存时:
nicInfo* nic = nicList(); int i = 0 ; while ((nic + i)->name ) { printf("nic name : %s\n", (nic + i)->name); free((void*)(nic + i)->name); // 先释放每个name的堆内存 i++; } delete[] nic; // 最后释放数组本身
3. 传递数组长度控制循环(最直接的兼容方案)
如果不想修改数组分配逻辑,也可以在返回数组的同时返回有效元素的长度,打印时用长度来控制循环,完全不用依赖空指针:
// 修改函数返回值为数组+长度的配对 std::pair<nicInfo*, int> nicList() { std::string num; std::string nic_name_command; nicInfo* nic = new nicInfo[nic_numbers]; for (int i = 0; i < nic_numbers; i++) { nic_name_command = "ls /sys/class/net | sed -n -s " + std::to_string(i+1) + "p"; std::string nameStr = sshObj->exec_ssh_command(nic_name_command); nic[i].name = strdup(nameStr.c_str()); } return {nic, nic_numbers}; }
打印时:
auto result = nicList(); nicInfo* nic = result.first; int count = result.second; for (int i = 0; i < count; i++) { printf("nic name : %s\n", nic[i].name); free((void*)nic[i].name); } delete[] nic;
关键注意点
你原来的代码里有个致命的指针悬空问题:nic[i].name = sshObj->exec_ssh_command(nic_name_command);如果exec_ssh_command返回的是std::string,那么这条语句会把临时std::string的c_str()指针赋值给name,但临时对象在语句结束后会立刻销毁,导致name变成指向已释放内存的悬空指针,后续访问大概率会崩溃或出现乱码。上面的方案里都用strdup复制了字符串到堆内存,或者改用std::string,就是为了修复这个问题。
内容的提问来源于stack exchange,提问作者fa7eme

