首页 > 代码库 > VC程序调试的方法
VC程序调试的方法
TRACE 使用方法与printf完全一致,在output框中输出调试信息
ASSERT 接收一个表达式,如果表达式为true,则无动作,否则终端当前程序执行,对于系统中出现这个宏导致的中断,应该认为函数调用未能满足系统的调用此函数的前提条件,例如,对于一个没有创建的窗口调用setwindowtext
VERIFY和ASSERT功能类似,所不同的是,在Release版本中,ASSERT不计算表达式的值,而VERIFY计算表达式的值
几个关于C/C++调试用到的函数
1调用栈系列
下面是函数原型
#include "execinfo .h"int backtrace(void **buffer, int size);char **backtrace_symbols(void *const *buffer, int size);void backtrace_symbols_fd(void *const *buffer, int size, int fd);
(1)backtrace用来获取当前程序的调用栈,把结果存在buffer中。通常,我们用gdb调试程序,设置合适的断点,停下来之后,用backtrce命令,就可以看到当前的调用栈,但是有时候,用到条件断点的时候。gdb的功能就没有程序本身的功能强大了,这个时候就可以考虑在程序中调用backtrace函数,来获取调用栈。
(2)backtrace_symbols把用backtrace获取的调用栈转换成字符串数组,以字符串数组的形式返回,使用这需要在外面释放返回的字符串数组所占用的内存
(3)backtrace_symbols_fd把用backtrace获取的调用栈信息写到fd所指定的文件中
void * __builtin_return_address (unsigned int level)
这个函数用来得到当前函数,或者调用它的函数的返回地址,得到这个地址后,通过gdb反汇编,便可得到调用函数相关的信息,这也是在应用中获取调用栈的一种fangfa。
2内存分配释放
#include "malloc .h"size_t malloc_usable_size((void *__ptr));
这个函数的用法是返回调用malloc后实际分配的可用内存的大小,我们知道在c++中,operator new()可以重载各种各样的版本,可以传入调用的相关信息来跟踪内存分配情况。但是operator delete()却只有两种形式,不能随意重载,尤其是全局的operator delete(),只有一种版本,这个时候就比较痛苦了,究竟释放了多少内存,这时候malloc_usable_size()这个函数就有用武之地了,调用它就可以获取当前释放的内存大小,那么在new中也请用他来统计开辟的内存,这样才能对应起来,因为在调用malloc时很多时候实际分配内存会比用户申请的要大一些,所以如果两边的统计方法对应不起来的话,统计结果也有比较大的判别
C++内存分配与管理
1函数
(1)operator new function
void * ::operator new(size_t); //Globalvoid * class-name::operator new(size_t); //Class
上面是C++中operator new function的原型,一个是全局类型,一个的类成员类型,全局类型的operator new 函数在下面两种情况下被调用:一种是在分配C++内建类型的动态内存时,一种是在分配用户自己定义的operator new函数,那么用户在用new申请该类型的动态内存时,便会调用该类型的成员函数operator new,而不是全局的operator new。
另外,我们注意到,上面的原型中函数的返回值void* 类型,第一个参数为size_t类型,这个是C++编译要求的,如果要自己重载operator new函数,返回值必须为void* 类型,第一个参数必须为size_t类型,否则,编译器会返回如下错误信息:
error: ‘operator new’ takes type ‘size_t’ (‘unsigned int’) as first parameter
这里需要注意的一点是,我们可以利用operator new function可以重载的特点,可以通过参数传入一些额外的信息,来调试程序,检测内存泄露,比如,我们可以向下面这样重载,传调用处的行号,函数名,这样就可以跟踪内存的分配
void * operator new(size_t unSize, int nLine, const char * pFunc){ prinft("Line: %d, Func: %s, allocate %u byte(s)\n", nLine, pFunc, unSize); return malloc(unSize);}
(2)operator delete function
void operator delete( void * );void operator delete( void *, size_t );
上面是operator delete function的原型,operator delete function也有全局和类成员的两种,这压力需要注意,一个类只能有一个operator delete function作为其成员函数,而且必须为上面两种中的其中一种,没有其他的形式,如果一个类实现了自己的operator delete function成员函数,那么在释放该类型的内存时,编译器便会调用成员operator delete function,而不是全局的。
上面两种原型,第一种在调用的时候,编译器会把释放的内存的首地址传入,第二种在调用的时候,编译器会把要释放的内存的首地址和大小都传入,因此,可以利用这一特性,如果我们在基类中实现第二种形式的operator delete function的成员函数,那么便可以之来释放子类类型的内存。
2 运算符
(1)new operator
[::] new [placement] new-type-name [new-initializer][::] new [placement] ( type-name ) [new-initializer]
上面是new operator 的原型,在c++中,动态内存的分配,通常都是调用new operator来完成的,利用new operator来分配动态内存,编译器要做下面两项工作:
a. 调用operator new function 分配内存
b. 调用构造函数来进行初始化
下面来说一说new operator的原型中各部分到底是干什么的:
placement:如果你重载了operator new function,placement可以用来传递额外的参数
type-name:指定要分配的内存类型,可以是内建类型,也可以是用户自定义类型
new-intializer:指定要分配后的内存的初始化参数,也就是构造函数的参数。这里需要注意一点,在分配一个对象的数组类型的内存时,不能够指定初始化参数:换言之,想要分配一个对象的数组类型的内存,该对象必须有缺省构造函数
(2)delete operator
[::] delete cast-expression[::] delete [ ] cast-expression
上面是delete operator的原型,第一种是用来释放普通的对象类型的内存,第二种是用来释放对象的数组类型的内存,在C++中,用new operator分配的动态内存,必须调用delete operator来释放,通常用delete operator 释放内存编译器要做下面两箱工作:
a 调用对象析构函数来析构对象
b 调用operator delete function 来释放内存
3关于new/delete使用过程中一些需要注意的点
(1)如何区别operator new/ delete function与new/delete operator?
通过上面的讲述,不难看出,我们分配/释放动态内存,调用的是new/delete operator, 而在调用new/delete的过程中,编译器会自动调用operator new/delete function来完成实际的内存分配/释放的工作
(2) 用delete operator去释放一块不是由new operator释放的内存,结果是不可预料的,因此,切记,operator new与operator delete一定要配对使用,这是写好程序的基础
(3) new operator调用失败会抛出std::bad_alloc异常,前提是你没有自己重载对应的operator new function;delete operator失败,常见的原因是多次delete同一块内存
(4) 如果一块内存被delete后,再对它解引用(Dereference),结果也是不可预测的,很可能导致程序崩溃
(5) delete一个空(NULL)指针是安全的,没有任何害处的
(6) 类成员类型的operator new/delete函数必须为静态(static)函数,因此它们不能为虚函数(virtual function),也遵守public, protected, private的访问权限控制
VC程序调试的方法