当前位置:C++技术网 > 资讯 > Linux程序莫名奇妙的出现SIGSEGV被kill,问题竟然出在vector

Linux程序莫名奇妙的出现SIGSEGV被kill,问题竟然出在vector

更新时间:2017-08-17 12:25:28浏览次数:1+次

        SIGSEGV错误,在Windows上对应的是内存访问错误,如空指针、内存越界等。而Linux中通常叫做Segment Fault,即段错误。这类问题通常都是由于内存访问出错。这个问题简直成了我写linux程序的阴影,原来不过如此。但是没有搞清楚之前,真是无尽的恐惧。

    Linux程序莫名奇妙的出现SIGSEGV被kill,问题竟然出在vector

        在POSIX兼容的平台上,SIGSEGV是当一个进程执行了一个无效的内存引用,或发生段错误时发送给它的信号。SIGSEGV的符号常量在头文件signal.h中定义。因为在不同平台上,信号数字可能变化,因此符号信号名被使用。通常,它是信号11。

    之前在测试TCP服务器程序的时候,发现服务器程序在运行一段时间后,就被系统Kill了。通过跟踪coredump文件发现,是被signal 11 (SIGSEGV)信号所杀。也就是说,程序代码中哪里出现了内存访问错误或叫做指针使用错误。
    此时我还不太会使用gdb调试coredump文件。所以我用Valgrind工具来检测内存错误的,结果发现终止程序运行的位置在定时器回调函数中。而在这函数中有这样一段代码:
vector<int>::iterator it=g_client_list.begin();
for (;it!=g_client_list.end();++it)
{
    //向所有客户端定期发送消息,维持客户端连接
    int ret = srv.send(*it, (const char*)"is_alive\r\n", sizeof("is_alive\r\n"));
    if (ret)
    {
        //发送失败,移除旧连接,客户端重新链接后会自动加入到客户端队列
        g_client_list.erase(it);
        it=g_client_list.begin();
    }
}
    Valgrind提示错误发生在erase()函数。通过分析后,才恍然大悟。
    这里是定期向客户端发送数据,如果发送数据失败,就意味着客户端连接被断开或者异常,这样我就将这个客户端删除。然后重新遍历客户端列表。我测试的是一个客户端,当这个客户端断开连接的时候,就会触发SIGSEGV信号。因为客户端一断开,定时器这里发送数据给客户端就会失败,失败之后就会删除客户端,然后就崩溃了。为什么呢?
    当发送数据失败后,客户端列表中就删除了这个客户端。然后迭代器又被赋值为列表的开头。然而我们只有一个客户端,开头就是结尾。所以此时的it也就等于end()。然后循环继续,执行了++it。此时的it是等于end()+1的。也就是说此时for循环里面的条件检测是可以通过的,但是内存访问就越界了。因为it已经指向了end()+1,所以所指的内存超越了客户端列表的内存范围。此时使用*it获取内存的数据,自然就是会出现内存非法访问,也就触发了SIGSEGV信号而被系统kill。
    出现这个问题,原因在于我对vector的使用不到位,需要进一步学习。这样的问题,是很难排查的。因为你自己以为这个代码自己很熟了,而且以为不会有错误的。然而就在你最熟悉的地方出现了错误。说明曾经以为非常正确的知识其实一直都是错误的,这是多么可怕的。其实我没有系统的学习过STL,只是简单的学习过一些,没有学完。说来真是惭愧,警钟长鸣,扎实的基础一直都是致胜的关键。

        下面是一段检测问题的代码:

    

    vector<int>::iterator it = g_client_list.begin();
    for (; it != g_client_list.end(); ++it)
    {
        if(it== g_client_list.begin())
            printf("it等于开始");
        if (it == g_client_list.end())
            printf("it等于结束");
        if (it == g_client_list.end()+1)
            printf("it等于结束+1");

        //向所有客户端定期发送消息,维持客户端连接
        int ret = srv.send(*it, (const char*)"is_alive\r\n", sizeof("is_alive\r\n"));
        if (ret)
        {
            //发送失败,移除旧连接,客户端重新链接后会自动加入到客户端队列
            g_client_list.erase(it);
            it = g_client_list.begin();
        }
    }
    可以检测到,当客户端断开后,打印了“it等于结束+1”,自然it就不等于end(),for的判定条件就会通过,然后就出现内存越界了。