c++ double free or corruption (!prev)


最简单方法:

  if (p) {                                                                                                     \
          free(p);                                                                                                 \

          p = NULL;                                                                                           \

       }                             



最近在开发分布式系统的时候,出现了double free的问题,查了发现自己并没有doublefree,后来在网上查了一些资料,才发现原来是如下原因。

写了一个多线程的代码,在主线程中申请了一块内存,在另一个线程中释放内存,老出错误,不知道是怎么回事。

*** glibc detected *** /home/ydx/vehicle-program/vehicle-v1.0/t:double free or corruption (!prev): 0x080644c8***
=======Backtrace: =========
/lib/libc.so.6[0x7c7261]
/home/ydx/vehicle-program/vehicle-v1.0/t[0x804991e]
/home/ydx/vehicle-program/vehicle-v1.0/t[0x8048fb0]
/lib/libpthread.so.0[0x8dcab5]
/lib/libc.so.6(clone+0x5e)[0x83383e]

00759000-008c7000 r-xp 00000000 fd:00158969    /lib/libc-2.11.so
008c7000-008c8000---p 0016e000 fd:00158969    /lib/libc-2.11.so
008c8000-008ca000r--p 0016e000 fd:00158969    /lib/libc-2.11.so
008ca000-008cb000rw-p 00170000 fd:00158969    /lib/libc-2.11.so

查找了好长时间的原因,都没找到,最后找到的原因原因如下

1.并没有doublefree,这一点是肯定的

原因:

n=get_node(STATINFO_SIZE);//申请了一个160字节的区域

调用sprintf函数向这个区域写的字节数是远大于160字节的

释放的时候就会出现这种问题

可能sprintf打印的字节数超过160字节时,没有报错,释放的时候要全部释放,超过160字节,就报出了段错误

为了解决这种无意识的错误,以后要用这个函数

黑客通常利用这类脆弱的代码来入侵看上去安全的系统。要修正这一缺陷,可以使用函数 snprintf() 代替函数 sprintf() ,函数 snprintf() 的原型为:
intsnprintf(char*buf, size_t maxlen, const char* fmt,...); 
第二个参数定义能被写到 buf 中的字符的最大个数,不考虑格式标志以及源字符串的大小:     
snprintf(buf,10, "s",p); //now safef("hello world!"); //string is chopped to"hello worl" 

相似的,使用strncpy()strncmp()strncat()strnicmp() strnset() 相应地代替strcmp()strcat()stricmp() strset()

参考文章:http://blog.sina.com.cn/s/blog_8d6e07cf0101785q.html

*** glibc detected *** free(): invalid pointer:
*** glibc detected *** malloc(): memory corruption:
*** glibc detected *** double free or corruption (out): 0x00000000005c18a0 ***
*** glibc detected *** double free or corruption (!prev): 0x0000000000a01f40 ***
*** glibc detected *** corrupted double-linked list: 0x00000000005ab150 ***


你是否遇到过这样的情况,太沮丧了,程序总是无端coredump,gdb到core文件里面也看不出个所以然来,这对于一个大型的商业系统来说太令人恐怖了,事故随时可能发生。

遇到棘手的问题,慌张是没用的,解决不了任何问题。先坐下来,喝杯茶,舒缓一下神经。

内存问题始终是c++程序员需要去面对的问题,这也是c++语言的门槛较高的原因之一。通常我们会犯的内存问题大概有以下几种:

1.内存重复释放,出现double free时,通常是由于这种情况所致。
2.内存泄露,分配的内存忘了释放。
3.内存越界使用,使用了不该使用的内存。
4.使用了无效指针。
5.空指针,对一个空指针进行操作。

对于第一种和第二种,第五种情况,就不用多说,会产生什么后果大家应该都很清楚。

第四种情况,通常是指操作已释放的对象,如:
1.已释放对象,却再次操作该指针所指对象。
2.多线程中某一动态分配的对象同时被两个线程使用,一个线程释放了该对象,而另一线程继续对该对象进行操作。

我们重点探讨第三种情况,相对于另几种情况,这可以称得上是疑难杂症了(第四种情况也可以理解成内存越界使用)。

内存越界使用,这样的错误引起的问题存在极大的不确定性,有时大,有时小,有时可能不会对程序的运行产生影响,正是这种不易重现的错误,才是最致命的,一旦出错破坏性极大。

