返回首页

{A}简介
这篇文章是"第二版",取代旧的,从以前的一些要求,并完善某些设计方面。
最初的想法,而写一个小程序,一定的数学计算,不想按预期方式工作。有必要在某些执行点跟踪的一些值,通常跟踪执行流。
在控制台应用程序,这是典型的做法与std::法院,但是这可能不是一个很好的选择,如果你想保留调试肮脏的控制台,或者您使用的是Windows应用程序,没有控制台。
历久弥新的C运行时LT; crtdbg.hgt;头,提供了一些类似的printf名为_RPTx宏。但是,如果我们想要的东西,可以支持一个通用的C风格的界面,可能每一种类型的好,缺少的东西。
的Win32 OutputDebugString的帮助,但它仅仅是一个LPCTSTR输出功能。所以,这个想法是写一个STL流输出使用该API。目录{A7}{A8}{A9}位内流
最直观的方式来此解决方案是有一个重写一个类operatorlt; LT;,调用Win32 API的输入数据转换成一个字符串后,
事实上,这样做,将导致。重新发明的STL格式公用事业。
的STL流是在事实上共同的std全部重新诠释:basic_ostream基类,提供给它一个方便覆盖的std::basic_streambuff类。
流需要通过选定的区域设置的数据格式,提供公用事业而streambuff提供了空间流写。流也需要照顾对媒体输出(在这种情况下,调试器;更普遍,标准输出或一个打开的文件)。
因此,适当的方式来处理这个任务是提供一个覆盖冲洗调试输出到提供的缓冲区的内容basic_streambuff。{A10}安排
如果我们希望能够在一个纯粹的Win32环境下使用等功能,我们必须准备与TCHARs。的std::ostream的和wostream出于这个原因,是不适合的,因为他们是一个预定义的字符类型。
我们需要另一种类型定义。在GE_::DBG命名空间,我把一个ostream_t的typedef,走样basic_ostreamlt; TCHARgt;
然后一个内在的report_h命名空间是用来包含实现细节。在这里面,类的全局声明为成员ostream_t流对象和streambuff对象,其中streambuff是从std派生的类:basic_streambuflt; TCHARgt。
要提供跟踪缩进,深度成员也出席了会议。
不管的streambuff实施,全局的构造函数初始化流,通过streambuff对象的地址。此外,在全球功能提供了一个懒惰的静态全局实例的访问。
内的命名空间,GE_::DBG:出(外)返回引用静态的隐藏流,使其成为全球可用。{A11} streambuff实施
streambuff类派生自std::basic_sreambufflt; TCHARgt;和它的构造函数调用继承pubsetbuff,提供的TCHAR []缓冲区,保持一个字符作为监护人,作为一个字符串结束符。
这种实现的关键部件是setbuf,溢出和同步覆盖虚函数。
setbuf覆盖简单地调用基SETP功能来告诉它使用提供的缓冲区,由先达第二的最后一个字符(,从而留下的最后一个总是空)。
同步功能是所谓的缓冲区时,需要被刷新流。它发生时的std::冲洗或在std::endl机械臂遇到。它只是获取当前正在运行的PBASE()和PPTR()(流输出,其最后的输出字符开始),计算出字符串的大小,力量的一个过去的最后一个输出字符'\ 0',并调用OutputDebugString的,从而导致缓冲区中的内容打印出来。在此之后,它调用pbump(深圳)倒带的缓冲区,允许流覆盖它。
溢出功能被称为流发送到输出比缓冲区可以包含多个字符。它只是要求同步,从而刷新缓冲区,地方在刚刚清洁的缓冲区"待"字,并递增文字的位置。{A12}简单的样品
上面描述的代码是所有这样的snipset要求是什么

int n = 5;

double d = 6.75;

dbg::out() << "n = " << n << "; d = " << d << std::endl;


