分享

Assert是你的朋友(转帖)

 昵称9476773 2012-03-29
在C++的消息告示板上经常能够看到揭示ASSERT讹谬的求援消息。固然这等闲是渴望肃清ASSERT讹谬的帮助哀求,然而几乎所有的求援者都感受 ASSERT本身是罪恶的。我全面能会意一个ASSERT讹谬给过程员新手带来的悲观。你的过程正在运行,等闲如你所愿,陡然一声巨响——一个 ASSERT讹谬!
那么就让我们来看看ASSERT们,为什么他们会展目前那里以及我们能从他们那里获得什么消息。我该当偏重一下,这篇文章谈论MFC如何处理ASSERT。

语言学
敞开google搜查,输入“define assert”,然后单击搜查。WordNet中“ASSERT”有四种含义:
1.断言、号称、主意……(直接了当的解说)
2.确认、查证、确认、宣誓……(正式而肃穆的公布某事属实,如:“Before God I swear I am innocent”——“在上帝面前我宣誓我是纯净的”)
3. 坚持己见斗胆地或强有力地提出(自己)的见解,以使其为大家所知。(“Women should assert themselves more!”)
4.偏重、阐明(阐明事实,“The letter asserts a free society”)
上面的含义都很接近,然而第4个含义字面上更加接近我们的ASSERT。ASSERT阐明条件曾经被偏重为真。万一条件不为真,则过程将处于严重故障中,而你——过程员,则该当收到这个警告。

ASSERT在代码中的含义
当一个过程员写一条ASSERT就阐明他在说“这个条件定然为真,否则我们将发生讹谬”。例如你正在写一个函数,渴望获得一个字符串指针。
void CMyClass::MyFunc(LPCTSTR szStringPtr)
{
if (*szStringPtr == ’7’)
DoSomething();
}
这个函数读取这个指针所指向的内存段,因而它良好是指向一段合法管用的内存。否则你的过程将分化!万一你递交一个空指针给这个函数,那么任何过程在调用这个函数时都将发生讹谬。万一目前那个指针即便空的,而且你的过程分化了,你就未曾足够的消息出处理它。仅仅只有一个消息框,告诉你一个代码地址和你曾试探读取0x00000000段的内存。关系这个代码地址到你切实代码的那一行并不是一件迅捷的事,尤其你对于处理这种情形还很面生。
那么让我们对这个函数做一个小小的修正。
void CMyClass::MyFunc(LPCTSTR szStringPtr)
{
ASSERT(szStringPtr);

if (*szStringPtr == ’7’)
DoSomething();
}
这里所做的即便测验szStringPtr。万一为空则即刻分化。分化?是的。然而万一你正用DEBUG编译过程它会以一种受控的措施分化。MFC具有一些 build-in plumbing能够捕捉受控的分化,并将其和编译器的副本联系到同时。万一你正运行这个过程的DEBUG,并且ASSERT失利,你将会看到一个与此相仿的消息框。


这向你阐明哪个文件哪一行引发了ASSERT.你能够抉择abort,间断过程;也能够抉择ignore疏忽这个讹谬,有时这么做也见效;可能你能够抉择retry,重试。凡是你的计算机上安装了调试器,即便独自运行这个过程这条语句也会起作用。

release和debug之间的差异
懂得ASSERT是编译副手很重要。万一你用DEBUG编译你的过程,编译器就会包括ASSERT(...)中的所有代码。万一你抉择RELEASE编译,那么ASSERT本身以及圆括号中的所有代码都会灭亡。它感受你曾经测验过你过程的DEBUG版了,并且捉拿了所有可能的讹谬。万一你不幸漏掉了一个讹谬,并且公布了一个带有BUG的过程,你只能祷告它能蹒跚着一路和平了。有时乐观也是一件好事!
有时也会有这种情形揭示,你渴望在DEBUG版里偏重某条件为真,而有一些你又渴望不论是在DEBUG版里还是在RELEASE版里都渴望你所偏重条件的代码被编译到过程里去。这时即将用到VERIFY了。在DEBUG版里VERIFY能让你跳到编译器中条件不中意的事件那里。在RELEASE版里, VERIFY中的代码依旧被包括在可厉行文件中。示例来说:
VERIFY(MoveFile(szOriginalFilename, szNewFileName));
以上语句在DEBUG版里,万一MoveFile()函数失利则会发生一个调试ASSERT.无论抉择那种措施编译过程,MoveFile()函数的调用都会被包括到过程中。然而在RELEASE版里,调用失利会容易地被疏忽。与下面这条语句作个比拟:
ASSERT(MoveFile(szOriginalFilename, szNewFileName))www.c886.com;
在DEBUG版里,MoveFile()会被编译和厉行。而在RELEASE版里这一行就全面灭亡了,未曾任何文件试图被挪动。

