VC调试总结 zz http://www.cppblog.com/kevinlynx/archive/2008/04/24/47998.html 博客。
2010-05-05 14:25
585 查看
很早前就想写点总结将编程中遇到的各种错误刨根挖底地罗列出来。但是因为这些错误(VC中开调试器遇到的各种错误对话框)都是随机性的,真正想总结的时候又不想不起来有哪些错误。恰好最近运气比较背,各种错误都被我遇遍了,于是恰好有机会做个总结。
这里所说的VC下的错误对话框时指在VC中开调试器运行程序时,IDE弹出的对话框。
1.不是错误的错误:断言 .
将断言视为错误其实有点可笑,但是因为有些同学甚至不知道这个,所以我稍微提一下。断言对话框大致上类似于:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/assert_thumb_1.jpg)
断言对话框是由assert引起的,在对话框上通常会给出表达式,例如assert( 0 ); 弹出对话框时就会将0这个表达式显示出来(Expression:0)。关于assert的具体信息建议自己google。这里稍微提一下一个技巧:有时候为了让assert提供更多的信息,我们可以这样写一个assert:
assert( expression && "Function : invalid argument!" );
因为字符串被用在布尔表达式中时,始终为true,不会妨碍对expression的判断,当断言发生时(expression为false) 时,断言对话框上就会显示这个字符串,从而方便我们调试。
要解决这个问题,首先要确定断言发生的位置,如果是你自己设置的断言被引发,就很好解决,如果是系统内部的函数产生的,那么一般是因为你传入的函数参数无效引起。
2.内存相关:最简单的非法访问:
C、C++程序中经常误用无效的指针,从而大致各种各样的非法内存访问(写/读)。最简单的情况类似于:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/wrongaccess_thumb.jpg)
这样的情况由类似以下代码引起:
char *p = 0;
*p = 'a';
当你看到类似于“写入位置XXXX时发生访问冲突“时,那么你大致可以断定,你的程序在某个地方访问到非法内存。开调试器对调用堆栈进行跟踪即可找出错误。
3.内存相关:不小心的栈上数组越界:
当你写下类似以下的代码时:
char str[3];
strcpy( str, "abc" );
就将看到如下的对话框:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/stackerror_thumb.jpg)
对话框大致的意思就是说str周围的栈被破坏了,因为str本身就被放在栈上,所以strcpy(str,"abc")多写入的'/0'就写到非法的栈区域。看到这样的对话框可以根据调用堆栈定位到错误发生的函数,然后检查此函数内部定义的数组访问,即可解决问题。
4.内存相关:不小心的堆上数组越界:
并不是每次数组越界都会得到上面所描述的错误,当数组是在堆上分配时,情况就变得隐秘得多:
char *str = new char [2];
strcpy( str, "ab" ); //执行到这里时并不见得会崩溃
delete [] str;//但是到这里时就肯定会崩溃
以上代码导致的错误对话框还要诡异些:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/heaperror_thumb.jpg)
似乎不同的DAMAGE对应的错误号(这里是47)都不一样,因为这里的错误发生在delete,而delete跟new很可能在不同的地方,所以这个错误调试起来不是那么容易,很多时候只能靠经验。
当看到类似的对话框时,根据调用堆栈跟到delete时,你就可以大致怀疑堆上数组越界。
5.调用相关:函数调用约定带来的错误:
这是所有我这里描述的错误中最诡异的一种,先看下对话框大致的样子:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/run_functioncall2_thumb.jpg)
对话框大致的意思就是说(没开调试器时对话框样式可能不一样),通过函数指针调用某个函数时,函数指针的类型(函数原型)可能与函数指针指向的函数的类型不一样。这里的类型不一致主要是调用约定(call conversation)不一样。如果函数类型(参数个数,返回值)不一样,一般不会出错。
调用约定是指调用一个函数时,函数参数的压入顺序、谁来清理栈的内容等。例如默认的C、C++调用约定__cdecl,对于函数的参数是从右往左压入。而__stdcall(WIN API的调用约定)则是从左向右压。我这里所说的函数类型不一样,就是指一个函数是使用__cdecl,还是__stdcall。例如以下代码:
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
#include <iostream>
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
void __stdcall show( const char *str )
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cppblog.com/Images/dot.gif)
{
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
}
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
void __stdcall show2()
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cppblog.com/Images/dot.gif)
{
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
}
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
int main()
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cppblog.com/Images/dot.gif)
{
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
typedef void (*Func)( const char *);
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
void *p = show;
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
Func my_func = (Func) p;
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
my_func( "kevin" );
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
return 0;
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
}
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
因为Func默认地被处理为__cdecl,而show是__stdcall的,所以当通过函数指针my_func时,就导致了以上对话框的出现。但是当p指向show2时,又不会出错,这是因为show2没有参数,不同的调用约定不影响这个规则。
6.异常相关:默认终止程序
当我们使用C++库时,因为库本身可能会抛出C++异常,如果你不捕获这个异常,那么C++默认就会调用abort(或者exit)函数终止程序。例如:
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
void test()
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cppblog.com/Images/dot.gif)
{
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
throw std::exception( "some exceptions" );
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
}
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
当你调用test函数时,如果不catch这个异常,开调试器就会得到类似的错误对话框:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/exception.jpg)
而如果不开调试器,则会得到:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/abort.jpg)
当你看到类似于“This application has requested the Runtime to terminate it…”之类的字眼时,那就表明程序调用了abort(或exit)函数,导致程序异常终止。其实这个错误只要开调试器,一般可以准确定位错误的发生点。
7.VC运行时检查-未初始化变量
VC的调试器会对代码进行运行时检查,这可能会导致VC弹出对你看上去正确的代码。这也许不是一个错误。例如:
int test_var;
if( test_var == -1 )
{
test_var = 0;
}
test_var没有初始化就进行if判断,当运行以上代码开调试器时,就会得到如下对话框:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/withoutinit_thumb.jpg)
8.破坏的堆
VC对于在堆上分配的内存都做了记录,我想这主要用于free释放内存时做归还处理。
char *p = (char*) malloc( 100 );
p += 10;
free( p );
当执行以上代码时,因为p的值已经改变,提交到free的指针值变化,VC就会给出以下错误提示:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/bad_heap_thumb.jpg)
posted on 2008-04-24 13:43 Kevin Lynx 阅读(3590) 评论(9) 编辑 收藏 引用 所属分类: C/C++ 、通用编程
![](http://www.cppblog.com/kevinlynx/aggbug/47998.html?webview=1)
全遭遇过了
不错的总结
高手一个啊! 回复 更多评论
不一定是调用约定错误,数组访问越界覆盖掉vc设置的cookie,也可能是这样的提示 回复 更多评论
例如?举个例子代码看看?(从对话框给的内容来看,似乎就是因为函数调用错误) 回复 更多评论
回复 更多评论
# re: 调试经验总结-VC下的错误对话框 2008-04-24 17:19 王晓轩 回复 更多评论 # re: 调试经验总结-VC下的错误对话框 2008-04-24 18:28 mm 回复 更多评论
这里所说的VC下的错误对话框时指在VC中开调试器运行程序时,IDE弹出的对话框。
1.不是错误的错误:断言 .
将断言视为错误其实有点可笑,但是因为有些同学甚至不知道这个,所以我稍微提一下。断言对话框大致上类似于:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/assert_thumb_1.jpg)
断言对话框是由assert引起的,在对话框上通常会给出表达式,例如assert( 0 ); 弹出对话框时就会将0这个表达式显示出来(Expression:0)。关于assert的具体信息建议自己google。这里稍微提一下一个技巧:有时候为了让assert提供更多的信息,我们可以这样写一个assert:
assert( expression && "Function : invalid argument!" );
因为字符串被用在布尔表达式中时,始终为true,不会妨碍对expression的判断,当断言发生时(expression为false) 时,断言对话框上就会显示这个字符串,从而方便我们调试。
要解决这个问题,首先要确定断言发生的位置,如果是你自己设置的断言被引发,就很好解决,如果是系统内部的函数产生的,那么一般是因为你传入的函数参数无效引起。
2.内存相关:最简单的非法访问:
C、C++程序中经常误用无效的指针,从而大致各种各样的非法内存访问(写/读)。最简单的情况类似于:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/wrongaccess_thumb.jpg)
这样的情况由类似以下代码引起:
char *p = 0;
*p = 'a';
当你看到类似于“写入位置XXXX时发生访问冲突“时,那么你大致可以断定,你的程序在某个地方访问到非法内存。开调试器对调用堆栈进行跟踪即可找出错误。
3.内存相关:不小心的栈上数组越界:
当你写下类似以下的代码时:
char str[3];
strcpy( str, "abc" );
就将看到如下的对话框:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/stackerror_thumb.jpg)
对话框大致的意思就是说str周围的栈被破坏了,因为str本身就被放在栈上,所以strcpy(str,"abc")多写入的'/0'就写到非法的栈区域。看到这样的对话框可以根据调用堆栈定位到错误发生的函数,然后检查此函数内部定义的数组访问,即可解决问题。
4.内存相关:不小心的堆上数组越界:
并不是每次数组越界都会得到上面所描述的错误,当数组是在堆上分配时,情况就变得隐秘得多:
char *str = new char [2];
strcpy( str, "ab" ); //执行到这里时并不见得会崩溃
delete [] str;//但是到这里时就肯定会崩溃
以上代码导致的错误对话框还要诡异些:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/heaperror_thumb.jpg)
似乎不同的DAMAGE对应的错误号(这里是47)都不一样,因为这里的错误发生在delete,而delete跟new很可能在不同的地方,所以这个错误调试起来不是那么容易,很多时候只能靠经验。
当看到类似的对话框时,根据调用堆栈跟到delete时,你就可以大致怀疑堆上数组越界。
5.调用相关:函数调用约定带来的错误:
这是所有我这里描述的错误中最诡异的一种,先看下对话框大致的样子:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/run_functioncall2_thumb.jpg)
对话框大致的意思就是说(没开调试器时对话框样式可能不一样),通过函数指针调用某个函数时,函数指针的类型(函数原型)可能与函数指针指向的函数的类型不一样。这里的类型不一致主要是调用约定(call conversation)不一样。如果函数类型(参数个数,返回值)不一样,一般不会出错。
调用约定是指调用一个函数时,函数参数的压入顺序、谁来清理栈的内容等。例如默认的C、C++调用约定__cdecl,对于函数的参数是从右往左压入。而__stdcall(WIN API的调用约定)则是从左向右压。我这里所说的函数类型不一样,就是指一个函数是使用__cdecl,还是__stdcall。例如以下代码:
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
#include <iostream>
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
void __stdcall show( const char *str )
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cppblog.com/Images/dot.gif)
{
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
}
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
void __stdcall show2()
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cppblog.com/Images/dot.gif)
{
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
}
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
int main()
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cppblog.com/Images/dot.gif)
{
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
typedef void (*Func)( const char *);
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
void *p = show;
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
Func my_func = (Func) p;
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
my_func( "kevin" );
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
return 0;
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
}
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
因为Func默认地被处理为__cdecl,而show是__stdcall的,所以当通过函数指针my_func时,就导致了以上对话框的出现。但是当p指向show2时,又不会出错,这是因为show2没有参数,不同的调用约定不影响这个规则。
6.异常相关:默认终止程序
当我们使用C++库时,因为库本身可能会抛出C++异常,如果你不捕获这个异常,那么C++默认就会调用abort(或者exit)函数终止程序。例如:
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
void test()
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockStart.gif)
![](http://www.cppblog.com/Images/OutliningIndicators/ContractedBlock.gif)
![](http://www.cppblog.com/Images/dot.gif)
{
![](http://www.cppblog.com/Images/OutliningIndicators/InBlock.gif)
throw std::exception( "some exceptions" );
![](http://www.cppblog.com/Images/OutliningIndicators/ExpandedBlockEnd.gif)
}
![](http://www.cppblog.com/Images/OutliningIndicators/None.gif)
当你调用test函数时,如果不catch这个异常,开调试器就会得到类似的错误对话框:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/exception.jpg)
而如果不开调试器,则会得到:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/abort.jpg)
当你看到类似于“This application has requested the Runtime to terminate it…”之类的字眼时,那就表明程序调用了abort(或exit)函数,导致程序异常终止。其实这个错误只要开调试器,一般可以准确定位错误的发生点。
7.VC运行时检查-未初始化变量
VC的调试器会对代码进行运行时检查,这可能会导致VC弹出对你看上去正确的代码。这也许不是一个错误。例如:
int test_var;
if( test_var == -1 )
{
test_var = 0;
}
test_var没有初始化就进行if判断,当运行以上代码开调试器时,就会得到如下对话框:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/withoutinit_thumb.jpg)
8.破坏的堆
VC对于在堆上分配的内存都做了记录,我想这主要用于free释放内存时做归还处理。
char *p = (char*) malloc( 100 );
p += 10;
free( p );
当执行以上代码时,因为p的值已经改变,提交到free的指针值变化,VC就会给出以下错误提示:
![](http://www.cppblog.com/images/cppblog_com/kevinlynx/WindowsLiveWriter/VC_BA3E/bad_heap_thumb.jpg)
posted on 2008-04-24 13:43 Kevin Lynx 阅读(3590) 评论(9) 编辑 收藏 引用 所属分类: C/C++ 、通用编程
评论
很好,不过好多都没有遇到过全遭遇过了
不错的总结
# re: 调试经验总结-VC下的错误对话框 2008-04-25 11:38 梦在天涯
chao hao !高手一个啊! 回复 更多评论
# re: 调试经验总结-VC下的错误对话框 2008-04-26 22:28 yafare
调用相关:函数调用约定带来的错误不一定是调用约定错误,数组访问越界覆盖掉vc设置的cookie,也可能是这样的提示 回复 更多评论
# re: 调试经验总结-VC下的错误对话框 2008-04-28 09:15 Kevin Lynx
@yafare例如?举个例子代码看看?(从对话框给的内容来看,似乎就是因为函数调用错误) 回复 更多评论
# re: 调试经验总结-VC下的错误对话框 2008-04-28 22:00 草上飞
呵呵我刚才在Matlab中也出现了这样的问题,想一想一小时前我还使用的好好的啊!仔细想想和操作系统相关的就是我把桌面主题给换了,隐约感觉到是这里的问题,于是我就把主题改为window XP,果然问题解决了,matlab可以正常启动了!回复 更多评论
# re: 调试经验总结-VC下的错误对话框 2008-04-29 22:22 李锦俊
这些都经常遇到,还有一些很难遇到,更隐秘的错误才会导致的出错对话框。 回复 更多评论# re: 调试经验总结-VC下的错误对话框(陆续更新5.5.2008) 2008-05-09 08:59 joke
顶 回复 更多评论# re: 调试经验总结-VC下的错误对话框(陆续更新6.12.2008) 2008-07-04 07:30 路缘
谢谢楼主分享,楼主真是有心人。 回复 更多评论# re: 调试经验总结-VC下的错误对话框 2008-04-24 17:19 王晓轩 回复 更多评论 # re: 调试经验总结-VC下的错误对话框 2008-04-24 18:28 mm 回复 更多评论
相关文章推荐
- tinyxml使用笔记与总结 http://www.cppblog.com/elva/archive/2008/04/24/47907.html
- 工欲善其事,必先利其器——VC2005的常用快捷键(来自http://www.cppblog.com/corelito/archive/2008/10/17/64233.html)
- GDB 单步调试 http://www.cppblog.com/baby-fly/archive/2010/07/27/121395.html
- 模版详解(模版与宏) 转自:http://www.cppblog.com/zmllegtui/archive/2008/10/28/65316.html
- http://www.cppblog.com/flyinghearts/archive/2012/03/18/168270.html
- 如何检查C++中的内存泄漏 (原文地址:http://www.cppblog.com/Lyt/archive/2009/03/22/77517.html)
- 容斥原理(转载http://www.cppblog.com/vici/archive/2011/09/05/155103.html)
- Bellman-Ford算法(转自http://www.cppblog.com/infinity/archive/2008/11/11/66621.html)
- NLP常用工具 from:http://www.cppblog.com/baby-fly/archive/2010/10/08/129003.html
- 转换汉字为unicode形式的字符串和转换unicode形式字符串转换成汉字__http://www.cppblog.com/biao/archive/2010/12/21/137087.html
- TThread 类 详细解析 --(原帖 华夏黑客同盟)http://www.cppblog.com/Khan/archive/2006/11/21/15503.html
- Windows Mobile 发送短信的问题(转自http://www.cppblog.com/SpringSnow/archive/2009/06/08/76107.html)
- windows核心编程--内存结构 http://www.cppblog.com/mzty/archive/2006/09/20/12764.html
- Linux 硬限制和软限制 http://www.cppblog.com/API/archive/2012/03/19/168289.html
- 二分查找学习札记转自http://www.cppblog.com/converse/archive/2009/10/05/97905.html
- http://www.cppblog.com/twzheng/archive/2008/07/07/55563.html
- LIB和DLL的区别、建立及使用http://www.cppblog.com/amazon/archive/2009/09/04/95318.html
- http://www.cnitblog.com/seeyeah/archive/2009/03/15/55440.html
- NDbUnit学习总结(转自:http://www.cnblogs.com/icebutterfly/archive/2009/05/12/1454869.html)
- OpenGL学习总结(转自白了少年头http://www.cnblogs.com/yxnchinahlj/archive/2010/11/23/1885579.html)