跟踪像这样的一行{C}{A13} RAII缩进
将DBG:跟踪类是RAII类(一类的析构函数"撤消"的构造做了什么资源),有助于跟踪函数调用的进入/退出。
它的名称和地址参数,并递增/建筑/销毁gl​​obals.depth成员递减。
它的构造将通过名称[地址]由GT之前,在深入广泛的领域的迹象。同样,析构函数写入LT的名称[地址],具有相同的缩进。
通用operatorlt; LT;流输出参数,前面一个缩进"。"这种"效果"还可以得到在DBG::dpeth机械臂,一个正常的"LT,LT"链内。
因此,此功能:
void function(int n)

{

    dbg::trace trc("function", function);

    trc << "hallo with n = " << n << std::endl;

    if(n) function(n-1);

}

如果N = 3的要求,将跟踪:
>function[xxxxxxxx]

 .hallo with n = 3

 >function[xxxxxxxx]

  .hallo with n = 2

  >function[xxxxxxxx]

   .hallo with n = 1

   >function[xxxxxxxx]

    .hallo with n = 0

   <function[xxxxxxxx]

  <function[xxxxxxxx]

 <function[xxxxxxxx]

<function[xxxxxxxx]

{A14} RAII对象跟踪
将DBG:轨道类是类似于在DBG:跟踪,但它使用的quot; C:"和"D:",而不是"长线"字符串"GT"的招牌,不递增/递减的深度(而连贯书面)。
,它可以被用来跟踪对象的创建/破坏,想在这snipset:
class myobject

{

private:

   dbg::track trk;

public:

   myobject() :trk("myobject", &trk) {}

};


与下面的代码:
int main()

{

    dbg::trace trc("main", 0);

    trc << "creating two objects on stack" << std::endl;

    myobject m1, m2;

    trc << "creating a static object" << std::endl;

    static myobject m3;

    trc << "Bye" << std::endl;

}

将跟踪
>main[00000000]

 .creating two object on stack

 C:myobject[xxxxxxxx]

 C:myobject[yyyyyyyy]

 .creating a static object

 C:myobject[zzzzzzzz]

 .Bye



 D:myobject[yyyyyyyy]

 D:myobject[xxxxxxxx]

<main[00000000]

D:myobject[zzzzzzzz]


(注意静态对象​​,主要返回后销毁)其他的东西
插入跟踪代码的问题之一是它消失从发行版本(避免跟踪字符串和可能是漫长的函数调用)。
在前面的文章中中,我建议使用"假流",其操作是"不作为"的实施,让编译器的优化过程,以消除假冒电话。
在这里,我使用的是更传统的方法。作为自己的参数的DBG宏定义,如果定义了_DEBUG符号,或为"无",如果没有定义这样一个符号。
这是可能放置在此snipset像调用:
void myfunc()

{

        DBG(dbg::trace trc("myfunc", myfunc);)

        ...

        DBG(trc << "some code" << a_variable << std::endl;)

        ...

}

或跟踪的对象中插入一些虚拟变量,如
class myclass

{

        DBG(dbg::trackedobj<myclass> trk;)

public:

