一、内存问题引起的Crash
程序的崩溃对每个开发人员来说,都是一种磨难的存在,不经历不会成长,但再怎么成长也不愿意经历。在程序崩溃的现象中,内存引起的程序崩溃一直是重要的原因,也可以说,内存的异常引起的程序崩溃是一种较大概率的存在。
二、典型的内存问题
内存问题有很多种,本篇文章主要分析两个典型的情况:
1、缓冲区溢出
Buffer Overflow,一般也可以认为是内存越界。这种现象对于初学者和中级开发者来说非常常见。它的危害性不光局限于引起程序崩溃,更可怕是的,黑客可以利用这些溢出的缓冲区植入各种木马和病毒,从而产生更大的危害。
最常见的几种缓冲区溢出的情况有:
1)数组(类似数组的内存区等)越界访问
2)指针的访问越界(比如拷贝大数据到一个小内存等 )
3)这里还要专门把字符串拿出来说事儿,在做字符串的拷贝、搜索定位等情况下,防止出现溢出。
而面对这种比较经典的内存问题,目前的避免或者说解决手段也比较多。常见的有:
1)代码检查,比如使用一个内存检测工具(Valgrind或Cppcheck等)或人工Reivew等,查看是否有内存越界
2)使用编译器进行自动检查,比如编译器经常会有一些内置的安全选项,如gcc中的-fsanitize=address
3)开发者的代码控制手段,也有几种方法,首先是增加对内存宽度的检查,如数组的长度,动态分配内存的长度是否匹配等,其次是使用一些更高级的函数,这些函数内部其实是增加了类似内存长度的检查这种机制,比如strcpy函数改为strncpy函数(可能在Windows和Linux下略有不同),再次就是可以借助一些库提供的高级的数据结构,降低对内存的直接依赖,诸如类似于vector这类自动内存管理的数据结构。
下面看一个简单的例子:
int main(){int buf[100] = {0};
//下面没有做准确的边界检查
for (int num = 0;num <= 100;num++){buf[num] = num;
}
//可以用vector替代
std::vector<int> vec;
for (int num = 0;num <= 100;num++){vec.emplace_back(num);
}return 0;
}
2、释放后应用
use after free,释放后应用。这个就属于看上去简单,其实真正应用起来就比较麻烦了。大家可能觉得,我不在应用释放掉的指针不就可以了?确实如此。但实际情况非常复杂,既有可能自己疏忽大意,也有可能是别人传入的指针被意外释放但没有置空,也有可能是多线程中的锅,凡此种种,不一而足。
常见的这种内存问题引起的原因有以下几种,看看自己犯过哪类:
1)野指针或悬垂指针:这种一般见于初学者,就是把指针释放后又能各种情况供其它模块使用,或者参数及返回值等情况传递给相关调用者(比如局部变量的指针)。
2)重复释放指针:这个一般是疏忽或者见于接口设计不好,互操作异常引起的。
3)提前释放全局或静态指针:这种情况一般也是设计未考虑周全,在某些情况下导致了指针的提前释放。但一般这种设计上都会有置nullptr或NULL的过程,不会引起什么大的问题。
4)多线程间的非安全指针访问:这个就属于比较难缠的情况,正常情况下,应该避免这种设计。如果是必须不可的应用,则要增加同步和判断机制。其实最好是引入更高一层的缓冲加锁控制。
解决这种内存问题同样和上面的溢出一样有几种方式:
1)使用内存工具或编译工具的内存检查选项(同上)
2)使用RAII封装内存应用(有点类似于智能指针)
3)使用智能指针
4)内存在使用完成后,指针置空(供后续检查避免问题)
5)使用内存池
三、说明
内存问题除了上面提到的两种,其实有很多。比如内存被破坏,内存泄露以及内存多线程访问等等。内存的问题一直是各个编程语言的主流的问题,究其原因就在于,内存的管理的复杂度。这个内存管理的复杂度既包含应用层也包含库,实际上也指向上操作系统。
所以说,解决内存问题,不是一个单纯的应用控制的问题,它是一个系统工程。需要开发者不断的提升自己的技术和编程思想,清楚明确的知道自己当前的需求和实际条件,“以无隙入有间”,这才能很好的解决实际问题。
另外,内存引起的问题,可谓千奇百怪。有的时候不光是应用层的锅。记得在网上看过,有人说在一个循环里不断的开辟内存,但内存较大,应用后会回收内存,时间长了,就内存溢出了。原因在于它们的底层使用的tcmalloc。而其有一个特点,使用过的内存一般不会交还给系统,而是为了高效便于再次利用采取自己管理。可当开辟的较大较多的情况下,连续不断的向系统申请较大内存,系统就抗不住了。
所以说,每一个开发者要对自己的代码熟悉,也要对自己的面对的开发环境熟悉。因为对于客户或者测试来说,不管是谁的锅,都得先甩给你。总之,就是一直提倡的大原则,要根据实际情况,实事求是的来处理问题。这就需要有扎实的理论功底和丰富的实践经验。
四、总结
要想尽量减少内存问题,不但要有足够的知识和技术,更重要的要关于利用工具。当然,调试的水平也要不断的提高。多管齐下,才是解决问题的好办法。千万条路去罗马,总有一条适合开发者自己。