归结起来,崩溃问题就一种,那就是访问了无效的内存地址,体现在具体的代码上无非两种情况,数组越界和失效的指针,下面将细说这两种情况,并且如何避免.
数组越界
c数组
T value[10];
value[11]; // crash
危险度:★★★
描述: 这种数组有比较好的性能,但往往不便于管理. 建议: 1.数组长度使用枚举,常量或者宏 2.封装一个获取数值的方法,例如GetArrValue(int index)
,方法内判断index >= 0 && index < length
std::vector
std::vector<T> vec;
vec[100]; // crash when (100 >= vec.size())
危险度:★★☆ 描述: c数组的替代品,大部分场合下使用,都可以忽略数组长度,但是还是有很多场合下会导致数组越界,因为它仍然可以通过下标去获取元素 建议: 在使用时依然要判断下标的范围,比如index >= 0 && index < vec.size()
,建议根据需要封装方法来获取元素
野指针
未初始化指针
T* p;
p->func(); // crash
if (p) {
p->func(); // crash too
}
危险度:★★★ 描述: 未初始化的指针指向未知的地址,而不是nullptr
或者0
,这样会使得你在做指针判空时失效if(p)
建议: 养成初始化的好习惯,由于c++新特性,都不需要在构造函数的初始化列表中去初始化了,只需要int* p{ nullptr }
在声明时这样写一下就ok了,你还懒得写初始化吗?编程是一项严谨的事情.
内存被释放
T* p = new T();
delete p;
// ...
p->func(); // crash
if (p) {
p->func(); // crash too
}
危险度:★★★ 描述: 因为指向的内存已经被释放,导致后面的调用失效,这种情况写的不好会非常难找,而且开发时往往还容易遇到很多 建议: 1.delete之后,一定要给指针赋值为空.
T* p = new T();
T* other = p;
delete p;
p = nullptr;
//...
if (other) {
other->func(); // crash
}
危险度:★★★ 描述: 多个指针指向同一个内存地址,某个地方被释放了,而其他指针不知道 建议: 1.封装的作用在这里就体现了,不要把成员指针给外面到处赋值,否则后果自负 2.养成良好的成对书写习惯和谁申请谁释放的准则.哪个模块申请的内存,哪个模块负责释放. 3.如果确实有多个指针的需求,delete之后,请一定记得将他们赋值为nullptr 4.使用智能指针
迭代器失效
std::vector<int> vec;
vec.resize(10);
auto ite = vec.begin();
while (ite != vec.end()) {
if (condition) {
vec.push_back(99);
std::cout << *ite; // crash
}
++ite;
}
危险度:★★☆ 描述: 迭代器失效本质上也是指向的内存区域发生变化,故而成为野指针,导致崩溃的问题 建议: 不要在遍历的时候去做插入和删除的事情
空指针
T* p = nullptr;
p->func(); // crash
危险度:★☆☆ 描述: 没有指向任何地址,鉴于出错时往往比较好定位,所以危险度不算太高,但出错情况还是很多的 建议: 做好必要的指针判空,很多时候比如加载资源,使用第三方方法获取对象,查找对象等等的操作时,都需要对指针判空.
最后:
避免写出可能崩溃的代码,需要保持良好的代码书写和书写约束,保持高内聚低耦合,保持良好的封装,保持有效的安全判断