添加std::vector声明后C++程序出现异常行为的原因
问题分析:C++程序的异常与无限运行根源
注意:以下程序存在未定义行为,运行有破坏性风险,后果自负
代码重现
结构体定义
struct MyStruct { int* myarr; int size; MyStruct(int size) { myarr = new int[size]; for (int i = 0; i < size; i++) { myarr[i] = i; } } MyStruct(const MyStruct& other) { size = other.size; myarr = new int[size]; for (int i = 0; i < size; i++) { myarr[i] = other.myarr[i]; } } MyStruct(MyStruct&& other) { this->myarr = other.myarr; this->size = other.size; other.myarr = nullptr; } MyStruct& operator=(MyStruct&& other) { this->myarr = other.myarr; this->size = other.size; other.myarr = nullptr; return *this; } };
函数定义
MyStruct newStruct(MyStruct& temp, int size) { temp = MyStruct{size}; return temp; }
主函数
int main() { vector<int> myvector {1}; MyStruct tempStruct {2}; MyStruct myst {newStruct(tempStruct, 5)}; }
运行现象
- 有时抛出异常:
terminate called after throwing an instance of 'std::bad_array_new_length' what(): std::bad_array_new_length
- 有时无限运行直至磁盘空间耗尽,需通过
SIGKILL终止进程 - 移除
std::vector/std::string声明后程序运行正常;替换为std::string会出现相同问题
问题根源:未定义行为与栈布局变化的耦合
你的程序存在两处核心缺陷,结合栈布局变化触发了不可预测的未定义行为:
1. 缺失析构函数导致内存泄漏与堆内存混乱
MyStruct未定义析构函数,编译器生成的默认析构函数不会释放myarr指向的堆内存。内存泄漏本身不会直接触发异常,但会导致堆内存中的旧块被后续分配(如vector的内存操作)重用,可能破坏其他对象的关键数据(比如size成员的值)。
2. 移动赋值运算符未处理原有资源释放
移动赋值运算符直接覆盖目标对象的myarr指针,未释放其原有指向的内存。这不仅加剧了内存泄漏,还可能导致堆内存的旧块被重用时,与其他对象的内存操作产生冲突,篡改size等关键成员的值。
3. 栈布局变化触发未定义行为的异常表现
当引入std::vector或std::string时,栈的内存布局发生改变,原本隐藏的未定义行为被触发:
- 如果
size成员被篡改为负数,new int[size]会将负数转换为极大的无符号整数,触发std::bad_array_new_length异常。 - 如果内存覆盖导致程序执行流被篡改,可能进入无限循环或持续分配内存,直至磁盘耗尽。
修复方案
遵循C++的三/五法则,补全MyStruct的必要函数,彻底消除未定义行为:
struct MyStruct { int* myarr; int size; // 构造函数 MyStruct(int size) : size(size), myarr(new int[size]) { for (int i = 0; i < size; i++) { myarr[i] = i; } } // 拷贝构造函数 MyStruct(const MyStruct& other) : size(other.size), myarr(new int[other.size]) { for (int i = 0; i < size; i++) { myarr[i] = other.myarr[i]; } } // 移动构造函数 MyStruct(MyStruct&& other) noexcept : myarr(other.myarr), size(other.size) { other.myarr = nullptr; other.size = 0; } // 拷贝赋值运算符 MyStruct& operator=(const MyStruct& other) { if (this != &other) { // 防止自赋值 delete[] myarr; // 释放原有资源 size = other.size; myarr = new int[size]; for (int i = 0; i < size; i++) { myarr[i] = other.myarr[i]; } } return *this; } // 移动赋值运算符 MyStruct& operator=(MyStruct&& other) noexcept { if (this != &other) { // 防止自赋值 delete[] myarr; // 释放原有资源 myarr = other.myarr; size = other.size; other.myarr = nullptr; other.size = 0; } return *this; } // 析构函数 ~MyStruct() { delete[] myarr; } };
同时优化newStruct函数,直接返回临时对象以触发移动语义,避免不必要的拷贝:
MyStruct newStruct(int size) { return MyStruct{size}; }
主函数简化为:
int main() { vector<int> myvector {1}; MyStruct myst {newStruct(5)}; }
修改后程序将稳定运行,不会出现异常或无限内存分配问题。
内容的提问来源于stack exchange,提问作者AntiClub
相关产品推荐
相关产品推荐

