调试经验总结-VC下的错误对话框

1.不是错误的错误:断言 .

将断言视为错误其实有点可笑,但是因为有些同学甚至不知道这个,所以我稍微提一下。断言对话框大致上类似于:

assert

断言对话框是由assert引起的,在对话框上通常会给出表达式,例如assert( 0 ); 弹出对话框时就会将0这个表达式显示出来(Expression:0)。关于assert的具体信息建议自己google。这里稍微提一下一个技巧:有时候为了让assert提供更多的信息,我们可以这样写一个assert:

assert( expression && "Function : invalid argument!" );

因为字符串被用在布尔表达式中时,始终为true,不会妨碍对expression的判断,当断言发生时(expression为false) 时,断言对话框上就会显示这个字符串,从而方便我们调试。

要解决这个问题,首先要确定断言发生的位置,如果是你自己设置的断言被引发,就很好解决,如果是系统内部的函数产生的,那么一般是因为你传入的函数参数无效引起。

 

2.内存相关:最简单的非法访问:

C、C++程序中经常误用无效的指针,从而大致各种各样的非法内存访问(写/读)。最简单的情况类似于:

wrongaccess

这样的情况由类似以下代码引起:

char *p = 0;

*p = 'a';

当你看到类似于“写入位置XXXX时发生访问冲突“时,那么你大致可以断定,你的程序在某个地方访问到非法内存。开调试器对调用堆栈进行跟踪即可找出错误。

 

3.内存相关:不小心的栈上数组越界:

当你写下类似以下的代码时:

char str[3];

strcpy( str, "abc" );

就将看到如下的对话框:

stackerror 

对话框大致的意思就是说str周围的栈被破坏了,因为str本身就被放在栈上,所以strcpy(str,"abc")多写入的'/0'就写到非法的栈区域。看到这样的对话框可以根据调用堆栈定位到错误发生的函数,然后检查此函数内部定义的数组访问,即可解决问题。

 

4.内存相关:不小心的堆上数组越界:
并不是每次数组越界都会得到上面所描述的错误,当数组是在堆上分配时,情况就变得隐秘得多:

char *str = new char [2];

strcpy( str, "ab" ); //执行到这里时并不见得会崩溃

delete [] str;//但是到这里时就肯定会崩溃

以上代码导致的错误对话框还要诡异些:

heaperror

似乎不同的DAMAGE对应的错误号(这里是47)都不一样,因为这里的错误发生在delete,而delete跟new很可能在不同的地方,所以这个错误调试起来不是那么容易,很多时候只能靠经验。

当看到类似的对话框时,根据调用堆栈跟到delete时,你就可以大致怀疑堆上数组越界。

 

5.调用相关:函数调用约定带来的错误:

这是所有我这里描述的错误中最诡异的一种,先看下对话框大致的样子:

run_functioncall2

对话框大致的意思就是说(没开调试器时对话框样式可能不一样),通过函数指针调用某个函数时,函数指针的类型(函数原型)可能与函数指针指向的函数的类型不一样。这里的类型不一致主要是调用约定(call conversation)不一样。如果函数类型(参数个数,返回值)不一样,一般不会出错。

调用约定是指调用一个函数时,函数参数的压入顺序、谁来清理栈的内容等。例如默认的C、C++调用约定__cdecl,对于函数的参数是从右往左压入。而__stdcall(WIN API的调用约定)则是从左向右压。我这里所说的函数类型不一样,就是指一个函数是使用__cdecl,还是__stdcall。例如以下代码:

 

#include < iostream >  

void __stdcall show( const   char   * str )

{

}
 

void __stdcall show2()

{

}
 

int main()

{

typedef
void (*Func)( const char *);

void *p = show;

Func my_func
= (Func) p;

my_func(
"kevin" );

return 0;

}
 

 

因为Func默认地被处理为__cdecl,而show是__stdcall的,所以当通过函数指针my_func时,就导致了以上对话框的出现。但是当p指向show2时,又不会出错,这是因为show2没有参数,不同的调用约定不影响这个规则。

6.异常相关:默认终止程序

当我们使用C++库时,因为库本身可能会抛出C++异常,如果你不捕获这个异常,那么C++默认就会调用abort(或者exit)函数终止程序。例如:

  

void test()
{
   
throw std::exception( "some exceptions" );
}

 

当你调用test函数时,如果不catch这个异常,开调试器就会得到类似的错误对话框:

 

而如果不开调试器,则会得到:

 

当你看到类似于“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判断,当运行以上代码开调试器时,就会得到如下对话框:

withoutinit

8.破坏的堆

VC对于在堆上分配的内存都做了记录,我想这主要用于free释放内存时做归还处理。

char *p = (char*) malloc( 100 );
p += 10;
free( p );

当执行以上代码时,因为p的值已经改变,提交到free的指针值变化,VC就会给出以下错误提示:

bad_heap

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值