一週一更之snprintf踩坑記
阿新 • • 發佈:2019-01-29
背景
上週,在做併發測試的時候,發現程式總是在某一時刻發生Segmentation fault,但在之前未做併發測試時,並沒有出現該問題,在認真的分析了core檔案之後,不得不再一次明白自己是如此的too young to naive。
踩坑
在core檔案中,有如下的分析以及結論:
- Segmentation fault的原因是因為查詢雜湊元素時訪問了一塊不可訪問的記憶體導致的。
- 儲存該雜湊表的結構體中的內容正常。
- 將雜湊表的陣列內容打印出來後發現,該陣列的應該是有效指標全部變成了不可訪問的地址。
- 之前以為是分配雜湊表的記憶體被釋放,但是發現該記憶體指標正常
- 如果內容被改寫,便想到了是否是出現記憶體越界導致雜湊表中的內容被改寫,根據這個結論,嘗試將這些不可訪問的地址強制轉換char型別輸出後發現,打印出了一串字串,該現象也證明了猜測是對,在對這字串分析後,發現是某段寫字串程式碼導致的。
- 在仔細分析了這段程式碼後,一切的罪魁禍首都是因為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,許多問題都是可以迎刃而解的,關鍵在於耐心。