- 浏览: 529519 次
文章分类
最新评论
-
chaodilei:
我可以提个意见吗?作为前端工程师,写博客应该注意段落标题的ba ...
我的前端之路 -
zhuchao_ko:
大家一起二。
我的前端之路
C++程序员必读:让你的代码更强大(1)
这篇文章提出了一些建议,能有引导我们写出更加强壮的C++代码,以避免产生灾难性的错误。即使、因为其复杂性和项目团队结构,你的程序目前不遵循任何编码规则,按照下面列出的简单的规则可以帮助您避免大多数的崩溃情况。
Introduction
在实际的项目中,当项目的代码量不断增加的时候,你会发现越来越难管理和跟踪其各个组件,如其不善,很容易就引入BUG。因此、我们应该掌握一些能让我们程序更加健壮的方法。
这篇文章提出了一些建议,能有引导我们写出更加强壮的代码,以避免产生灾难性的错误。即使、因为其复杂性和项目团队结构,你的程序目前不遵循任何编码规则,按照下面列出的简单的规则可以帮助您避免大多数的崩溃情况。
Background
先来介绍下作者开发一些软件(CrashRpt),你可以http://code.google.com/p/crashrpt/网站上下载源代码。CrashRpt 顾名思义软件崩溃记录软件(库),它能够自动提交你电脑上安装的软件错误记录。它通过以太网直接将这些错误记录发送给你,这样方便你跟踪软件问题,并及时修改,使得用户感觉到每次发布的软件都有很大的提高,这样他们自然很高兴。
图 1、CrashRpt 库检测到错误弹出的对话框
在分析接收的错误记录的时候,我们发现采用下文介绍的方法能够避免大部分程序崩溃的错误。例如、局部变量未初始化导致数组访问越界,指针使用前未进行检测(NULL)导致访问访问非法区域等。
我已经总结了几条代码设计的方法和规则,在下文一一列出,希望能够帮助你避免犯一些错误,使得你的程序更加健壮。
Initializing Local Variables
使用未初始化的局部变量是引起程序崩溃的一个比较普遍的原因,例如、来看下面这段程序片段:
- //Definelocalvariables
- BOOLbExitResult;//ThiswillbeTRUEifthefunctionexitssuccessfully
- FILE*f;//Handletofile
- TCHARszBuffer[_MAX_PATH];//Stringbuffer
- //Dosomethingwithvariablesabove...
上面的这段代码存在着一个潜在的错误,因为没有一个局部变量初始化了。当你的代码运行的时候,这些变量将被默认负一些错误的数值。例如bExitResult 数值将被负为-135913245 ,szBuffer 必须以“\0”结尾,结果不会。因此、局部变量初始化时非常重要的,如下正确代码:
- //Definelocalvariables
- //InitializefunctionexitcodewithFALSEtoindicatefailureassumption
- BOOLbExitResult=FALSE;//ThiswillbeTRUEifthefunctionexitssuccessfully
- //InitializefilehandlewithNULL
- FILE*f=NULL;//Handletofile
- //Initializestringbufferwithemptystring
- TCHARszBuffer[_MAX_PATH]=_T("");//Stringbuffer
- //Dosomethingwithvariablesabove...
注意:有人说变量初始化会引起程序效率降低,是的,确实如此,如果你确实非常在乎程序的执行效率,去除局部变量初始化,你得想好其后果。
Initializing WinAPI Structures
许多Windows API都接受或则返回一些结构体参数,结构体如果没有正确的初始化,也很有可能引起程序崩溃。大家可能会想起用ZeroMemory宏或者memset()函数去用0填充这个结构体(对结构体对应的元素设置默认值)。但是大部分Windows API 结构体都必须有一个cbSIze参数,这个参数必须设置为这个结构体的大小。
看看下面代码,如何初始化Windows API结构体参数:
- NOTIFYICONDATAnf;//WinAPIstructure
- memset(&nf,0,sizeof(NOTIFYICONDATA));//Zeromemory
- nf.cbSize=sizeof(NOTIFYICONDATA);//Setstructuresize!
- //Initializeotherstructuremembers
- nf.hWnd=hWndParent;
- nf.uID=0;
- nf.uFlags=NIF_ICON|NIF_TIP;
- nf.hIcon=::LoadIcon(NULL,IDI_APPLICATION);
- _tcscpy_s(nf.szTip,128,_T("PopupTipText"));
- //Addatrayicon
- Shell_NotifyIcon(NIM_ADD,&nf);
注意:千万不要用ZeroMemory和memset去初始化那些包括结构体对象的结构体,这样很容易破坏其内部结构体,从而导致程序崩溃.
- //DeclareaC++structure
- structItemInfo
- {
- std::stringsItemName;//Thestructurehasstd::stringobjectinside
- intnItemValue;
- };
- //Initthestructure
- ItemInfoitem;
- //Donotusememset()!Itcancorruptthestructure
- //memset(&item,0,sizeof(ItemInfo));
- //Insteadusethefollowing
- item.sItemName="item1";
- item.nItemValue=0;
- 这里最好是用结构体的构造函数对其成员进行初始化.
- //DeclareaC++structure
- structItemInfo
- {
- //Usestructureconstructortosetmemberswithdefaultvalues
- ItemInfo()
- {
- sItemName=_T("unknown");
- nItemValue=-1;
- }
- std::stringsItemName;//Thestructurehasstd::stringobjectinside
- intnItemValue;
- };
- //Initthestructure
- ItemInfoitem;
- //Donotusememset()!Itcancorruptthestructure
- //memset(&item,0,sizeof(ItemInfo));
- //Insteadusethefollowing
- item.sItemName="item1";
- item.nItemValue=0;
Validating Function Input
在函数设计的时候,对传入的参数进行检测是一直都推荐的。例如、如果你设计的函数是公共API的一部分,它可能被外部客户端调用,这样很难保证客户端传进入的参数就是正确的。
例如,让我们来看看这个hypotethical DrawVehicle() 函数,它可以根据不同的质量来绘制一辆跑车,这个质量数值(nDrawingQaulity )是0~100。prcDraw 定义这辆跑车的轮廓区域。
看看下面代码,注意观察我们是如何在使用函数参数之前进行参数检测:
- BOOLDrawVehicle(HWNDhWnd,LPRECTprcDraw,intnDrawingQuality)
- {
- //Checkthatwindowisvalid
- if(!IsWindow(hWnd))
- returnFALSE;
- //Checkthatdrawingrectisvalid
- if(prcDraw==NULL)
- returnFALSE;
- //Checkdrawingqualityisvalid
- if(nDrawingQuality<0||nDrawingQuality>100)
- returnFALSE;
- //Nowit'ssafetodrawthevehicle
- //...
- returnTRUE;
- }
Validating Pointers
在指针使用之前,不检测是非常普遍的,这个可以说是我们引起软件崩溃最有可能的原因。如果你用一个指针,这个指针刚好是NULL,那么你的程序在运行时,将报出异常。
- CVehicle*pVehicle=GetCurrentVehicle();
- //Validatepointer
- if(pVehicle==NULL)
- {
- //Invalidpointer,donotuseit!
- returnFALSE;
- }
Initializing Function Output
如果你的函数创建了一个对象,并要将它作为函数的返回参数。那么记得在使用之前把他复制为NULL。如不然,这个函数的调用者将使用这个无效的指针,进而一起程序错误。如下错误代码:
- intCreateVehicle(CVehicle**ppVehicle)
- {
- if(CanCreateVehicle())
- {
- *ppVehicle=newCVehicle();
- return1;
- }
- //IfCanCreateVehicle()returnsFALSE,
- //thepointerto*ppVehcilewouldneverbeset!
- return0;
- }
正确的代码如下;
- intCreateVehicle(CVehicle**ppVehicle)
- {
- //FirstinitializetheoutputparameterwithNULL
- *ppVehicle=NULL;
- if(CanCreateVehicle())
- {
- *ppVehicle=newCVehicle();
- return1;
- }
- return0;
- }
Cleaning Up Pointers to Deleted Objects
在内存释放之后,无比将指针复制为NULL。这样可以确保程序的没有那个地方会再使用无效指针。其实就是,访问一个已经被删除的对象地址,将引起程序异常。如下代码展示如何清除一个指针指向的对象:
- //Createobject
- CVehicle*pVehicle=newCVehicle();
- deletepVehicle;//Freepointer
- pVehicle=NULL;//SetpointerwithNULL
Cleaning Up Released Handles
在释放一个句柄之前,务必将这个句柄复制伪NULL (0或则其他默认值)。这样能够保证程序其他地方不会重复使用无效句柄。看看如下代码,如何清除一个Windows API的文件句柄:
- HANDLEhFile=INVALID_HANDLE_VALUE;
- //Openfile
- hFile=CreateFile(_T("example.dat"),FILE_READ|FILE_WRITE,FILE_OPEN_EXISTING);
- if(hFile==INVALID_HANDLE_VALUE)
- {
- returnFALSE;//Erroropeningfile
- }
- //Dosomethingwithfile
- //Finally,closethehandle
- if(hFile!=INVALID_HANDLE_VALUE)
- {
- CloseHandle(hFile);//Closehandletofile
- hFile=INVALID_HANDLE_VALUE;//Cleanuphandle
- }
下面代码展示如何清除File *句柄:
- //FirstinitfilehandlepointerwithNULL
- FILE*f=NULL;
- //Openhandletofile
- errno_terr=_tfopen_s(_T("example.dat"),_T("rb"));
- if(err!=0||f==NULL)
- returnFALSE;//Erroropeningfile
- //Dosomethingwithfile
- //Whenfinished,closethehandle
- if(f!=NULL)//Checkthathandleisvalid
- {
- fclose(f);
- f=NULL;//Cleanuppointertohandle
- }
Using delete [] Operator for Arrays
如果你分配一个单独的对象,可以直接使用new ,同样你释放单个对象的时候,可以直接使用delete . 然而,申请一个对象数组对象的时候可以使用new,但是释放的时候就不能使用delete ,而必须使用delete[]:
- //Createanarrayofobjects
- CVehicle*paVehicles=newCVehicle[10];
- delete[]paVehicles;//Freepointertoarray
- paVehicles=NULL;//SetpointerwithNULL
- or
- //Createabufferofbytes
- LPBYTEpBuffer=newBYTE[255];
- delete[]pBuffer;//Freepointertoarray
- pBuffer=NULL;//SetpointerwithNULL
Allocating Memory Carefully
有时候,程序需要动态分配一段缓冲区,这个缓冲区是在程序运行的时候决定的。例如、你需要读取一个文件的内容,那么你就需要申请该文件大小的缓冲区来保存该文件的内容。在申请这段内存之前,请注意,malloc() or new是不能申请0字节的内存,如不然,将导致malloc() or new函数调用失败。传递错误的参数给malloc() 函数将导致C运行时错误。如下代码展示如何动态申请内存:
- //Determinewhatbuffertoallocate.
- UINTuBufferSize=GetBufferSize();
- LPBYTE*pBuffer=NULL;//Initpointertobuffer
- //Allocateabufferonlyifbuffersize>0
- if(uBufferSize>0)
- pBuffer=newBYTE[uBufferSize];
为了进一步了解如何正确的分配内存,你可以读下Secure Coding Best Practices for Memory Allocation in C and C++(http://www.codeproject.com/KB/tips/CBP_for_memory_allocation.aspx)这篇文章。
Using Asserts Carefully
Asserts用语调试模式检测先决条件和后置条件。但当我们编译器处于release模式的时候,Asserts在预编阶段被移除。因此,用Asserts是不能够检测我们的程序状态,错误代码如下:
- #include<assert.h>
- //Thisfunctionreadsasportscar'smodelfromafile
- CVehicle*ReadVehicleModelFromFile(LPCTSTRszFileName)
- {
- CVehicle*pVehicle=NULL;//Pointertovehicleobject
- //Checkpreconditions
- assert(szFileName!=NULL);//ThiswillberemovedbypreprocessorinReleasemode!
- assert(_tcslen(szFileName)!=0);//ThiswillberemovedinReleasemode!
- //Openthefile
- FILE*f=_tfopen(szFileName,_T("rt"));
- //CreatenewCVehicleobject
- pVehicle=newCVehicle();
- //Readvehiclemodelfromfile
- //Checkpostcondition
- assert(pVehicle->GetWheelCount()==4);//ThiswillberemovedinReleasemode!
- //Returnpointertothevehicleobject
- returnpVehicle;
- }
看看上述的代码,Asserts能够在debug模式下检测我们的程序,在release 模式下却不能。所以我们还是不得不用if()来这步检测操作。正确的代码如下;
- CVehicle*ReadVehicleModelFromFile(LPCTSTRszFileName,)
- {
- CVehicle*pVehicle=NULL;//Pointertovehicleobject
- //Checkpreconditions
- assert(szFileName!=NULL);//ThiswillberemovedbypreprocessorinReleasemode!
- assert(_tcslen(szFileName)!=0);//ThiswillberemovedinReleasemode!
- if(szFileName==NULL||_tcslen(szFileName)==0)
- returnNULL;//Invalidinputparameter
- //Openthefile
- FILE*f=_tfopen(szFileName,_T("rt"));
- //CreatenewCVehicleobject
- pVehicle=newCVehicle();
- //Readvehiclemodelfromfile
- //Checkpostcondition
- assert(pVehicle->GetWheelCount()==4);//ThiswillberemovedinReleasemode!
- if(pVehicle->GetWheelCount()!=4)
- {
- //Oops...aninvalidwheelcountwasencountered!
- deletepVehicle;
- pVehicle=NULL;
- }
- //Returnpointertothevehicleobject
- returnpVehicle;
- }
Checking Return Code of a Function
断定一个函数执行一定成功是一种常见的错误。当你调用一个函数的时候,建议检查下返回代码和返回参数的值。如下代码持续调用Windows API ,程序是否继续执行下去依赖于该函数的返回结果和返回参数值。
- HRESULThres=E_FAIL;
- IWbemServices*pSvc=NULL;
- IWbemLocator*pLoc=NULL;
- hres=CoInitializeSecurity(
- NULL,
- -1,//COMauthentication
- NULL,//Authenticationservices
- NULL,//Reserved
- RPC_C_AUTHN_LEVEL_DEFAULT,//Defaultauthentication
- RPC_C_IMP_LEVEL_IMPERSONATE,//DefaultImpersonation
- NULL,//Authenticationinfo
- EOAC_NONE,//Additionalcapabilities
- NULL//Reserved
- );
- if(FAILED(hres))
- {
- //Failedtoinitializesecurity
- if(hres!=RPC_E_TOO_LATE)
- returnFALSE;
- }
- hres=CoCreateInstance(
- CLSID_WbemLocator,
- 0,
- CLSCTX_INPROC_SERVER,
- IID_IWbemLocator,(LPVOID*)&pLoc);
- if(FAILED(hres)||!pLoc)
- {
- //FailedtocreateIWbemLocatorobject.
- returnFALSE;
- }
- hres=pLoc->ConnectServer(
- _bstr_t(L"ROOT\\CIMV2"),//ObjectpathofWMInamespace
- NULL,//Username.NULL=currentuser
- NULL,//Userpassword.NULL=current
- 0,//Locale.NULLindicatescurrent
- NULL,//Securityflags.
- 0,//Authority(e.g.Kerberos)
- 0,//Contextobject
- &pSvc//pointertoIWbemServicesproxy
- );
- if(FAILED(hres)||!pSvc)
- {
- //Couldn'tconectserver
- if(pLoc)pLoc->Release();
- returnFALSE;
- }
- hres=CoSetProxyBlanket(
- pSvc,//Indicatestheproxytoset
- RPC_C_AUTHN_WINNT,//RPC_C_AUTHN_xxx
- RPC_C_AUTHZ_NONE,//RPC_C_AUTHZ_xxx
- NULL,//Serverprincipalname
- RPC_C_AUTHN_LEVEL_CALL,//RPC_C_AUTHN_LEVEL_xxx
- RPC_C_IMP_LEVEL_IMPERSONATE,//RPC_C_IMP_LEVEL_xxx
- NULL,//clientidentity
- EOAC_NONE//proxycapabilities
- );
- if(FAILED(hres))
- {
- //Couldnotsetproxyblanket.
- if(pSvc)pSvc->Release();
- if(pLoc)pLoc->Release();
- returnFALSE;
- }
Using Smart Pointers
如果你经常使用用享对象指针,如COM 接口等,那么建议使用智能指针来处理。智能指针会自动帮助你维护对象引用记数,并且保证你不会访问到被删除的对象。这样,不需要关心和控制接口的生命周期。关于智能指针的进一步知识可以看看Smart Pointers - What, Why, Which?(http://ootips.org/yonat/4dev/smart-pointers.html) 和 Implementing a Simple Smart Pointer in C++(http://www.codeproject.com/KB/cpp/SmartPointers.aspx)这两篇文章。
如面是一个展示使用ATL's CComPtr template 智能指针的代码,该部分代码来至于MSDN。
- #include<windows.h>
- #include<shobjidl.h>
- #include<atlbase.h>//ContainsthedeclarationofCComPtr.
- intWINAPIwWinMain(HINSTANCEhInstance,HINSTANCE,PWSTRpCmdLine,intnCmdShow)
- {
- HRESULThr=CoInitializeEx(NULL,COINIT_APARTMENTTHREADED|
- COINIT_DISABLE_OLE1DDE);
- if(SUCCEEDED(hr))
- {
- CComPtr<IFileOpenDialog>pFileOpen;
- //CreatetheFileOpenDialogobject.
- hr=pFileOpen.CoCreateInstance(__uuidof(FileOpenDialog));
- if(SUCCEEDED(hr))
- {
- //ShowtheOpendialogbox.
- hr=pFileOpen->Show(NULL);
- //Getthefilenamefromthedialogbox.
- if(SUCCEEDED(hr))
- {
- CComPtr<IShellItem>pItem;
- hr=pFileOpen->GetResult(&pItem);
- if(SUCCEEDED(hr))
- {
- PWSTRpszFilePath;
- hr=pItem->GetDisplayName(SIGDN_FILESYSPATH,&pszFilePath);
- //Displaythefilenametotheuser.
- if(SUCCEEDED(hr))
- {
- MessageBox(NULL,pszFilePath,L"FilePath",MB_OK);
- CoTaskMemFree(pszFilePath);
- }
- }
- //pItemgoesoutofscope.
- }
- //pFileOpengoesoutofscope.
- }
- CoUninitialize();
- }
- return0;
- }
Using == Operator Carefully
先来看看如下代码;
- CVehicle*pVehicle=GetCurrentVehicle();
- //Validatepointer
- if(pVehicle==NULL)//Using==operatortocomparepointerwithNULL
- returnFALSE;
- //Dosomethingwiththepointer
- pVehicle->Run();
上面的代码是正确的,用语指针检测。但是如果不小心用“=”替换了“==”,如下代码;
- CVehicle*pVehicle=GetCurrentVehicle();
- //Validatepointer
- if(pVehicle=NULL)//Oops!Amistypinghere!
- returnFALSE;
- //Dosomethingwiththepointer
- pVehicle->Run();//Crash!!!
看看上面的代码,这个的一个失误将导致程序崩溃。
这样的错误是可以避免的,只需要将等号左右两边交换一下就可以了。如果在修改代码的时候,你不小心产生这种失误,这个错误在程序编译的时候将被检测出来。
原文:http://blog.csdn.net/xxxluozhen/article/details/6611663
相关推荐
懒得写描述了,大学几年下载了好多资源,分享给大家,都不要资源分。就这样了。。。
文档连阶段给出了相对应的书目,和适合想从事C++的人员参考。
C++ Primer 第5版 ,C++程序员 必读系列
体会:这里打包了作为一个C++程序员必读的经典书籍,工作多年,这些被翻得斑驳的书籍依然带在我手边;里面资料全是英文版的,不懂英文的就别浪费积分了,希望对新手,老手有用!
C++程序员 必读 常用 面试经典题 面试题 详解面试题重点 有详细解说 答案 关键字 函数等语法都解说很全面
个人整理的c++程序员各阶段应该读的书籍 包括effective c++, more effective c++, thinking in c++, exceptional c++, more exceptional c++, c++语言的设计和演化,深度探索c++对象模型
程序员必读经典电子书收集 包括设计模式 C++ 算法等等
面向对象设计程序员必读,c++初学者适合一看,各有启迪
介绍了所有学习c++的好书,想成为c++高手必须看的书。
一个真正的c++程序员的必读书目
如何优化C语言代码(程序员必读) 1、选择合适的算法和数据结构 2、使用尽量小的数据类型 。。。。 嵌入式实时程序设计中C/C++代码的优化
本书是美国微软出版社授权的 Microsoft Visual Studio系列中文版图书之一,它是 Visual C++ 6.0程序员的实用参考书。 书中讨论的许多主题均以范例程序进行说明,所有范例程序的项目文件都在配套光盘上。本书是从事...
C++高级程序员必备教程,高级C++开发必读教程,面试必备
C程序员面试必读 包括(C++)改善程序设计技术的50个有效做法(PPT) C语言经典程20例 C++C编程规范 c与c++面试题汇总
1.2.4 系统更容易表达和理解 4 1.2.5 “库”使你事半功倍 4 1.2.6 错误处理 5 1.2.7 大程序设计 5 1.3 方法学介绍 5 1.3.1 复杂性 5 1.3.2 内部原则 6 1.3.3 外部原则 7 1.3.4 对象设计的五个阶段 9 1.3.5 方法承诺...
c\c++程序员必读!c\c++程序员必读!c\c++程序员必读!c\c++程序员必读!
学习C++ 和STL学习学习的书,三合一(共140种改善您的C++程序的经验之谈,C++程序员必读),英文原版,带书签,2018版本。难得的c++学习资料
C++编程惯用法.高级程序员常用方法和技巧.pdf ,C++程序员必读