什么原因会造成内存越界使用呢?有以下几种情况,可供参考:
例1:
        char buf[32] = {0};
        for(int i=0; i 32
        {
            buf[i] = 'x';
        }
        ....       
例2:
        char buf[32] = {0};
        string str = "this is a test sting !!!!";
        sprintf(buf, "this is a test buf!string:%s", str.c_str()); //out of buffer space
        ....   
例3:
        string str = "this is a test string!!!!";
        char buf[16] = {0};
        strcpy(buf, str.c_str()); //out of buffer space
        
类似的还存在隐患的函数还有:strcat,vsprintf等
同样,memcpy, memset, memmove等一些内存操作函数在使用时也一定要注意。
        
当这样的代码一旦运行,错误就在所难免,会带来的后果也是不确定的,通常可能会造成如下后果:

1.破坏了堆中的内存分配信息数据,特别是动态分配的内存块的内存信息数据,因为操作系统在分配和释放内存块时需要访问该数据,一旦该数据被破坏,以下的几种情况都可能会出现。
        *** glibc detected *** free(): invalid pointer:
        *** glibc detected *** malloc(): memory corruption:
        *** glibc detected *** double free or corruption (out): 0x00000000005c18a0 ***
        *** glibc detected *** corrupted double-linked list: 0x00000000005ab150 ***        

2.破坏了程序自己的其他对象的内存空间,这种破坏会影响程序执行的不正确性,当然也会诱发coredump,如破坏了指针数据。

3.破坏了空闲内存块,很幸运,这样不会产生什么问题,但谁知道什么时候不幸会降临呢?

通常,代码错误被激发也是偶然的,也就是说之前你的程序一直正常,可能由于你为类增加了两个成员变量,或者改变了某一部分代码,coredump就频繁发生,而你增加的代码绝不会有任何问题,这时你就应该考虑是否是某些内存被破坏了。

排查的原则,首先是保证能重现错误,根据错误估计可能的环节,逐步裁减代码,缩小排查空间。
检查所有的内存操作函数,检查内存越界的可能。常用的内存操作函数:
sprintf snprintf
vsprintf vsnprintf
strcpy strncpy strcat
memcpy memmove memset bcopy

如果有用到自己编写的动态库的情况,要确保动态库的编译与程序编译的环境一致。

参考文章:http://www.cnblogs.com/zhenjing/archive/2011/01/26/memory_overwrite.html


还有一种情况:

先来段代码:

#include 
#include 

#define DEBUG() printf("lines: %d\n", __LINE__); fflush(stdout);

int main(void)
{
   void* p = malloc(100);
   DEBUG();

   //void* p2 = malloc(114);
   DEBUG();

   realloc(p, 200);
   DEBUG();

   realloc(p, 300);
   DEBUG();

   return 0;
}

gcc main.c -o main  && ./main

运行后无误,然后去掉

//void* p2 = malloc(114);
的注释,运行后会报错:
…………
*** glibc detected *** ./main: double free or corruption (!prev): 0x09211008 ***
======= Backtrace: =========
/lib/libc.so.6[0x7cd5a5]
/lib/libc.so.6[0x7cf7bc]
/lib/libc.so.6(realloc+0xe6)[0x7d0386]
./main[0x80484ec]
/lib/libc.so.6(__libc_start_main+0xdc)[0x779e9c]
./main[0x8048381]
======= Memory map: ========
00746000-00760000 r-xp 00000000 08:02 4196833    /lib/ld-2.5.so
00760000-00761000 r-xp 00019000 08:02 4196833    /lib/ld-2.5.so
…………
各种百思不得其解啊... 各种百度各种头疼后,突然顿悟,原来是realloc的原因。前几天看realloc的原型时,把
extern void *realloc(void *mem_address, unsigned int newsize);
看错成:
extern void realloc(void *mem_address, unsigned int newsize);
结果导致找了好久的错。


解决方法:

realloc(p, 200);
这一行应该改为
p = realloc(p, 200);
也就是说,malloc(114)使得原有的malloc后面的块不足以分配(200-100)大小的内存,于是realloc找到了一块新的内存,把原来的内存数据搬运到新的内存中,原内存在realloc内被释放掉了,而当运行第二次的realloc时,realloc内部再次进行 "找新内存 → 搬家 → free原内存” 的过程,因此出现了二次free的情况。

参考:https://my.oschina.net/bbdlg/blog/85387

你可能感兴趣的:(c++)