让段错误不再是一个错误

今天的主题是:访问了空指针一定会出现段错误(segmentation fault)吗?

看下面代码:
test.c

#include <stdio.h>

int main()
{
    int *p = NULL;

    *p = 1;

    return 0;
}

在Linux里面写了这么多代码,大家应该很清楚,上面的代码会出现段错误,因为访问了空指针。

#gcc test.c -o test
#./test
Segmentation fault
#

现象和我们想的一样。在Linux中,如果访问了空指针或者野指针,都会出现段错误(segmentation fault),这也是操作系统出于对内存的保护。 发生段错误的时候,系统会发出11信号(SIGSEGV),收到这个信号,程序就挂了。我们来验证一下:

circle.c

#include <stdio.h>

int main()
{
    while (1);

    return 0;
}

就是一个简单的死循环程序,运行起来,再打开一个终端,先查询进程的pid,再用kill发送一个SIGSEGV信号:

在这里插入图片描述
再来看下运行程序的结果:

在这里插入图片描述清楚了没,现象和程序出现段错误的现象一样。

众所周知,在Linux里面,进程对信号的处理有三种方法,捕获、忽略、缺省行为。一般情况下,你什么都不做,行为就是缺省的,比如CTRL+C就会让进程死。

但是下面的进程,CTRL+C之后不仅死不了,还能打印东西:

signal.c

#include <stdio.h>
#include <signal.h>

void handler(int sig)
{
    printf("receive signal %d\n", sig);
}

int main()
{
    signal(SIGINT, handler);

    while (1);

    return 0;
}

编译运行,然后不停的按CTRL+C:

在这里插入图片描述
原因很简单,进程的2号信号SIGINT被我们捕获了。这个从ps命令里面也可以看出(留意CAUGHT这列):

在这里插入图片描述
当然,我们也可以选择忽略它:

#include <stdio.h>
#include <signal.h>

int main()
{
    signal(SIGINT, SIG_IGN);

    while (1);

    return 0;
}

不管怎么按CTRL+C,都没用:

在这里插入图片描述
我们还是可以ps一下(留意IGNORED这列):

在这里插入图片描述在这么多信号里面,除了极少数信号类似SIGKILL、SIGSTOP这种不能捕获和忽略以外,其他的都是可以被我们拿来把玩的,当然也包括看起来牛逼轰轰的SIGSEGV段错误(编号11)的信号。

我必须反复强调一点,当你用CTRL+C等对应的信号去杀死一个进程A的时候,从来都不是你杀死了A,而是你给A发了个信号,而A在响应这个信号的时候,其对应行为是进程exit。所以,不是你杀死了进程A,而是你发个信号,“通知”目标进程A去死。所以你不能用人类世界的杀死来理解Linux的杀死。Linux的逻辑类似:你对进程A说:“你去死吧”(发个可以让它死的信号),A看到这个pending的信号后,啥废话都不说,立即闷声死翘翘!所以,你只要改变A的响应行为,就可以选择不死。

下面问题就简单了,我们利用setjmp和longjmp来实现段错误后继续执行:

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>

jmp_buf env;

void handler(int sig)
{
    longjmp(env, 1);         //跳转到上一次保存的现场
}

int main()
{
    int ret = setjmp(env);   //保存执行现场,返回0
    if (0 == ret)
    {   
        signal(SIGSEGV, handler);

        printf("制造段错误...\n");
        int *p = NULL;
        *p = 1;
    }   
    else
    {   
        printf("段错误后!\n");
    }

    return 0;
}

setjmp这个函数的原理是:

调用这个函数的时候,它会保存执行现场,并返回0;之后调用longjmp,可恢复到setjmp保存的现场,setjmp再次返回,不过这次返回的是longjmp()的第二个参数。看下面这个图:

在这里插入图片描述
如果我们对代码稍加修改,删除一行:

#include <stdio.h>
#include <signal.h>
#include <setjmp.h>

jmp_buf env;

void handler(int sig)
{
    longjmp(env, 1);         //跳转到上一次保存的现场
}

int main()
{
    int ret = setjmp(env);   //保存执行现场,返回0
    if (0 == ret)
    {   
        //signal(SIGSEGV, handler);

        printf("制造段错误...\n");
        int *p = NULL;
        *p = 1;
    }   
    else
    {   
        printf("段错误后!\n");
    }

    return 0;
}

现象又是我们熟悉的:

在这里插入图片描述
看明白没,段错误其实只是操作系统为了保护内存而发出的信号,进程收到信号缺省处理为挂掉,所以要想避免段错误,忽略SIGSEGV就行。当然这绝对是下下策,用这个方法来解决段错误简直无语,更重要的还是写代码的时候注意内存的使用,不要访问不能访问的内存。

更多文章、视频、嵌入式学习资料,微信关注公众号 『学益得智能硬件』

在这里插入图片描述

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页