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還是不能理解,也許是為了增加安全性吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM