背景
上周,在做并发测试的时候,发现程序总是在某一时刻发生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,许多问题都是可以迎刃而解的,关键在于耐心。