一、程序崩溃的定位
先给出一个例子,该代码有致命bug,运行时将使程序崩溃。在VC中输入以下代码:
/////////////// 示例1 ////////////////////
#include <stdio.h> #include <stdlib.h> struct Object { int id; char name[32]; }; void show(Object* p) { printf("Object [%d, %s] ", p->id, p->name); } void test(int id, const char* name) { Object* obj = NULL; show(obj); //<--空指针 } int main() { int aaa = 9801; // 未使用 char* str = "127.0.0.1"; // 未使用 int id = 123; const char* name = "shafa"; test(id, name); return 0; }
按CTRL+F5运行
显示程序已崩溃,如下图所示:
这种提示意味着代码中存在严重bug,导致了程序崩溃。那么,怎么知道是哪儿出错了呢?
* 按F5启动调试
黄色箭头指向的位置,就是出错的位置。在程序崩溃时,VC会自动地停在导致崩溃的那一行代码上,
注意两点:
- 提示的错误为“未处理的异常 0XC000005,读取位置0x00000000时发生访问冲突”。以后凡是看到这种提示,表示错误的原因是“空指针”。
- 在代码编辑器,黄色箭头已经指向了错误的行。
在界面上,点“中断”
在界面上,点开“调用堆栈”
这个窗口里可以直接观察到发生错误的时候、函数栈的各层函数的信息。( 如果没有显示这个窗口,可从菜单里 “调试 | 窗口 | 调用堆栈”里打开)
二、“调用堆栈”窗口的使用方法
“调用堆栈”窗口里可以观察到:
- 函数的调用层次 :main() -> test(id, name) ->show(p)
- 每一次函数里的局部变量(含参变量)的值
- 全部变量的值
(1)从上到下,依次是函数的调用层次
(2)每一行由以下信息组成
Hello.exe!show(Object* p=0x00000000)行12+0xc字节
模块名:Hello.exe
函数名: show
参数值:Object*p = 0x00000000
位置:第12行
可以发现,在main()函数之上还有一些东西,那些就是Windows应用程序的框架。
(3)双击某个函数,可以看到这个函数内的局部变量的值
注:显示的此时此刻(发生错误的时刻),函数栈上的各个层次的所有局部变量的值。观察它们的值,即可有助于程序员判断到底是哪儿写错了。
三、程序崩溃的原因分类
3.1 读取未赋值的变量
这种往往是疏忽大意造成的,因为逻辑错误非常明显。
//////////////// 示例 //////////////////
#include <stdio.h> #include <stdlib.h> // 求两数的积 int multiply(int m, int n) { return m * n; } int main() { int a, b; int m = multiply(a, b);//<--这里有错 printf("result: %d ", m); return 0; }
按Ctrl + F5运行
注意其错误提示的特征:“The variable is being used without being initialized”。 显然,a,b都没有初始值,而且也未赋值,那么multiply(a,b)毫无意义、不是正常的逻辑。
3.2 函数栈溢出
以下两种情况会导致函数栈溢出:
(1)定义了一个体积太大的局部变量
(2)函数嵌套调用,层次过深(如无穷递归)
//////////////////// 示例 //////////////////////
#include <stdio.h> #include <stdlib.h> // 局部变量的体积太大 void test() { int buf[1024*1024*16]; // 这个变量体积太大 printf("DO NOT define a very large buffer on the stack!"); for(int i = 0; i<sizeof(buf)/sizeof(int); i++) { buf[i] = i; } } int main() { test(); return 0; }
按CTRL+F5运行,
3.3 数组越界访问
///////////////// 示例 /////////////////
#include <stdio.h> #include <stdlib.h> void test(char* p) { for(int i=0; i<5; i++)//<--这里有错 { p[i] *= 10; printf("%d ", p[i]); } } int main() { char buf[4] = {1,2,3,4}; test(buf); return 0; }
3.4指针的目标对象不可用
请参考 C/C++学习指南(语法篇),第九章,9.5讲的视频讲解
指针指向的对象(内存)必须保证是有效的、可以访问的。
分以下几种情况:
(1)空指针
(2)野指针
- 指针未赋值
- free/delete释放了的对象
- 不恰当的指针强制转换
3.4.1 空指针
#include <stdio.h> #include <stdlib.h> struct Object { int id; char name[32]; }; void show(Object* p) { printf("Object [%d, %s] ", p->id, p->name); } void test(int id, const char* name) { Object* obj = NULL; show(NULL);//<--这里有错 } int main() { int id = 123; const char* name = "shafa"; test(id, name); return 0; }
3.4.2 野指针: 指针未赋值
#include <stdio.h> #include <stdlib.h> struct Object { int id; char name[32]; }; void show(Object* p) { printf("Object [%d, %s] ", p->id, p->name); } int main() { Object* p; show(p);//<--这里有错 return 0; }
3.4.3 野指针: free/delete释放了的对象
指针指向一个动态分配的对象,被free/delete释放之后,该指针不再可用
#include <stdio.h> #include <stdlib.h> #include <string.h> struct Object { int id; char name[32]; }; void show(Object* p) { printf("Object [%d, %s] ", p->id, p->name); } int main() { Object* p = (Object*)malloc(sizeof(Object)); p->id = 123; strcpy(p->name, "邵发"); free(p); // p指向的内存被释放 p->id = 12; //<--这里有错,不可再对其访问 show(p); return 0; }
3.4.4 野指针:不恰当的指针强制转换
#include <stdio.h> int main() { int a = 10; double* p = (double*) &a; *p = 123.345; // 程序崩溃 return 0; }