- 浏览: 497094 次
- 性别:
- 来自: 深圳
文章分类
- 全部博客 (185)
- job (15)
- linux/windows/unix/bash/shell (31)
- JAVA/J2EE/spring/hibernate/struts (30)
- VC/C++ (48)
- mysql/postgresql (6)
- php/jsp/asp/pear (1)
- FMS/flex/openlaszlo/red5/openmeetings (34)
- apache/tomcat/ftp/svn (6)
- xen/vm/Hadoop/cloudcompute (6)
- visual studio/eclipse/zendstudi/ant (8)
- others (1)
- windows异常处理 __try __except (1)
- (1)
- matlab (4)
- android (0)
最新评论
-
hongzhounlfd:
很透彻,很详细
依赖注入和控制反转 -
jefferyqjy:
谢谢~言简意赅~很明了!
依赖注入和控制反转 -
elderbrother:
太好了,谢谢
依赖注入和控制反转 -
east_zyd_zhao:
终于搞明白了
依赖注入和控制反转 -
Dremeng:
完美,一看就懂理解透彻
依赖注入和控制反转
关键词:C++;内存错误;内存泄漏;质量保证
Techniques and Tools of Defending Memory-related Defects in Software Coded in C++
CHENG Zhenlin, FANG Jinyun, TANG Zhimin
(Institute of Computing Technology, Chinese Academy of Sciences, Beijing 100080)
【Abstract】Most of the defects and errors in the software coded in C++ are memory-related. Based on the practice in the "network, large volumespatial information oriented GIS" project, this paper presentsthe techniques and tools to find and fix the memory problems during the coding,debugging and production release phase with the support of the C++ language mechanism, development environment and related quality-assurancetools.
【Key words】C++; Memory errors; Memory leak; Quality assurance
C++语言是桌面系统,尤其是系统软件、大型应用软件的主流开发语言。C++语言以其灵活性著称,同时也更复杂。利用C++编写健壮的代码,更具有挑战性。C++允许动态内存管理, 同时也容易导致更多和内存相关的问题。一般而言, 除了系统设计上的缺陷, 基于C++的软件的缺陷和错误大部分都和内存缺陷(主要包括内存访问错误和内存泄漏两类)相关。 所以,消除代码中的内存相关缺陷,成为程序员编写、调试、维护代码中的任务,也是保证软件质量的关键。
本文的工作基于“863”计划项目“面向网络海量空间信息的大型GIS”课题。该系统是基于C++/MFC编写,开发环境是Visual Studio .net 2003。本文基于此项目的工程实践,总结了如何使用C++语言机制、开发环境和相关质量保证工具来预防、发现各种编译期、运行期和内存相关的缺陷的方法和工具。
1 遵循C++相关的编码规范和惯用法,预防缺陷
编码规范是语言相关的规则,是经过实践总结出来的经验。良好的编程标准将有效地帮助开发人员避免开发有潜在危险的代码。一般来说,为了减少内存缺陷,应该遵循下列编码规则[1]:
(1)基类或者带有虚函数的类应该将其析构函数声明为虚函数。
(2)在构造函数中防止内存泄漏,在析构函数中不要抛出异常。
(3)使用对应形式的new和delete。即:用delete来释放new申请的内存,delete[]释放new[]申请的内存。
(4)指针在使用前必须初始化,指向动态内存的指针在释放后应立即置为空。
(5)如果类构造函数中分配了资源,那么需要显式提供拷贝构造函数和赋值操作符,并且在析构函数中释放资源。
值得重视的是C++中的惯用法RAII。RAII核心思想是利用对象来管理资源,在对象的构造函数中获取资源,在其析构函数中释放资源[2]。为了保证动态申请的内存能在即使出现异常的情况下仍能释放,比较理想的方法是使用局部变量来管理动态内存的所有权(ownership),就是所谓的智能指针。STL中的auto_ptr就是为解决资源所有权问题设计的,但是缺少对引用数和数组的支持并且不能用在STL容器中。Boost库[3]提供的智能指针相对成熟,实用价值高。其中,shared_ptr线程安全并且可以用在STL容器中。具体示例参考文献[3]。
1.1 编码规范检查工具 CodeWizard
CodeWizard能够对源程序直接进行自动扫描、分析和检查。一旦发现违例,产生信息告知与哪条规则不符并作出解释。以CodeWizard 4.3 为例,其中内置了超过500条编码标准。CodeWizard可以选择对于当前的工程执行哪些编码标准。CodeWizard可以和VC++紧密集成,安装完毕以后,VC++中有CodeWizard工具条。
1.2 代码检查工具 PC-Lint
PC-Lint可检查编译器不易发现的错误。PC-Lint可对100多个C库函数进行检查,可以发现标准C/C++代码中的1 000多个常见错误。要把PC-lint和Visual Studio集成在一起,需要自己配置。Jon Zyzyck提供了一个报告生成器,可以帮助完成这个工作。可在http://www.ddj.com下载。文献[4]说明了如何在VC++环境中集成PC-Lint。
2 利用语言机制、开发环境和相关工具以预防和发现内存缺陷
发现问题是解决问题的前提。相对于修复内存缺陷,发现内存缺陷并准确定位导致缺陷的代码更为费时费力。及早准确地发现内存缺陷,对于提高开发效率非常重要。
2.1 利用断言及早暴露内存缺陷
断言是布尔调试语句,用来检测在程序运行的时候某一条件的值是否总为真。断言经常用来确认函数的输入、输出,检查对象的当前状态是否合法等。 在以下的场景使用断言可以帮助发现和内存非法访问相关的错误:
(1)验证指针是否可读/写。在函数的入口处,经常需要验证指针所指向的内容区域是否可读/写。 通常采用assert(p!= NULL)的检测形式。 但是,指针的值不为空并不代表指针指向了合法可读/写内存。Win32 API提供了函数IsBadReadPtr、IsBadWritePtr、IsBadStringPtr、IsBadCodePtr用来检测指针指向的内存区域是否可读/写。C运行时库提供了_CrtIs ValidPointer、_CrtIsValidHeapPointer等函数,MFC库提供了AfxIsValidAddress、AfxIsValidString函数来完成类似功能。
(2)对基于MFC的程序,ASSERT_VALID宏通过调用重载的AssertValid函数来确定指向CObject派生类对象的指针是否有效。ASSERT_VALID宏主要调用了AfxIsValidAddress函数和CObject派生类对象的AssertValid函数(参考MFC源代码afx.h、objcore.cpp)。
2.2 利用C运行时刻库检查内存泄漏
VC++的C运行库(CRT)提供了广泛的功能,帮助用户检测内存泄漏。CRT提供了_CrtMemCheckPoint、_CrtDump MemoryLeaks、_CrtSetDbgFlag等函数来帮助调试内存泄漏。
对于非MFC的工程, 要开启有效的内存泄漏报告功能, 需要进行如下设置:
(1)在StdAfx.h的头部添加如下代码并开启编译器/Yu 选项:
#define _CRTDBG_MAP_ALLOC #include <stdlib.h> #include <crtdbg.h> #define DEBUG_NEW new(_NORMAL_BLOCK, THIS_FILE, __LINE__)
(2)确保在每个.cpp文件的头部包含以下内容:
#include "stdafx.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif
(3)在程序的开始处开启报告内存泄漏的开关:
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
对于MFC工程, MFC已经做了相关的工作, 只需要确认在每个.cpp文件的头部包含上述第(2)点的内容。
在某些情况下,需要知道发生内存泄漏的内存块中的内容,但是标准的内存转储只是内存块头部的十六进制形式。为了得到更多的有用信息,需要以用户块类型(_CLIENT_ BLOCK)申请内存,并利用_CrtSetDumpClient建立用户块型内存的转储函数。具体的说,对于不是从CObject继承的类,需要:
(1)为每个类/结构指定一个用户块子类型(参考crtdbg.h)。
(2)在申请内存时,采用重载的new形式:void* __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int nLine)(参考MFC源代码 afxmem.cpp),其中nType就是用户块的子类型。
(3)创建一个用户块内存转储函数,专门对每种需要转储的子类型进行处理(需要包含dbgint.h)。
(4)利用_CrtSetDumpClient对用户块内存转储函数进行注册(参考MFC源代码dumpinit.cpp)。
对于从CObject继承来的类,MFC 已经按照上述方法做了基础工作(参考MFC源代码 afxmem.cpp、dumpinit.cpp)。要有效转储从CObject继承的对象,需要:(1)对每个从CObject继承的类重载虚函数Dump。(2)在程序的初始化部分 加入代码 afxDump.SetDepth(1)来开启深度转储。
2.3 利用Purify和Insure++查找运行时内存缺陷
Rational Purify和Parasoft Insure++ 是用于运行时错误检查的工具。Purify主要检测:数组内存越界读/写,使用未初始化的内存,对已释放的内存进行读/写,内存泄漏等。Insure++利用其专利技术(源码插装和运行时指针跟踪)能够发现大量的内存操作错误,报告错误的源代码行和执行轨迹。根据笔者的测试(基于98个有各种内存错误的C++程序,涵盖了典型情形),Insure++ 6.1都能准确检测。
3 利用VC++环境的调试和诊断功能,检查和发现常见内存缺陷
理解常见的内存缺陷问题以及在VC++环境下的症状,能辅助我们减少问题的发生和及时修改问题。
从错误的表现形式上看, 和堆栈有关的错误主要分为两大类:堆栈溢出和函数返回信息被破坏。
(1)堆栈溢出(overflow)
此类错误主要有两种情形:
1)过大的局部变量。缺省情况下Windows为每个线程保留1M堆栈空间。在菜单Project->Properties->Configuration Properties -> Linker->System中可以看到Stack Reserve Size选项可以调整保留的堆栈空间大小。
2)递归调用层数过深。在调试过程中,调用堆栈(call stack)窗口中可以发现函数递归调用的模式。
(2)函数返回信息被破坏
此类错误主要有两种情形:
1)对局部变量的写操作超出了范围(上溢)。在调试过程中,函数堆栈被破坏掉的明显标志是无法显示调用堆栈,并且错误发生在被调用函数即将返回的位置。
2)在调用函数和被调用函数之间如果出现了函数参数的不匹配或者调用规范的不一致。
为了检查此类错误,应该在代码编译时打开/GS、/RTCs开关(在菜单Project->Properties->Configuration Properties-> C/C++->Code Generation下设置)。
另外一类错误是动态内存错误。典型的情况如下:
(1)内存写越界。在调试版本中,如果是写上溢,就会收到“Damage:after block...”的跟踪消息,如果是写下溢出就会收到“Damage: before block...”的跟踪消息。
(2)删除不合法指针。在调试版本中,删除未初始化的指针或者非堆指针时,会收到_CrtIsValidHeapPointer断言错误。
(3)多次释放。在调试版本中,如果多次删除同一指针, 会收到_BLOCK_TYPE_IS_VALID断言错误。要防止此类错误,应在delete某个指向动态内存的指针后立即将其置为空。
4 利用Windows结构化异常处理机制处理发布版本软件的内存崩溃
在程序的发布阶段,应尽量减少程序错误尤其是内存崩溃。如果崩溃了,应该“优雅”地退出,尽量收集程序崩溃时的运行信息以帮助程序供应商后续的调试。要捕捉内存非法访问并获知非法访问的指令地址、寄存器内容等信息,需要用到Windows的结构化异常处理(Structured Exception Handling,SEH)机制[6]。MiniDumpWriteDump是dbghelp.dll提供的一个 API函数(参考MSDN),用于转储用户模式程序的一些信息(比如堆栈情况等)并存为一个文件(比如.dmp文件),此文件可以被微软的调试器(VC++或者WinDBG)利用进行事后调试。使用此函数需要dbghelp.h、dbghelp.lib和dbghelp.dll(这些文件可以在Windows Platform SDK中找到)。
要事后根据.dmp文件调试代码,需要为发布版本软件产生debug symbols (pdb)文件(打开编译器/DEBUG选项)。在拿到.dmp文件以后,用VC++打开.dmp文件,然后调试执行(按F5键)。这样,崩溃现场就会重现。文献[5]基于上述的方法实现了崩溃报告系统。
5 结论
实践证明,在上述方法和工具支持下的减少软件内存缺陷的方法和工具,可以有效防止和查找代码中的内存错误和内存泄漏,并且能和开发人员日常编码无缝结合,执行起来非常高效。上述方法配合单元测试、代码评审、每日构建、Bug追踪等措施,形成了一个高效的质量保证流程,在我们的大型平台软件开发过程中起到了重要作用。
发表评论
-
C++STL轻松导学(2)
2011-09-27 17:02 13202.2.2 第二版:工业时代- ... -
C++ STL轻松导学
2011-09-27 16:59 1177作为C++标准不可缺少的 ... -
Chapter 6 Exceptions(JAVA EXCEPTION IN NATIVE CODE)
2011-09-26 09:53 1495Contents | Prev | Next | Index ... -
JNI编程中如何传递参数和返回值。
2011-09-14 17:51 1792首先要强调的是,native方法不但可以传递Java的基本类型 ... -
Windows Mobile与Android应用开发对比
2011-09-06 11:44 1291Windows Mobile在经历过最初的Wince系列,po ... -
android和JNI经典blog.doc
2011-09-01 15:29 1750Android JNI调用 2011-02-24 1 ... -
定义VC 消息映射函数小结
2011-08-21 22:15 1318定义VC 消息映射函数小 ... -
多线程中的事件对象
2011-08-21 14:23 1442Using Event Objects 使用事件对象 Appl ... -
VC++多线程调用webservice实例
2011-08-21 12:04 1587一、开始多线程 1.开始 ... -
多线程同步机制(Vc++)
2011-08-21 09:46 1740Synchronizing Execution of Mult ... -
如何结束线程VC++
2011-08-21 09:20 2796Terminating a Thread Terminati ... -
VS2005使用多字节字符集问题
2011-08-03 13:27 20831>------ 已启动生成: 项目: psgdatat ... -
matlab的作图函数(二维) 星号,点号 颜色
2011-07-27 14:57 10028zz matlab的作图函数(二维 ... -
android 调用C++的so
2011-07-08 18:36 4391第一步:开发环境的安 ... -
windows异常处理__try__except
2011-07-07 14:24 1972try-except用法 try except是win ... -
Java中的一个byte
2011-06-30 14:34 1012Java中的一个byte,其范围是-128~127的,而Int ... -
NDK中char*如何转换成jstring
2011-06-30 13:05 1874JNIEXPORT jstring JNICALLJava_T ... -
CFileDialog多选文件时的最大数量
2011-06-25 20:29 2280system("explorer d:\我的 ... -
C++信号处理编程风格规范
2011-06-24 10:07 21631.背景: C++做数字信号处理很普遍,如何 ... -
C++如何获取系统时间
2011-06-22 11:31 655//方案— 优点:仅使用C标准库;缺点:只能精确到秒级 #in ...
相关推荐
为了编写出健壮的代码,程序员必须掌握一系列减少内存缺陷的方法。以下内容将详细介绍在C++编程中预防和发现内存缺陷的方法以及可用的工具。 首先,遵守C++的编码规范和惯用法是预防内存缺陷的基础。编码规范通常...
下面将详细探讨几种有效的工具和方法,旨在帮助程序员预防、发现并消除C编程中的内存缺陷。 ### 一、遵循C++编码规范与惯用法 #### 预防缺陷的基础 1. **虚析构函数**:基类或包含虚函数的类应将其析构函数声明为...
5. **持续学习与交流**:参与社区讨论,阅读相关文献和技术博客,不断学习新的技术和方法,以及分享自己的经验和教训,都是提升编程技能的重要途径。 ### 结语 C/C++作为一种强大但复杂的编程语言,其缺陷和陷阱对...
5. **面向未来的拓展**:虽然本书主要聚焦于当前C/C++编程中的常见问题和解决方法,但作者也表达了对未来版本中增加更多内容(如网络编程、模板技术等)的期待,以更好地满足读者的需求。 综上所述,《从缺陷中学习...
根据提供的信息,《C和C++安全编码(第2版)》这本书由Robert C. Seacord撰写,专注于探讨如何在使用C和C++这两种广泛使用的编程语言时避免潜在的安全问题。尽管部分内容没有给出具体章节或段落信息,但从标题和描述...
在汽车行业中,C/C++编码规范扮演着至关重要的角色,因为现代汽车的电子控制系统和软件系统日益复杂,安全性和可靠性成为关注焦点。C/C++作为底层编程语言,被广泛用于汽车软件开发,如车载信息娱乐系统、自动驾驶...
《C++内存泄露检查指导》 引言: 内存泄露是C++编程中常见的问题,它会导致程序性能下降,甚至在严重情况下使...通过掌握正确的检测方法和采取有效的预防措施,可以显著减少内存泄露问题,提升程序的稳定性和效率。
总之,在嵌入式系统中编写安全的C/C++代码不仅涉及到具体的编程技巧和工具的选择,还需要一个全面的安全策略作为支撑。通过实施上述提到的最佳实践和技术手段,可以有效提升系统的安全性和稳定性。
C++TEST是由Parasoft公司提供的自动测试工具,主要用于提高C/C++代码的质量和开发效率。该工具集成了多种先进的测试技术,包括编码策略增强、静态分析、动态分析、单元与组件测试等,帮助开发者在开发过程中及时发现...
C++test工具中的“BugDetective”功能是一个动态分析模块,需要获得相应许可证后才能使用。它能够检测代码中的各种潜在问题,如访问数组越界、无效指针解引用、循环条件判断错误等。通过静态分析源代码,...
综上所述,C++单元测试培训资料全面介绍了单元测试的重要性、实施方法、技术工具以及在实际开发中的应用。通过这些内容的学习,开发者可以深刻理解单元测试的价值,掌握相关技术和工具,从而提高开发效率和软件质量...
Parasoft C++test是一款强大的静态代码分析和自动化单元测试工具,旨在帮助开发者提升代码质量,降低软件缺陷,确保软件的可靠性和稳定性。这份测试版用户指南提供了该工具的详细介绍和使用方法,以下是其中的一些...
总的来说,《Imperfect C++》是一本面向经验丰富的C++开发者的书籍,它鼓励读者在理解语言缺陷的基础上,找到编写高效、可靠代码的方法。通过深入讨论和实例,它提供了一个更全面的视角来应对C++的挑战,从而提升...
C++Test 是一款强大的自动化软件测试工具,专为C++编程语言设计,旨在提高代码质量、检测潜在的缺陷和遵循编码标准。它提供了静态分析、动态分析和单元测试等多种功能,帮助开发者在软件开发的各个阶段发现并修复...
它提供了超过 800 条内建规则,涵盖 MISRA、JSF、Ellemtel、Meyers's Effective C++ 和 Effective STL 等权威指南和书籍中的编程建议。 - **静态分析**:通过静态分析,C++test 能够在无需执行代码的情况下识别潜在...
C++ Test 是一款强大的静态代码分析工具,尤其在软件测试领域中,它能帮助开发者在程序执行前发现潜在的错误和不规范的编码习惯。本实验主要目标是理解和掌握静态测试的原理与实践,通过Parasoft C++ Test 进行代码...
- **变量存储**:选择合适的变量存储方式可以减少内存使用和提高性能。 - **整型、浮点型、枚举和布尔型变量及运算符**:这些基础数据类型的使用和操作都影响性能。 - **指针与引用**:指针和引用的使用方法对性能有...
在IT行业中,软件测试是确保产品质量的关键步骤,而白盒测试和C++Test则是其中的重要工具。本篇文章将深入探讨“白盒测试”这一概念,以及如何利用C++Test进行自定义目标系统的配置。 白盒测试,也被称为结构测试或...