• 程序崩溃处理方法一(调用堆栈)


     一、程序崩溃的定位

    先给出一个例子,该代码有致命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;
    }
    View Code

     按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;
    }
    View Code

    按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;
    }
    View Code

    按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;
    }
    View Code

    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;
    }
    View Code

    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;
    }
    View Code

     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;
    }
    View Code

    3.4.4 野指针:不恰当的指针强制转换 

    #include <stdio.h>
    int main()
    {
        int a = 10;
        double* p = (double*) &a; 
        *p = 123.345; // 程序崩溃
        return 0;
    }
    View Code
    111
  • 相关阅读:
    成年人的快乐
    90后家庭的宠物
    计算机科学与技术易错知识点需记篇
    微机原理与接口编程技术2020 考试大纲知识点超细总结
    84.手写一个类的继承
    83.对前端路由的理解?前后端路由的区别
    81.$(document).ready()方法和window.onload有什么区别?
    80.浏览器是如何渲染页面的
    77、常用的拖拽事件
    75.json和jsonp的区别?
  • 原文地址:https://www.cnblogs.com/zwj-199306231519/p/13659353.html
Copyright © 2020-2023  润新知