Jenocn
908 字
5 分钟
C++运行时崩溃问题汇总
2019-12-27

归结起来,崩溃问题就一种,那就是访问了无效的内存地址,体现在具体的代码上无非两种情况,数组越界和失效的指针,下面将细说这两种情况,并且如何避免.

数组越界#

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

危险度:★☆☆ 描述: 没有指向任何地址,鉴于出错时往往比较好定位,所以危险度不算太高,但出错情况还是很多的 建议: 做好必要的指针判空,很多时候比如加载资源,使用第三方方法获取对象,查找对象等等的操作时,都需要对指针判空.

最后:#

避免写出可能崩溃的代码,需要保持良好的代码书写和书写约束,保持高内聚低耦合,保持良好的封装,保持有效的安全判断

C++运行时崩溃问题汇总
https://jenocn.github.io/posts/code/cpp/crashwithcpp/
作者
Jenocn
发布于
2019-12-27
许可协议
CC BY-NC-SA 4.0