在MFC中追寻ASSERT
万一你目前还在继续往下读,渴望从中打听ASSERT讹谬,比拟大的可能是你正在调试的ASSERT并不是来自你自己的代码。万一你够走运,它会来自MFC,因为你具有它的源代码。
必需记住的第一件事是它不太可能是由MFC中的BUG构成的。我不抵赖MFC中存在BUG,然而在我利用MFC的十多年中,我历来未曾遭到过一个ASSERT是由MFC的BUG构成的。
第二件必需记住的事是ASSERT的揭示定然有起因。你必需察看引发ASSERT讹谬的那一行代码,并且弄打听它在测验什么。
示例说来,相当一局部MFC类是环抱WINDOWS控件的。众多情形下,封装函数将一个SendMessage调用改换得看起来像等闲函数,以此来简化你的编码。例如,CTreeCtrl::SortChildren()函数,接受一个树形句柄,并对其子项举行分类。在你的代码中可能相仿下面的语句:
m_myTreeCtrl.SortChildren(hMyNode);
事实上,这个类向树形控件发送了一条消息。你只是容易的调用了一个函数,而MFC则翔实递交那条消息必需用到的参数。下面是MFC源码中重新型式过的函数代码:
_AFXCMN_INLINE BOOL CTreeCtrl::SortChildren(HTREEITEM hItem)
{
ASSERT(::IsWindow(m_hWnd));
return (BOOL)::SendMessage(m_hWnd, TVM_SORTCHILDREN,0, (LPARAM)hItem);
}
它做的第一件事是阐明你的CTreeCtrl对象中的窗口句柄是合法管用的!目前我真的不懂得万一你试图送一个TVM_SORTCHILDREN消息到一个不存在的窗口会有什么坏事情发生。我所懂得的是:在我正在试探那么做的时候,我渴望被告诉。万一我正在做一些未曾获胜渴望的事情,ASSERT在这里能够直接警?**摇?br /> 因而万一你在调用相仿那样的函数时遭到了ASSERT讹谬,你良美观看失利的那一行,并会觉察它正在偏重窗口句柄定然是一个存在90.cj90.com的窗口。那是它单一偏重的事情。万一它失利,单一的可能即便那个句柄窗口并不存在。那些即便你追寻讹谬的伏笔。
上面是一个简明的例子解释MFC是如何利用ASSERT的,以及你如何在你的工程中觉察MFC的ASSERT起因。目前让我们来看看如何在自己的代码中利用ASSERT。
void CMyClass::MyFunc(LPCTSTR szStringPtr)
还是来看这个函数,前?**姨岬接靡桓鋈菀椎牟炜蠢词顾萁坏闹刚敕强铡J率瞪衔颐悄芄槐壬厦孀龅酶谩FC和WINDOWS本身都供给了一串函数,我们能够利用它们来测定一个指针是否指向一段管用内存。
void CMyClass::MyFunc(LPCTSTR szStringPtr)
{
ASSERT(szStringPtr);
ASSERT(AfxIsValidString(szStringPtr));

if (*szStringPtr == ’7’)
DoSomething();
}
第一条语句仅仅捉拿一种讹谬,即是否空指针。加上第二条语句我们就能够测验指针是否指向管用内存。这个测验察看你是否具有读取该段内存的权限,并且该内存段包括字符串的告终符。一个相干的函数是AfxIsValidAddress,它察看你是否有权拜会前面调用中声明过大小的内存块。你也能够察看是否有这个块的读可能写权限。

其它的ASSERT察看
除非前面提到的两种察看,还可能用到察看一个递交的对象是否某个特定种类。万一你正在写一个处理CEmployee对象和CProduct对象的过程,显明他们不能互换。因而必需确认处理CEmployee对象的函数只接受相应种类的对象。在MFC中你能够像下面这么做:
void CMyClass::AnotherFunc(CEmployee *pObj)
{
ASSERT(pObj); //it can’t be a NULL
ASSERT_KINDOF(CEmployee, pObj);
}
像前面一样,我们率先确认这个指针不是空的。然后我们察看这个对象指针的种类是不是CEmployee型。只有对于从CObject派生的类能力这么处理,并且必需增加runtime扶持。走运的是增加runtime扶持真的无足轻重。
你定然曾经声明这个对象起码是dynamic。解释一下,在MFC中你能够声明一个类包括runtime类消息。你能够穿越在类声明中包括 DECLARE_DYNAMIC(ClassName)宏和在厉行处包括IMPLEMENT_DYNAMIC(ClassName, BaseClassName)来告终这一垄断。
class CMyReallyTrivialClass : public CObject
{
DECLARE_DYNAMIC(CMyReallyTrivialClass)
public:

// Various class members and functions...
};
and the implementation file
IMPLEMENT_DYNAMIC(CMyReallyTrivialClass,CObject);
.
.
.
// Other class functions...

万一你只是想利用ASSERT_KINDOF宏,这两行曾经足够了。目前,当你写过程时,你能够在任何地方利用ASSERT_KINDOF宏来察看一个递交给你的对象指针是否你要的种类。万一不是,你的过程将如前所述以一种受控的措施分化,并收到调试器给的一个ASSERT失利。
万一你的对象曾经包括DECLARE_DYNCREATE宏可能DECLARE_SERIAL宏,你就无须要利用DECLARE_DYNAMIC了,因为这些宏包括了ASSERT_KINDOF宏必需的runtime类消息。

结论
以上阐述了如何用ASSERT捉拿runtime讹谬并将你带到调试器中导致ASSERT讹谬的行。我们看到了如何追溯ASSERT失利起因的措施。沿着这个思路,我们学会了如何测验我们自己的代码中指向内存的指针的管用性;如何察看我们获得的对象指针是否是代码必需的。
最近几年,我始终在我的代码中利用ASSERT作为runtime察看器,感受补益匪浅。我很少遭到必需处原因空指针可能讹谬种类的对象指针所构成的过程分化。private Dimension d = new Dimension (0, 0);   public Example (){ }

目录的创立与剔除号召.

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多