1. 程式人生 > >一週一更之snprintf踩坑記

一週一更之snprintf踩坑記

背景

上週,在做併發測試的時候,發現程式總是在某一時刻發生Segmentation fault,但在之前未做併發測試時,並沒有出現該問題,在認真的分析了core檔案之後,不得不再一次明白自己是如此的too young to naive。

踩坑

在core檔案中,有如下的分析以及結論:

  1. Segmentation fault的原因是因為查詢雜湊元素時訪問了一塊不可訪問的記憶體導致的。
  2. 儲存該雜湊表的結構體中的內容正常。
  3. 將雜湊表的陣列內容打印出來後發現,該陣列的應該是有效指標全部變成了不可訪問的地址。
  4. 之前以為是分配雜湊表的記憶體被釋放,但是發現該記憶體指標正常
  5. 如果內容被改寫,便想到了是否是出現記憶體越界導致雜湊表中的內容被改寫,根據這個結論,嘗試將這些不可訪問的地址強制轉換char型別輸出後發現,打印出了一串字串,該現象也證明了猜測是對,在對這字串分析後,發現是某段寫字串程式碼導致的。
  6. 在仔細分析了這段程式碼後,一切的罪魁禍首都是因為snprintf的錯誤使用導致的。

下面我將貼上與該問題類似的程式碼:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>



int main()
{
    int i          = 0;
    int iRet       = 0;
    int *pi        = {NULL};
    char *pBuf     = NULL;
    char acBuf[1024] = {0};


    pi = malloc
(5 * sizeof(int)); for(i = 0; i < 5; ++i) { pi[i] = i; } pBuf = acBuf; do { pBuf += iRet; iRet = snprintf(pBuf, 32-iRet, "%s", "abcdeeeeeeeeeeee"); if(iRet <= 0) { printf("buf:%s\n", acBuf); break; } printf
("input:%d\n", pi[0]); }while(1); if(pi) free(pi); return 0; }

可能有經驗的看官一眼就瞧出了問題的所在,問題所在就是因為我錯誤的把snprintf的返回值效果與sprintf理解成了一致。根據介紹snprintf主要有以下的幾點需要注意:

函式原型:int snprintf(char *str, size_t size, const char *format, …)
1、如果格式化後的字串長度 < size,則將此字串全部複製到str中,並給其後新增一個字串結束符(‘\0’);
2、如果格式化後的字串長度 >= size,則只將其中的(size-1)個字元複製到str中,並給其後新增一個字串結束符(‘\0’),返回值為欲寫入的字串長度
3、返回為格式化字串的長度

需要我們特別的注意的是返回值並不是寫入到緩衝區的長度,而是格式化字串的長度,這也就導致了上述程式碼中的iRet的返回值永遠是16,因此上述程式碼就導致了acBuf的陣列發生了越界,最終導致pi陣列被覆蓋,成為了一塊不可訪問的記憶體。

結束語

其實,出現該問題都是因為對於此類函式並沒有過多去理解,認知總是建立在之前的基礎上,所以導致了問題的出現。實際中的程式碼量可能要龐大的多,因為越界導致的問題可能會讓人抓狂不已。但是藉助於GDB,許多問題都是可以迎刃而解的,關鍵在於耐心。