        ...     

};
结论
我希望这个新版本可以比其前身更加灵活和实用。一些旧的文章(如stackval操盘"fetaures",不在座的,主要是因为他们没有如果使用RAII的包装要求。
在任何情况下,我放在旧的文章文字和嵌入式代码在这个阶段"的利益,谁愿意看到的历史。编译器的兼容性
这里给出的代码是编译在C 8.0(顺昌表达)。代码本身并不是特别明智的编译器,但不同的版本,编译器提供的STL可以赚取差价。
一个特定的情况下,使用Unicode是:当TCHAR ID定义为char(没有Unicode),为wchar_t *类型是由性病治疗:作为一个void *的ostream(打印地址的十六进制表示) 。
定义UNCODE时,在C 8 STL处理都作为字符串的char *和wcahr_t *由std::wostream。这允许你写C字符串(为const char []),即使在Unicode环境的调试字符串文字。
不幸的是,这是不guaranted始终工作:旧的C编译器提供的STL实现可能无法代表的char *"字符串"在std::wostream时,TCHAR是定义为wchar_t。德育:除非你知道你的工作正好与STL版本提供商,始终使用_T宏()。。 (正是我没有使用本文在我的示例snipsets。)

回答

评论会员:游客 时间:2011/12/06
,与一般的流问题是,虽然他们可能是线程安全转移不NBSP,即线程1...LT,LT;"JOOP"LT,LT,"皮特"LT,LT;STD::endl;线程2...LT,LT,"基斯"LT,LT"卡拉斯"LT,LT;STD::endl;NBSP日志;joopkeespiet卡拉斯解决方案可以转移首先在一个缓冲区后,写缓冲区1件。
马尔科姆Pirie的
评论会员:游客 时间:2011/12/06
...但是这也意味着重写的转变(即流的一部分,而我重写的缓冲,而不是转移,这也意味着数据呈现)。另一种方法,可以派遣不同的缓冲区,由线程ID映射(即:给每个线程一个独特的缓冲区)。,我可能会按照在下一个版本(完全多线程的支持)2错误。编译...65534错误。{S0}
wtwhite
评论会员:游客 时间:2011/12/06
。这篇文章,以纪念文章更新进一步读者,请注意以下职位:其中不少是没有更多的意义。的欢呼声。emilio_grv2错误。编译...65534错误。{S0}
wtwhite
评论会员:游客 时间:2011/12/06
大位的代码,但我有一个问题,得到它的工作在VisualStudio6。
wtwhite
评论会员:游客 时间:2011/12/06
我提出了其他意见的情侣在这里,但还没有提到我看来你的文章......{S2}我想栈测量仪,尤其是小说!一个小小的建议:我不知道为什么你追加的'\n'您收到的字符串-为什么不只是让呼叫者使用RPTLT,LT;LT,LT;的'\n',通常是做呢?这是因为中的OutputDebugString()的限制?(我不熟悉的功能)。虽然配售附加呼叫者结果稍微在每次调用现场的工作只是散发出的调试文本单行,(a)指输出线可建在几个典型案例中的'\n'的负担LT;operatorlt()调用,和(b)省却了4094字节的限制。只是一个念头。你有我5。添"
depaolim
评论会员:游客 时间:2011/12/06
操盘的ostream::endl()插入一个换行符到一个流,然后刷新-如果停止自动加上'\N'内同步(),你就可以取代:{BR}RPTLT,LT;的"foo"LT,LT;STD:冲洗;RPTLT,LT;的"foo"LT,LT;STD::endl;这是一样简洁的,仍然使您可以建立一个与多个操作作为before.nbsp单行;你认为添
wtwhite
评论会员:游客 时间:2011/12/06
我不能确定,如果STD::endl操纵者也导致一个刷新我发现不同的编译器的非相干的行为...,在这里,点是,如果执行的是一步一步调试,你遇到一个跟踪呼叫,您可能希望立即打印结果,因为它可能给你对你正在测试状态的信息。{BR}所以没有插入的\n,也许你应该做喜欢的东西RPTLT,LT;......性病::endlLT,LT;STD:冲洗;,为简便起见,我想避免。在任何情况下,无论你想在这个意义上说,以适应代码,你总是有冲洗时插入"\0',因为OutputDebugString的期望C字符串(不是原始的缓冲区)插入或不会自动\n是在这一点上更比"真正的"个人喜好的问题。2错误。编译...65534错误。{S0}
depaolim
评论会员:游客 时间:2011/12/06
我看,我认为,endl()承诺到永远冲洗后写的"\ñ',因为这是在{A15}说流]和也在微软的MSVC文档。这是一个耻辱,如果不同的编译器始终不实施这种行为。'\0'我只是标志着在C字符串的结束,而不是复制到输出。但我同意,为典型的使用,自动加上'\N'是有道理的。添
wtwhite
评论会员:游客 时间:2011/12/06
?是你确保磷堆栈基地好文章!
depaolim
评论会员:游客 时间:2011/12/06
P是用来存放我的第一次实例的地址;P的自己的地址在这里并不重要。
depaolim
评论会员:游客 时间:2011/12/06
但静态变量分配在一个特定的地区......而不是在栈至于我能记得
戈兰米特罗维奇
评论会员:游客 时间:2011/12/06
你右,静态数据分配到其他地方,但我们不**护理**P的自己的地址-只用一个指针变量包含我的地址,我****在堆栈上分配。请注意,我们没有在任何地方看到p的地址(即P)正在采取。只有I(我)的地址是
wtwhite
评论会员:游客 时间:2011/12/06
感谢您非常埃米利奥现在清透我没有放弃声明",如果P=I(P);"应有的重视感谢你呢!
戈兰米特罗维奇
评论会员:游客 时间:2011/12/06
因为p是静态的,它甚至不能在栈上。但它存在于程序的整个生命。这是第一次,你调用stackval,P是创建为null,因此,我的地址存储在P和仍然存在,直到程序运行。他们就差异PI,事实上,是当前我地址和i的第一个实例的地址之间的距离因此,它会告诉你有多深,是当前跟踪方面非常跟踪使用stackval2错误。编译...65534错误。{S0}
wtwhite
评论会员:游客 时间:2011/12/06
非常感谢你埃米利奥现在清透我没有放弃的声明"如果P=I(P);"应有的重视{S2}
戈兰米特罗维奇
评论会员:游客 时间:2011/12/06
我喜欢你的方法,但是,它有一个严重的问题。解决方案的基础上#define宏使您可以轻松地放弃所有的调试/跟踪发布版本中的信息。C对象,你不能这样做。当然,你可以转换你的方法,在"做什么"的,但是,你无法避免至少在没有短路的运营商可能的参数评估()...{BR}-戈兰
wtwhite
评论会员:游客 时间:2011/12/06
虽然它的可能,编译器的优化将不会完全删除了调试跟踪语句的代码,你应该写的语句,以便它有没有副作用反正(除了在跟踪实际印刷消息)。例如,您显然不希望这样的事情:TRACE(I);在您的代码,转折点,因为在这种情况下,调试关闭将改变i的值。只要你遵守这个重要的规则,无论调试跟踪代码不优化你的程序没有"明显的效果",比其他性能命中。但是从埃米利奥的个案研究它看起来像编译器中常见的情况还算不错。添
wtwhite
评论会员:游客 时间:2011/12/06
这里有两个很可能的情况下:1。读(挥发性)硬件寄存器2。否则lenghty非modifing如枚举窗口/进程/等操作,我相当频繁地使用他们都与我的经典跟踪实用程序(没有任何幻想)...{BR} -戈兰
罗宾
评论会员:游客 时间:2011/12/06
是的,我可以看到,有可能是一个严重的性能,在这样的情况下,有没有办法,编译器就可以知道,所谓的功能程序的状态没有影响击中。因此,我同意埃米利奥的技术不会在这些情况下的理想选择。虽然它不是作为清洁作为宏观免费的解决方案,我觉得计算留在打一个性能太多的情况下,我宁愿使用类似以下内容:的#ifdef_DEBUG#定义_D(X)X#ELSE#定义_D(X)#ENDIF_D(RPTLT,LT"结果="LT,LT;non_modifying_but_time_consuming_func()LT,LT;".");{BR}_D()基本上是一个完整的#ifdef语法糖...#endif块。如果我有一个,我想在调试过程中建立只执行几个语句块,我通常会附上他们明确地内的的#ifdef_DEBUG...#endif块,虽然_D()也可以用来包装多个语句(就看你是否把最后的分号内部或外部的右括号)添