我明白了,我明白了为什么在(3)中发现了两处内存泄漏!先回顾一下(3)中的第二处泄漏的call stack, 注意这个stack是经过C++ 的符号mangle的,可读性很差,我首先用gc++filt处理一下:
> 080a7300$<bufctl_audit ! gc++filt
ADDR BUFADDR TIMESTAMP THREAD
CACHE LASTLOG CONTENTS
80a7300 80a4fc0 d073d324dabb8 1
8090390 8075064 0
libumem.so.1`umem_cache_alloc_debug+0x16c
libumem.so.1`umem_cache_alloc+0xe1
libumem.so.1`umem_alloc+0x3f
libumem.so.1`malloc+0×23
libstdc++.so.6`operator new(unsigned int)+0×25
libstdc++.so.6`std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_Rep::_S_create(unsigned int, unsigned int, std::allocator<char> const&)+0×43
libstdc++.so.6`std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_mutate(unsigned int, unsigned int, unsigned int)+0x5a
libstdc++.so.6`std::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_replace_safe(unsigned int, unsigned int, char const*, unsigned int)+0×28
libstdc++.so.6`std::basic_string<char, std::char_traits<char>, std::allocator<char> >::assign (char const*, unsigned int)+0x3f
libstdc++.so.6`std::basic_string<char, std::char_traits<char>, std::allocator<char> >::operator= (char const*)+0x2e
Foo::Foo(int) +0x5e
main+0×44
_start+0×80
略显恐怖哦-:)不要怕,我已经把关键部分用粗体标记出来了,可见这个call stack对应了源代码里面的 m_IDname=buf; 使用一个指向字符串的指针为std::string赋值。std::string的 operator=(char const*)的默认行为是deep copy,所谓deep copy是指要自己分配一块内存用于存放要存储的字符串。
在(3)中libumeme报的第一出内存泄漏根据计算是8个字节,计算的方法我在(2)中介绍数据结构的时候已经提到过了,这里不再熬述。这8个字节中4个字节是虚表指针,另外4字节是std::string本身所占的内存,本质上就是一根指针。这根指针指向真正存储字符串的内存。问题就在这里!
libumem报的第一处内存泄漏并不包含真正存储字符串的那块buffer!!
我们来验证一下,首先我们知道,一共分配了100个Foo实例,释放了99个,那另外的99个实例的内存分配情况如何呢?
> ::umalog ! less
T-0.000000000 addr=809dfc0 umem_alloc_16
libumem.so.1`umem_cache_free_debug+0×135
libumem.so.1`umem_cache_free+0x3f
libumem.so.1`umem_free+0xd5
libumem.so.1`process_free+0xfd
libumem.so.1`free+0×14
libstdc++.so.6`_ZdlPv+0×21
_ZN3FooD0Ev+0×36
main+0x8f
_start+0×80
T-0.000000685 addr=80a4f80 umem_alloc_48
libumem.so.1`umem_cache_free_debug+0×135
libumem.so.1`umem_cache_free+0x3f
libumem.so.1`umem_free+0xd5
libumem.so.1`process_free+0xfd
libumem.so.1`free+0×14
libstdc++.so.6`_ZdlPv+0×21
libstdc++.so.6`_ZNSs4_Rep10_M_destroyERKSaIcE+0x1b
libstdc++.so.6`_ZNSsD1Ev+0x4d
_ZN3FooD0Ev+0x1f
main+0x8f
_start+0×80
T-0.000001421 addr=80a4f80 umem_alloc_48
libumem.so.1`umem_cache_alloc_debug+0x16c
libumem.so.1`umem_cache_alloc+0xe1
libumem.so.1`umem_alloc+0x3f
libumem.so.1`malloc+0×23
libstdc++.so.6`_Znwj+0×25
libstdc++.so.6`_ZNSs4_Rep9_S_createEjjRKSaIcE+0×43
libstdc++.so.6`_ZNSs9_M_mutateEjjj+0x5a
libstdc++.so.6`_ZNSs15_M_replace_safeEjjPKcj+0×28
libstdc++.so.6`_ZNSs6assignEPKcj+0x3f
libstdc++.so.6`_ZNSsaSEPKc+0x2e
_ZN3FooC1Ei+0x5e
main+0×44
_start+0×80
T-0.000002402 addr=809dfc0 umem_alloc_16
libumem.so.1`umem_cache_alloc_debug+0x16c
libumem.so.1`umem_cache_alloc+0xe1
libumem.so.1`umem_alloc+0x3f
libumem.so.1`malloc+0×23
libstdc++.so.6`_Znwj+0×25
main+0×30
_start+0×80
上面显示了一个Foo实例从new到delete的完整的生命周期。一共牵涉到4次内存操作:分配Foo实例内存–>分配m_IDname所管理的用于真正存储字符串的内存–>释放字符串内存–>释放Foo实例。
我们来看看第二处泄漏内存的dump情况:
> 080a4fc0/16X
0x80a4fc0: 2a 3a10bfd6 15 15 0
(地址是80a4fd4 -> ) 206f6f46 656a624f 77207463 20687469 353d4449
babb0030 baddcafe feedface 292f 80a7300
a91abbed
我用斜体标出了User Data Section的内容,老实讲我不记得 std::string内部在管理字符串是什么样的数据结构了,我也不想去挖代码。但是有一点注意了,注意我用蓝颜色标注的部分,这个部分的起始地址是 0x80a4fc0+0×14 = 0x80a4fd4。0x80a4fd4是什么?就是第一处内存泄漏的Foo实例中std::string的第一个4字节指针:
> 0809dfe0/8X –> 0x0809dfe0 就是第一出泄漏buffer的起始地址
0x809dfe0: 10 3a10bff0 8050e30 80a4fd4 feedfabb
fb1 80a3200 a91afaed
> 80a4fd4/S
0x80a4fd4: Foo Object with ID=50
现在清楚了,上面蓝色部分的memory dump就是 "Foo Object with ID=50"的Ascii码。还记得字符’0′的ASCII是多少吗?是30!看看0xbb前最后的两个字节 0×30 0×00 正好是 ’0′和”。
好了,内存泄漏介绍完了,其实主要还是看经验,要了解自己的代码和数据结构,才能有效的使用像mdb这样比较底层的工具,有比较好的作用。下一篇我会介绍如何使用libumem+mdb处理 memory corruption。