记录一下公司某产品在运行过程中产生的崩溃问题,通过调试 core文件,我们定位到是程序在调用realloc 时候出现了问题,这是一个保受争议的函数,首先来看一下程序中的那块代码段;
`
char * pData = NULL; // 实际定义在头文件并在类的构造函数中初始化,析构函数中释放
int DisplayChannel::create_canvas(int width,int height)
{
//省略N行 .....
if(pData == NULL) {
pData = (char *) memalign (32,width * height);
} else {
char *ptr = (char *)relloc(pData,width * height);
if(ptr == NULL){
printf("relloc failed!");
} else {
pData = ptr;
}
}
}
`
实际项目中和上面的代码不完全一样,但大体意思一样。上面代码实际可能会被调用多次,乍看上去也没有什么问题,其实至少存在两处不妥之处。出现这样的问题主要是没有真正理解realloc的使用,下面先来看一下realloc的具体使用:
原型:
void *realloc(void *mem_address, unsigned int newsize);
指针名=(数据类型***)**realloc(要改变内存大小的指针名,新的大小)。
功能:
先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将mem_address返回,如果空间不够,先按照 newsize 指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域,而后释放原来mem_address所指内存区域,同时返回新分配的内存区域的首地址。即重新分配存储器块的地址。
返回值:
如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
百科中总结的关于realloc的用法
1、realloc失败的时候,返回NULL
2、realloc失败的时候,原来的内存不改变,不会释放也不会移动
3、假如原来的内存后面还有足够多剩余内存的话,realloc的内存=原来的内存+剩余内存,realloc还是返回原来内存的地址; 假如原来的内存后面没有足够多剩余内存的话,realloc将申请新的内存,然后把原来的内存数据拷贝到新内存里,原来的内存将被free掉, realloc返回新内存的地址。
4、如果size为0,效果等同于free()。
5、传递给realloc的指针必须是先前通过malloc(), calloc(), 或realloc()分配的,或者是NULL
6、传递给realloc的指针可以为空,等同于malloc。
有了对realloc 的充分了解,我们再来分析以下上面代码存在的不妥之处:
第一处:如果第二次调用传递的width * height(即传给realloc的) 小于第一次传递的width * height(即传给memalign的)就可能会发生数据丢失的情况,这个不是本文说明的重点,点到为止。
第二处:如果第二次调用传递的width * height = 0(即传给realloc的),就会引发第三次调用realloc崩溃。这也是我们实际项目中出问题的地方。
那为什么呢?原因就是上面说的第4点,如果size为0,效果等同于free()。可以去查看realloc 源码实现,这里需要注意的是只对指针本身进行释放,并没有将指针指向NULL,实际指向了一块”垃圾“内存,也就是说存在野指针的问题。
知道原因了,那上面的代码块我们该如何去修改呢?修改很简单就是加一条赋值语句,即realloc 失败时将 pData = NULL 或者 直接 pData = (char *) memalign (32,width * height); 即可。
下面是一段测试代码,编辑TestRealloc.c:
#include
#include
#include
#include //linux 下使用memalign函数头文件,Windows可以去掉
char * pData = NULL;
int create_canvas (int width, int height)
{
if (pData == NULL) {
pData = (char *) memalign (32, width * height);
printf("memalign success pData %p \n", pData);
} else {
char *ptr = (char *)realloc(pData, width * height);
if (ptr == NULL) {
printf("relloc failed and pData %p \n",pData);
//pData = NULL; //加上这句就不会崩溃了
} else {
pData = ptr;
printf("relloc success pData %p \n", pData);
}
}
}
int main()
{
int count = 0;
while (++count < 5) {
printf("count %d and ",count);
if (1 == count)
create_canvas(1024,768);
else if (2 == count)
create_canvas(0,0);
else
create_canvas(1024,768);
sleep(1);
//Sleep(1000); windows 下用这个
}
return 0;
}
从执行结果来看,count == 2 时,即width * height = 0,导致realloc失败,这是pData 会被free 掉,但是我们发现它的地址仍然是0x7fe9961cd040 并没有被赋值为NULL,所以成了前面说的野指针;这样当count == 3时,就会导致realloc 发生段错误(SIGNAL 11)而产生崩溃。
从core文件程序崩溃的堆栈信息来看,最后是调用realloc发生了错误,也验证了我们前面分析的内容。