如何调试程序
来源:互联网 发布:手机淘宝订单生成器 编辑:程序博客网 时间:2024/06/05 09:14
原文地址:
初学C语言/C++程序的编写时,可能经常会遇到程序崩溃的现象。一般来说,程序崩溃是由于内存操作不当引发的。但是具体来讲,由哪些原因可以导致程序崩溃呢?以及当程序崩溃时该如何找到错误的位置呢?本教程即是讲解这个问题。
本文的视频讲解在 C/C++学习指南(补充篇)- 单步调试 的第7,8节课。
一、程序崩溃的定位
先给出一个例子,该代码有致命bug,运行时将使程序崩溃。在VC中输入以下代码:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
* 按CTRL+F5运行
显示程序已崩溃,如下图所示:
这种提示意味着代码中存在严重bug,导致了程序崩溃。那么,怎么知道是哪儿出错了呢?
* 按F5启动调试
黄色箭头指向的位置,就是出错的位置。在程序崩溃时,VC会自动地停在导致崩溃的那一行代码上,
注意两点:
- 提示的错误为“未处理的异常 0XC000005,读取位置0x00000000时发生访问冲突”。以后凡是看到这种提示,表示错误的原因是“空指针”。
- 在代码编辑器,黄色箭头已经指向了错误的行。
在界面上,点“中断”
在界面上,点开“调用堆栈”
这个窗口里可以直接观察到发生错误的时候、函数栈的各层函数的信息。( 如果没有显示这个窗口,可从菜单里 “调试 | 窗口 | 调用堆栈”里打开)
二、“调用堆栈”窗口的使用方法
“调用堆栈”窗口里可以观察到:
- 函数的调用层次 :main() -> test(id, name) ->show(p)
- 每一次函数里的局部变量(含参变量)的值
- 全部变量的值
(*)从上到下,依次是函数的调用层次
(*)每一行由以下信息组成
Hello.exe!show(Object* p=0x00000000)行12+0xc字节
模块名:Hello.exe
函数名: show
参数值:Object*p = 0x00000000
位置:第12行
可以发现,在main()函数之上还有一些东西,那些就是Windows应用程序的框架。
(3)双击某个函数,可以看到这个函数内的局部变量的值
注:显示的此时此刻(发生错误的时刻),函数栈上的各个层次的所有局部变量的值。观察它们的值,即可有助于程序员判断到底是哪儿写错了。
三、程序崩溃的原因分类
- 函数栈溢出
一个变量未初化、未赋值,就读取它的值。
( 这属于逻辑问题,往往是粗心大意的导致的 ) - 函数栈溢出
(1)定义了一个体积太大的局部变量
(2)函数嵌套调用,层次过深(如无穷递归) - 数组越界访问
访问数组元素时,下标越界 - 指针的目标对象不可用
(1)空指针
(2)野指针- 指针未赋值
- free/delete释放了的对象
- 不恰当的指针强制转换
3.1 读取未赋值的变量
这种往往是疏忽大意造成的,因为逻辑错误非常明显。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
按Ctrl + F5运行
注意其错误提示的特征:“The variable is being used without being initialized”。 显然,a,b都没有初始值,而且也未赋值,那么multiply(a,b)毫无意义、不是正常的逻辑。
按F5启动调试
注意其错误特征:“The variable is being used without being initialized”。
点“中断”,
3.2 函数栈溢出
以下两种情况会导致函数栈溢出:
(1)定义了一个体积太大的局部变量
(2)函数嵌套调用,层次过深(如无穷递归)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
按CTRL+F5运行,
按F5启动调试
注意错误提示的特征:“未处理的异常:0Xc00000FD: Stack overflow”
点“中断”,则黄色箭头停在出错位置。
在调用堆栈里点main,
绿色箭头 表示,从第16行返回后,函数栈发生异常。
结论:当变量体积太大时,应该用malloc或new来动态分配内存。
会导致函数栈溢出的另一种原因:函数递归调用,层次太深,没有终止条件。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
则运行时也会崩溃。
3.3 数组越界访问
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
按CTRL + F5运行
错误特征:” Stack around the variable was corrupted ”
3.4指针的目标对象不可用
请参考 C/C++学习指南(语法篇),第九章,9.5讲的视频讲解
指针指向的对象(内存)必须保证是有效的、可以访问的。
分以下几种情况:
(1)空指针
(2)野指针
- 指针未赋值
- free/delete释放了的对象
- 不恰当的指针强制转换
3.4.1 空指针
示例代码
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
按CTRL+F5运行,显示程序已崩溃
按F5启动调试
错误特征:” 未处理的异常:0xC0000005:读取位置0x00000000时发生访问冲突 “
3.4.2 野指针: 指针未赋值
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
错误特征:
The variable is being used without being initialized
此时VC不能自动找到错误的精确位置,应该按照用单步调试的手段,逐步找到出错的行。
3.4.3 野指针: free/delete释放了的对象
指针指向一个动态分配的对象,被free/delete释放之后,该指针不再可用。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
[参考习题 #145]
此时VC不能自动找到错误的精确位置,应该按照用单步调试的手段,逐步找到出错的行。
3.4.4 野指针:不恰当的指针强制转换
关于指针强制转换的各种情况,请参考此文:
指针类型的转换 http://tieba.baidu.com/p/4103000163
这样会导致各种类型的错误提示,并没有统一的错误特征。所以指针强制转时,必须要对它有足够的理解才能使用。
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
2
用depends.exe查看exe依赖的dll及其版本号。
崩溃的时候在弹出的对话框按相应按钮进入调试,按Alt+7键查看Call Stack即“调用堆栈”里面从上到下列出的对应从里层到外层的函数调用历史。双击某一行可将光标定位到此次调用的源代码或汇编指令处,看不懂时双击下一行,直到能看懂为止。
- 如何调试程序,
- 如何调试Erlang程序
- qt如何调试程序
- iOS: 如何调试程序
- 如何调试DX程序
- 如何调试DirectX3D程序
- 如何调试DirectX3D程序
- 如何调试Erlang程序
- 如何调试PHP程序
- 如何调试程序
- 如何调试aspx程序
- 如何调试R程序
- Android如何调试程序
- 如何调试Python程序
- powerbuilder 如何调试程序
- 如何调试程序
- 如何调试Python程序
- 如何使用gdb调试程序
- 自定义广播
- Scala学习--Trait
- 滑动门
- (2017/4、8、/ B
- iOS TriLogic小游戏 教程
- 如何调试程序
- WEB-INF目录与META-INF目录的作用
- stm32在keil编译环境下使用printf函数
- WEB-INF目录与META-INF目录的作用
- Swift 3必看:新的访问控制fileprivate和open
- 318. Maximum Product of Word Lengths | 字符串长度相乘最大值
- c语言 实现二分查找
- HTTP协议分析
- keras的Embedding层