内存问题一直是C/C++程序员的心头大患,因为没有GC机制,所以需要我们自己管理内存。在 从“new和malloc的不同”出发看CC++的内存分配 一文中,讲述了几种内存错误的例子,那么避免这些陷阱呢?
1. new/delete 、malloc/free、new[]/delete[] 不匹配,导致的内存污染或者内存泄漏
2. 野指针,delete/free之后,没有置NULL,再次使用
3. delete/free之后,再次释放,double free
3. 申请内存之后,没有判断是否成功,直接使用
4. 内存越界,数组下标越界
5. memset 参数错误,造成 内存踩踏, 污染内存
6. 函数返回指向局部指针变量,离开作用域,变量失效了,再使用就可能发生异常。注意,编译时会有警告。空悬指针
7. 内存分配成功,没有初始化
8. 类成员中含有类对象,但使用memset等函数进行内存操作
9. map erase 删除操作会使当前指向被删除元素的迭代器失效。要注意这样删除map.erase(it_pos++);, 这样map.erase(it_pos);再继续访文迭代器是错误的。
10. 缓冲区溢出
1. 使用智能指针unique_ptr
unique_ptr是独享被管理对象指针所有权的智能指针,离开作用域之后会自动销毁对象释放资源,可以解决原始指针易别重复释放或者不释放(即内存泄漏)的问题。
2. 使用智能指针shared_ptr/weak_ptr
shared_ptr可以解决空悬指针和野指针的问题。shared_ptr中最关键的是增加了一个引用计数,用来标识多少个智能指针访问此资源,这样相比unique_ptr它就可以复制拷贝。并且作为一个原子变量,它本身是线程安全的。但是一定要注意,它所管理的对象不是线程安全的。
3. 使用静态代码分析工具
如cppcheck,clang-tidy等
以cppcheck为例子,它可以辅助检查代码,如下问题
1. 自动变量检查
2. 数组的边界检查
3. class类检查
4. 过期的函数,废弃函数调用检查
5. 异常内存使用,释放检查
6. 内存泄漏检查,主要是通过内存引用指针
7. 操作系统资源释放检查,中断,文件描述符等
8. 异常STL 函数使用检查
9. 代码格式错误,以及性能因素检查
还有一些具体的未定义行为
- 死指针
- 被零除
- 整数溢出
- 无效的移位操作数
- 无效的转换
- STL的用法无效
- 内存管理
- 空指针取消引用
- 越界检查
- 未初始化的变量
- 写入const数据
4. Valgrind检查运行时内存错误
最常用的命令格式(test为我们的应用程序):
valgrind --tool=memcheck --leak-check=full ./test
对与malloc()/free()/new/delete的调用都会被捕获。下列问题,它都能动态的检测出来
- 使用未初始化内存
- 读/写释放后的内存块
- 读/写超出malloc分配的内存块
- 读/写不适当的栈中内存块
- 内存泄漏,指向一块内存的指针永远丢失
- 不正确的malloc/free或new/delete匹配
- memcpy()相关函数中的dst和src指针重叠
基本原理如下图所示
Memcheck 会建立了两张全局表
Valid-Value 表:这张表存放了进程地址空间的所有有效数据和CPU寄存器。
Valid-Address 表:1个bit位映射进程地址空间中的一个字节(byte),负责记录该地址是否能够被读写。
Memcheck中内置一个虚拟CPU( 英文为 synthetic CPU、直译为合成CPU),结合上述两张表进行工作。
当内存中的某个字节被加载到CPU中的时候,也会被加载到虚拟CPU中。如果寄存器中的值,对内存产生变化,那么memcheck将会检查Vaild-Value Map, 如果找不到就是尚未初始化,则报告未初始化内存错误。如果读写内存中的某个字节,则会检查Vaild-Address Map,如果这个位置标记的是无效位置,则报告读写错误。valgrind 功能很强大,也不只memcheck一个工具,如果对这块感兴趣,强烈建议直接参考官网的手册 这里。
5. 相互review
同事之间相互review code,可以提高代码质量。
参考