strncpy引起程序崩溃的问题,原因探究

问题出现:

  今天在测试程序的时候,程序直接给了一个Segmentation fault.这可不大好。于是就开始了苦逼的debug里程。

debug过程:

  一开始,先需要定位错误出现在什么地方。于是,调用gdb,run。然后再重新测试。

  gdb清晰的指出了问题所在的地方。

  

  至少是一个好开始吧。

  不过一看,傻眼了。直接报了是string析构时除了问题。这可如何是好,库函数里头出错怎么调试呢。

  手头没有debug模式编译的lib,看来这条路走不通了。而且,一般来说,这种成熟的库是不会出问题的。于是再仔细看自己的代码。

  这个函数中,我总共申请了4个string的变量。既然是局部变量析构导致的,那看起来,就只有一个string有嫌疑了。

  

  看到这里又开心又郁闷。郁闷的是,这个看起来没有什么问题啊。很正常。

  于是接着翻,找我的getRules()函数。

  

  这里是用了自己弄的一个共享内存的库,从共享内存里获取数据后,赋值给rules,然后返回。看起来也很正常。

  我们都知道,c++里的string采用了写时拷贝的技术。只有写的时候,才会将内容拷贝过去,否则,多个string就是共享同一块存储字符串的空间。于是,就有理由想,是不是原来的数据被析构了,而rules仍然指向原来的内存,最后导致了析构失败呢?

  于是,改了句代码,return rules =>return rules.substr();

  再查看其c_str()所返回的地址的确不一样了。

  

  不过,就像结果显示的那样,还是挂了。

  看来,和这个rules没什么关系了。不过,程序的确是在析构这个rules出现了问题。

  再冷静下来,发现出现这个问题还有一个特征,那就是rules不是为空的时候。当rules=“”的时候就没事。

  于是,问题就定位到了

  

  仔细一看,终于发现代码中的问题了:我的tmprule开了512,但是却在strncpy指定拷贝了1024。改之,代码就跑顺畅了。

问题反思:

  1. 程序在调用string的析构函数出现了错误。

  原因: strncpy在拷贝的时候,由于我指定了长度为1024,于是strncpy复制的时候就越界了。由于我的string是局部变量,声明在tmprule之前。因此,strncpy就一路复制下去把string的数据全部破坏了。进而string在析构的时候,就全部乱了套。而且,即使string不出问题,这个函数的调用堆栈也完蛋了,程序肯定也不行了。

  2. strncpy复制导致栈中数据被破坏

  原因: 明显的原因,是我只申请了512大小的数据,却要求复制1024长度,于是,strncpy就一路复制下去了。可是,strncpy不是遇到'\0'就停止复制了么?我的rule长度不到20,怎么会导致失败的。

    带着这个疑问,我去找了strncpy的源码。看了源码应该就能理解了。

 1 char* __strncpy(char* dest, const char* src, size_t n)
 2 {
 3     char c;
 4     char *s = dest;
 5     if (n >= 4)
 6     {
 7         size_t n4 = n >> 2;
 8         for (;;)
 9         {
10             c = *src++;
11             *dest++ = c;
12             if (c == '\0')
13                 break;
14             c = *src++;
15             *dest++ = c;
16             if (c == '\0')
17                 break;
18             c = *src++;
19             *dest++ = c;
20             if (c == '\0')
21                 break;
22             c = *src++;
23             *dest++ = c;
24             if (c == '\0')
25                 break;
26             if (--n4 == 0)
27                 goto last_chars;
28         }
29         n -= dest - s;
30         goto zero_fill;
31     }
32 last_chars:
33     n &= 3;
34     if (n == 0)
35         return dest;    
36     for (;;)
37     {
38         c = *src++;
39         --n;
40         *dest++ = c;
41         if (c == '\0')
42             break;
43         if (n == 0)
44             return dest;
45     }    
46 zero_fill:
47     while (n-- > 0)
48         dest[n] = '\0';    
49     return dest - 1;
50 }

  这个是gnu的strncpy的实现。我们能清晰的看到,原来strncpy在复制的时候,在遇到'\0'时,先复制过去,然后很“负责任”的把dest剩下置为了0。于是,我们的函数的栈就全完蛋了。(这里得思考下函数调用时栈,局部变量的布局)。

总结:

  今天遇到的问题看起来很神奇,string析构失败了。实际上还是在使用时并没有明确函数的特性。这个bug也害我浪费了2个小时。好在最后还是翻了出来。也算是一个教训,下次使用strncpy一定会注意了。还有,源码真是好东西。不过,对于gnu在设计的时候,增加了zero_fill还是不能理解,也许是为了增加安全性吧。

原文地址:https://www.cnblogs.com/marchtea/p/2848278.html