`
923723914
  • 浏览: 636251 次
文章分类
社区版块
存档分类
最新评论

浅析ASSERT&TRACE宏

 
阅读更多
1.TRACE
1.1.TRACE的宏定义

同样的,我们先从TRACE的宏定义开始研究,TRACE被定义在AFX.H中。但是我在这个H文件查找时,并没有发现TRACE被#define成某个函数。虽然你会发现类似的下面两行代码:
#defineTRACE __noop
///////////////////////////////////
#defineTRACE ATLTRACE
但是,ATL的宏定义并不是我们要找的,而__noop,如果你翻过MSDN会发现这个Keyword的作用仅仅是忽略被包含的函数及不对参数进行评估(具体请看A附录)。

那么,TRACE到底是如何被使用的呢?机缘巧合之下(这个……),我在TRACE的宏定义附近发现了下面的代码:
inline voidAFX_CDECL AfxTrace(...) { }// Look at here!
#defineTRACE __noop
#defineTRACE0(sz)
#defineTRACE1(sz,p1)
#defineTRACE2(sz,p1,p2)
#defineTRACE3(sz,p1,p2,p3)
关注那个AfxTrace。如果说我们前面找到的都不是真正的TRACE的话,那么,这个AfxTrace就非常可能是我们要找的,而且,他还是个inline函数!于是,我以“AfxTrace”为关键字Google,果然找到了一些信息。

在以前的AFX.H文件中,存在类似下面的代码:
#ifdef _DEBUG
void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...);
#define TRACE ::AfxTrace
#else
#define
TRACE 1 ? (void)0 : ::AfxTrace
#endif
很明显,我们可以看到,TRACE被定义成了AfxTrace。但是这里有个问题,为什么在我的VC2003的AFX.H文件中,找不到这段代码呢?看来需要高人回答哈~

1.2.AfxTrace

既然TRACE宏只是调用了AfxTrace,那么我们就来看看AfxTrace函数实现了什么。不过很可惜,AfxTrace并不是一个文档记录函数(Documented-function),这意味着你在MSDN是找不到他的相关信息,那么我们只能通过他的源代码来了解他的行为。AfxTrace的源代码在DUMPOUT.CPP里。
voidAFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...)
{
va_list args;
va_start(args,lpszFormat);

intnBuf;
TCHAR szBuffer[512];

nBuf=_vsntprintf(szBuffer,_countof(szBuffer),lpszFormat,args);

// was there an error? was the expanded string too long?
ASSERT(nBuf>=0);

afxDump<<szBuffer;

va_end(args);
}
首先我们来看三个东西:
va_list args;
va_start(args,lpszFormat);
va_end(args);
这一组宏主要是用来解决函数的不定参数的。回想一下,我们可以在TRACE中输出任意个参数,靠的就是这三个东西。因为C没有重载函数,更何况即使有,当函数参数个数不确定时,重载的局限性就显得非常大,于是就有人想通过利用指针参数解决了这个问题。

由于这个东西比较复杂(MS足够再写一篇专门的文章了),所以在此不作详细阐述,有兴趣的可以参考下列URL:

1.MSDN:http://msdn.microsoft.com/en-us/library/kb57fad8.aspx
2.http://www.cppblog.com/qiujian5628/archive/2008/01/21/41562.html

接着我们可以看到,AfxTrace声明了大小为512的TCHAR数组作为缓冲区,然后用_vsntprintf往缓冲区里写已经格式化好的数据。

而_vsntprintf系列函数专门用于以va_list处理可变参数的函数输出。具体请参考附录

最后,AfxTrace又用afxDump对szBuffer进行转储(似乎就是输出到输出框)。看来,我们还需要对afxDump函数进行跟进。

1.3.afxDump

afxDump是一个CDumpContext类的预定义对象,用于在Debug模式下,往VC的输出窗口输出调试信息。很幸运,M$在MSDN中记录了他的一些信息。(具体请参考附录)

所以,一般的,当存在如下代码时:
LPCTSTR lpszKC=L"KC is a Fucker";
afxDump<<lpszKC;
输出框便会输出“KC is a fucker”。

我们现在来看看afxDump的定义代码。源代码在AFX.H中
#ifdef_DEBUG
externAFX_DATA CDumpContext afxDump;// Look At Here!
externAFX_DATA BOOL afxTraceEnabled;// 这个变量和afxTraceFlags同为调式输出的开关标志
// 不过MS在新版本的MFC中被废除了
#endif
然后我们再来看看CDumpContext辅助类的定义。源代码同样也在AFX.H中
classCDumpContext
{
public:
CDumpContext(CFile*pFile=NULL);

// Attributes
intGetDepth()const; // 0 => this object, 1 => children objects
voidSetDepth(intnNewDepth);

// Operations
CDumpContext&operator<<(LPCTSTR lpsz);
#ifdef_UNICODE
CDumpContext
&operator<<(LPCSTR lpsz);// automatically widened
#else
CDumpContext&operator<<(LPCWSTR lpsz);// automatically thinned
#endif
CDumpContext&operator<<(const void*lp);
CDumpContext&operator<<(constCObject*pOb);
CDumpContext&operator<<(constCObject&ob);
CDumpContext&operator<<(BYTE by);
CDumpContext&operator<<(WORD w);
CDumpContext&DumpAsHex(BYTE b);
CDumpContext&DumpAsHex(WORD w);
#ifdef_WIN64
CDumpContext
&operator<<(LONG l);
CDumpContext&operator<<(DWORD dw);
CDumpContext&operator<<(intn);
CDumpContext&operator<<(UINT u);
CDumpContext&DumpAsHex(LONG l);
CDumpContext&DumpAsHex(DWORD dw);
CDumpContext&DumpAsHex(intn);
CDumpContext&DumpAsHex(UINT u);
#else
CDumpContext&operator<<(LONG_PTR l);
CDumpContext&operator<<(DWORD_PTR dw);
CDumpContext&operator<<(INT_PTR n);
CDumpContext&operator<<(UINT_PTR u);
CDumpContext&DumpAsHex(LONG_PTR l);
CDumpContext&DumpAsHex(DWORD_PTR dw);
CDumpContext&DumpAsHex(INT_PTR n);
CDumpContext&DumpAsHex(UINT_PTR u);
#endif
CDumpContext&operator<<(floatf);
CDumpContext&operator<<(doubled);
CDumpContext&operator<<(LONGLONG n);
CDumpContext&operator<<(ULONGLONG n);
CDumpContext&DumpAsHex(LONGLONG n);
CDumpContext&DumpAsHex(ULONGLONG n);
CDumpContext&operator<<(HWND h);
CDumpContext&operator<<(HDC h);
CDumpContext&operator<<(HMENU h);
CDumpContext&operator<<(HACCEL h);
CDumpContext&operator<<(HFONT h);
voidHexDump(LPCTSTR lpszLine,BYTE*pby,intnBytes,intnWidth);
voidFlush();
// Implementation
protected:
// dump context objects cannot be copied or assigned
CDumpContext(constCDumpContext&dcSrc);
void operator=(constCDumpContext&dcSrc);
voidOutputString(LPCTSTR lpsz);
intm_nDepth;

public:
CFile*m_pFile;
};
CDumpContext只有一个构造函数,而且默认把m_pFile设置成了NULL,这点很关键,我们在后面马上会看到~

这里可能会有点疑问,为什么会存在一个CFile*类型的Public成员变量?我也不知道,KC个人的猜测是,CDumpContext不仅能够往输出框输出信息,应该还能够往外写文件。而下面的m_pFile->Write也能够支持我的猜测。

另一个亮点是,CDumpContext中存在多个<<重载运算符,这样便于afxDump进行不同类型的<<运算。不过这里有一个插曲,CDumpContext的上述代码中有几行比较有意思:
// Operations
CDumpContext&operator<<(LPCTSTR lpsz);
#ifdef_UNICODE
CDumpContext
&operator<<(LPCSTR lpsz);// automatically widened
#else
CDumpContext&operator<<(LPCWSTR lpsz);// automatically thinned
#endif
之前我一直不明白这段的用意,后来经D大提醒,幡然醒悟。

这段宏的作用大致是:在UNICODE下,遇到MBCS字符串自动做扩大处理;在MBCS下,遇到UNICODE字符串自动做缩小处理。相应的实现代码如下:
#ifdef_UNICODE
// special version for ANSI characters
CDumpContext&CDumpContext::operator<<(LPCSTR lpsz)
{
if(lpsz==NULL)
{
OutputString(L"(NULL)");
return*this;
}

// limited length
TCHAR szBuffer[512];
_mbstowcsz(szBuffer,lpsz,_countof(szBuffer));
szBuffer[511] =0;
return*this<<szBuffer;
}
#else //_UNICODE
// special version for WIDE characters
CDumpContext&CDumpContext::operator<<(LPCWSTR lpsz)
{
if(lpsz==NULL)
{
OutputString("(NULL)");
return*this;
}

// limited length
charszBuffer[512];
_wcstombsz(szBuffer,lpsz,_countof(szBuffer));
szBuffer[511] =0;
return*this<<szBuffer;
}
#endif//!_UNICODE

/////////////////////////////////////////////////////////////////////////////
接下来我们重点看CDumpContext对<<的实现。虽然<<的重载很多,但是从本质上,可以分成对String和数值类型的两类。那么我们先来看看对于数值类型的处理,额,随便挑一个~当~当~当~当~
CDumpContext&CDumpContext::operator<<(WORD w)
{
TCHAR szBuffer[32];

wsprintf(szBuffer,_T("%u"), (UINT)w);
OutputString(szBuffer);

return*this;
}
因为是数值类型,所以算上64Bit的大整数,也长不到哪里去。所以这里分配的缓冲区数组的下标只有32.

然后利用wsprintf把数字格式化,最后用OutputString输出。wsprintf详细信息请参考附录

至于对String的处理,代码如下:
CDumpContext&CDumpContext::operator<<(LPCTSTR lpsz)
{
if(lpsz==NULL)
{
OutputString(_T("NULL"));
return*this;
}

ASSERT(lpsz!=NULL);
if(lpsz==NULL)
AfxThrowUserException();

if(m_pFile==NULL)
{
TCHAR szBuffer[512];
LPTSTR lpBuf=szBuffer;
while(*lpsz!='\0')
{
if(lpBuf>szBuffer+_countof(szBuffer) -3)
{
*
lpBuf='\0';
OutputString(szBuffer);
lpBuf=szBuffer;
}
if(*lpsz=='\n')
*
lpBuf++ ='\r';
*
lpBuf++ = *lpsz++;
}
*
lpBuf='\0';
OutputString(szBuffer);
return*this;
}

m_pFile->Write(lpsz,lstrlen(lpsz)*sizeof(TCHAR));
return*this;
}
做<<前,先对参数进行合法性检查,然后在m_pFile为NULL的情况下,分配缓冲区(由于是字符串,所以下标为512),然后逐一的复制字符,最后相同的用OutputString转出。

比较上面两种<<的运算实现,我们可以很明显的看出,最后的数据都被传递到了OutputString里,所以我们还必须跟进OutputString。

1.4.OutputString

我们现在跳到OutputString的实现源代码上:
voidCDumpContext::OutputString(LPCTSTR lpsz)
{
// use C-runtime/OutputDebugString when m_pFile is NULL
if(m_pFile==NULL)
{
TRACE(traceDumpContext,0,lpsz);
return;
}
ASSERT(lpsz!=NULL);
if(lpsz==NULL)
AfxThrowUserException();
// otherwise, write the string to the file
m_pFile->Write(lpsz,lstrlen(lpsz)*sizeof(TCHAR));
}
因为前面说过,m_pFile的值为NULL(我们没有给他传值,构造函数又自动给他NULL掉了),所以OutputString应该会执行下面的代码:
// use C-runtime/OutputDebugString when m_pFile is NULL
if(m_pFile==NULL)
{
TRACE(traceDumpContext,0,lpsz);
return;
}
很奇怪,很神奇,很……囧……又回到了TRACE……

更何况,上面注释写着use C-runtime/OutputDebugString的字眼呢,多大个的字啊……

无奈中,我去翻了下MSDN,又去Google,结果得到了惊人的发现!在MSDN,对于CDumpContext有这么一段的描述:
Under the Windows environment,the output from the predefined afxDump object,conceptually similar to the cerr stream,is routed to the debugger via the Windows function OutputDebugString.
换句话说,转储的东西的的确确会经过底层的C运行时库函数或者OutputDebugString这个API。

此时我想起了之前出现的一个关于TRACE的BUG:在UNICODE下无法输出中文。当时我通过F9/F10/F11不断的跟进,但是单语句调试到TRACE(traceDumpContext, 0, lpsz)这里时,却提示没有可显示的语句。所以,有可能转储的东西跑到了某个C底层函数去(如果是OutputDebugString,中文也应输出)。

于是我把目光瞄准了traceDumpContext,发现这个是个宏(很奇怪,是ATL系列的),经过多次进进出出的跟进后,发现了一个叫做CTrace的类,而且在里面还发现如下代码:
classCTrace
{
public:
typedef int(__cdecl*fnCrtDbgReport_t)(int,const char*,int,const char*,const char*,...);

private:
CTrace(
#ifdef_ATL_NO_DEBUG_CRT
fnCrtDbgReport_t pfnCrtDbgReport
=NULL)
#else
fnCrtDbgReport_t pfnCrtDbgReport=_CrtDbgReport)
#endif
我很敏感的关注了_CtrDbgReport这个函数,去MSDN翻了下,得到的结果很惊人!
Generates a report with a debugging messageandsends the report to three possible destinations(debug version only).
而且,Remark上还有这么一段(具体请参考附录):
In Visual C++2005,_CrtDbgReportW is the wide-character version of _CrtDbgReport.All its outputandstring parameters are in wide-character strings;otherwise it is identical to the single-byte character version.

_CrtDbgReportand_CrtDbgReportW create the user messageforthe debug report by substituting the argument[n]arguments into the format string,usingthe same rules defined by the printforwprintf functions.These functions then generate the debug reportanddetermine the destinationordestinations,based on the current report modesandfile definedforreportType.When the report is sent to a debug message window,the filename,lineNumber,andmoduleName are included in the information displayed in the window.
所以,我最后在假设的情况下得出了下列结论:

OutputString通过宏定义,最终转到了_CtrDbgReport这个C-Runtime函数上,有他负责在底层的某个地方往输出框写调试信息。

又因为我的IDE是2003,所以_CtrDbgReport只能输出Single-byte Char,所以无法输出中文。

如此,我们的浅析TRACE的部分应该算是圆满结束了-。-||

2.ASSERT

看完了TRACE,我们再来看看ASSERT。相对来说,ASSERT就比TRACE简单很多,所以我们不再分布叙述~

老规矩,还是先从宏定义开始入手。源代码在AFX.H中
#defineASSERT(f) (void) ((f) || !AfxAssertFailedLine(THIS_FILE,__LINE__) || (AfxDebugBreak(),0))
比较简单,只有一行。不过不得不说的是,这句写得非常巧妙。你还记得||的运算顺序么?

当f为FALSE的时候,会跳转到AfxAssertFailedLine,这个函数的源代码在AFXASERT.CPP中
BOOL AFXAPI AfxAssertFailedLine(LPCSTR lpszFileName,intnLine)
{
#ifndef_AFX_NO_DEBUG_CRT
// we remove WM_QUIT because if it is in the queue then the message box
// won't display
MSG msg;
BOOL bQuit=PeekMessage(&msg,NULL,WM_QUIT,WM_QUIT,PM_REMOVE);
BOOL bResult=_CrtDbgReport(_CRT_ASSERT,lpszFileName,nLine,NULL,NULL);
if(bQuit)
PostQuitMessage((int)msg.wParam);
returnbResult;
#else
// Not supported.
#error_AFX_NO_DEBUG_CRT isnotsupported.
#endif// _AFX_NO_DEBUG_CRT
}
首先,AfxAssertFailedLine会阻塞所有活动的线程,然后显示我们非常熟悉也非常不愿意见到的消息框-_-!。这里也有一个_CtrDbgReport,个人猜测,那个消息框可能就是通过这个C-Runtime函数产生的。

这个函数成功后会返回TRUE,经过!运算后就变成了FALSE,于是继续执行AfxDebugBreak。

AfxDebugBreak是一个系列宏定义,源代码在AFXVER_.H中
#ifndefAfxDebugBreak
#ifdef_AFX_NO_DEBUG_CRT
// by default, debug break is asm int 3, or a call to DebugBreak, or nothing
#ifdefined(_M_IX86) && !defined(_AFX_PORTABLE)
#defineAfxDebugBreak()_asm{int3}
#else
#define
AfxDebugBreak()DebugBreak()
#endif
#else
#define
AfxDebugBreak()_CrtDbgBreak()
#endif
#endif

#ifndef
_DEBUG
#ifdefAfxDebugBreak
#undefAfxDebugBreak
#endif
#define
AfxDebugBreak()
#endif// _DEBUG
根据不同的环境使用不同的方式。不过他的表现行为还都是类似的,主要是把控制返回给VC的调试器,这样一来,你就可以交互式的进行调式。

3.尾声

好了,终于可以说End Up了~这个算是KC放假后做的第一个Project吧,虽然不具有实际价值-_-!。其实我很早之前就像研究这两个宏了,只是当时没有时间。

之前被TRACE在UNICODE下无法输出中文的问题困扰了好久,一直说要研究SRC弄清楚原因,现在总算可以给自己交上一份答卷了~HOHO~

分享到:
评论

相关推荐

    assert()宏的用法

    assert()宏是用于保证满足某个特定条件。 用法是: assert(表达式); 如果表达式的值为假,整个程序将退出,并输出一条错误信息。如果表达式的值为真则继续执行后面的语句。 使用这个宏前需要包含头文件assert.h ...

    assert,assert_valid,verify,trace用法

    对于开始学vc的人,对于assert,assert_valid,verify,trace的宏感到很奇怪,总是觉得很难掌握似的,其实这些主要是没有理清楚他们各自宏之间深层次的意义。

    vc中ASSERT()和VERIFY()区别

    ASSERT与VERIFY宏在Debug模式下作用基本一致,二者都对表达式的值进行计算,如果值为非0,则什么事也不做;如果值为0,则输出诊断信息。 ASSERT与VERIFY宏在Release模式下效果完全不一样。ASSERT不计算表达式的值,...

    C语言头文件 ASSERT

    C语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC语言头文件 ASSERTC...

    debug_assert, 简单灵活和模块化断言宏.zip

    debug_assert, 简单灵活和模块化断言宏 debug_assert debug_assert是只提供一个非常灵活的DEBUG_ASSERT() 宏的简单。C 11.头库。 你自己写了多少次断言宏,因为 assert() 是全局控制的,不能在程序的某些部分启用? ...

    assert_matches:提供宏`assert_matches`来测试模式匹配

    assert_matches 提供一个assert_matches宏,它测试值是否与给定的模式匹配,如果匹配失败,则会引起恐慌。#[macro_use] extern crate assert_matches;#[derive(Debug)]enum Foo { A ( i32 ), B ( i32 ),}let a = Foo...

    release 下trace工具

    说明:ASSERT宏在发行版本中不起作用,而应该用VERIFY来进行发行版的调试。如果发行版本运行有问题,可以先禁止所有代码优化再进行调试。 和TRACE一样用XTRACE 如果在发行程序时,想去掉这些附加的代码,无须...

    assert_ng:用于Rust的改进的断言宏

    这为Rust编程语言提供了一个改进的assert宏,从而无需使用assert_eq! 同时还提供!= , &gt;等的等效项。 基本思想是,如果为宏提供了格式为a == b的条件,它将执行assert_eq! 会执行,即,如果断言失败,则打印出a和b...

    assert:将Node.js assert.js移植到浏览器

    assert.js 最新兼容版本: assert.js是浏览器的Node.js标准声明库的端口。 原始代码和测试来自Node.js,并已被修改为与浏览器兼容。 例如,您可以将其与一起使用,以同构方式(在服务器和客户端上)执行测试。 ...

    Visual assert

    Visual Assert is a Visual Studio® AddIn that allows you to easily write, manage, run, and debug your C/C++ unit tests – without ever leaving the Visual Studio® IDE. No fiddling with command line ...

    Android读取Assert资源目录下数据库,数据库操作

    Android读取Assert资源目录下数据库,数据库操作; Android读取Assert资源目录下数据库,数据库操作 Android读取Assert资源目录下数据库,数据库操作

    VC中TRACE ASSERT VERIFY之用法.doc

    VC++调试方面的资料,希望对大家有用; 彻底共享,决不要分!

    浅析C语言中assert的用法

    assert宏的原型定义在&lt;assert&gt;中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:#include &lt;assert&gt;void assert( int expression );assert的作用是现计算表达式 expression ,如果其值为假(即为0),...

    assert2-rs:由Catch2启发的Rust的assert!()和check!()宏

    不再需要assert_eq或assert_ne ,只需编写assert!(1 + 1 == 2) ,甚至assert_ne assert!(1 + 1 == 2) assert!(1 + 1 &gt; 1) ! 您可以测试模式匹配: assert!(let Err(_) = File::open("/non/existing/file")) 。 ...

    assert.h头文件

    assert.h头文件下载

    Assert.java

    适用于Java大部分非空判断,有效的减少空指针问题,直接调用Assert 里面的方法就行,非常方便,你值得拥有

    debug_assert:简单,灵活和模块化的声明宏

    debug_assert是一个简单的C ++ 11,仅标头的库,它提供了非常灵活的DEBUG_ASSERT()宏。 您自己写了一个断言宏多少次,因为assert()是全局控制的,并且不能仅对程序的某些部分启用? 该库通过提供灵活的模块化声明宏...

    VC中如何使用ASSERT断言

    VC中如何使用ASSERT断言 VC中如何使用ASSERT断言

    assert函数

    介绍assert函数的具体用法 方便初学者学习

Global site tag (gtag.js) - Google Analytics