问题出现:
今天在测试程序的时候,程序直接给了一个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还是不能理解,也许是为了增加安全性吧。