注意:这不是gcc/stl的bug。
例子程序如下:
inc.h
#include
#pragma pack(1)
typedef struct {
char a1;
char a2;
char a3;
char a4;
} record_t;
main.cc
#include "inc.h"
#include
/*
* A test program to evaluate the miss use of pragma pack
* leading to std::string's strange SEGV problem.
*/
int main(int argc, char** argv)
{
std::string filename("abc.txt");
std::string::size_type pos = filename.rfind('.');
std::string prefix = filename.substr(0, pos);
printf("prefix=%s\n", prefix.c_str());
return 0;
}
Makefile
CXXFLAGS=-g -O2
all: testpack
.PHONY: all clean
%.o: %.cc
$(CXX) $(CXXFLAGS) -c -o $@ $<
testpack: main.o
$(CXX) -o testpack -O2 main.o
clean:
rm testpack main.o -rf
在linux环境下,gcc3.3.3以上版本,编译运行,会出现以下错误:
prefix=abc
*** glibc detected *** /home/aaa/testpack/testpack: free(): invalid pointer: 0x0000000000601044 ***
======= Backtrace: =========
/lib64/libc.so.6[0x3b7a87247f]
/lib64/libc.so.6(cfree+0x4b)[0x3b7a8728db]
/home/aaa/testpack/testpack(__gxx_personality_v0+0x1c5)[0x4008cd]
/lib64/libc.so.6(__libc_start_main+0xf4)[0x3b7a81d994]
/home/aaa/testpack/testpack(__gxx_personality_v0+0x71)[0x400779]
... (下略)
不加-O2参数,该错误不出现。
重新运行,调试过程:
(gdb) r
Starting program: /home/aaa/testpack/testpack
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x2aaaaaaab000
prefix=abc
*** glibc detected *** /home/aaa/testpack/testpack: free(): invalid pointer: 0x0000000000601044 ***
(gdb) f 7
#7 main (argc=
(gdb) p prefix
$1 = {static npos = 18446744073709551615, _M_dataplus = {
_M_p = 0x601058 "abc"}}
(释放prefix的_M_dataplus指向的区域时出现问题。看一下代码:)
(gdb) f 6
#6 ~basic_string (this=
478 { _M_rep()->_M_dispose(this->get_allocator()); }
(看一下_M_rep()的实现:)
(gdb) list 281
276
277 _CharT*
278 _M_data(_CharT* __p)
279 { return (_M_dataplus._M_p = __p); }
280
281 _Rep*
282 _M_rep() const
283 { return &((reinterpret_cast<_Rep*> (_M_data()))[-1]); }
284
285 // For the internal use we have functions similar to `begin'/`end'
(看一下_M_data()的实现:)
(gdb) list 273
268
269 private:
270 // Data Members (private):
271 mutable _Alloc_hider _M_dataplus;
272
273 _CharT*
274 _M_data() const
275 { return _M_dataplus._M_p; }
276
(获取的_Rep*指针应为_M_dataplus._M_p减去一个_Rep结构体大小。看一下_Rep结构体大小:)
(gdb) p sizeof(std::basic_string
$4 = 20
(看一下这个结构体的详细信息:)
(gdb) ptype std::basic_string
type = struct std::basic_string
: public std::basic_string
static const size_t _S_max_size;
static const char _S_terminal;
static size_t _S_empty_rep_storage[3];
public:
static std::basic_string
bool _M_is_leaked(void) const;
bool _M_is_shared(void) const;
void _M_set_leaked(void);
void _M_set_sharable(void);
void _M_set_length_and_sharable(unsigned long);
char * _M_refdata(void);
char * _M_grab(const std::allocator
static std::basic_string
void _M_dispose(const std::allocator
void _M_destroy(const std::allocator
char * _M_refcopy(void);
char * _M_clone(const std::allocator
}
(继承的部份应该没有占空间,看一下基类:)
(gdb) ptype std::basic_string
type = struct std::basic_string
size_t _M_length;
size_t _M_capacity;
_Atomic_word _M_refcount;
}
(因为是64位系统,所以前面两个size_t加起来是16位,后面一个_Atomic_word,经过在/usr/include下面grep,发现定义为int型,那么在当前x86_64的ABI下仍为4字节,加起来20字节……等一下!考虑对齐的话,最后一个int应该也占8字节,所以加起来应该是24字节,不是吗?为什么gdb显示20字节?)
(我们来检查一下倒底-24还是-20是这个真正的结构体:)
(gdb) p *(std::basic_string
$9 = {_M_length = 12884901888, _M_capacity = 0, _M_refcount = -1}
(gdb) p *(std::basic_string
$10 = {_M_length = 3, _M_capacity = 3, _M_refcount = 0}
(很显然我们的prefix存放的"abc"应该是后者。等一下,结尾的'\0'没有占空间?这个应该是分配的时候多分配了1个字节。capacity应该是没有包含terminating zero的。)
因此,该错误的直接原因在于,本程序的std::string在释放时,通过字符串位置计算字符串头部数据(一个共享的string data区域)时,把头部数据的大小搞错了。
再次注意:这肯定不是std::string的bug。你可以看到,inc.h里面有一句#pragma pack(1),这是相当糟糕的写法。没有#pragma pack(push, 1)和#pragma pop(),造成main.cc在引用时首先包含了这句#pragma pack,然后再引用stl的string头文件。那么在string头文件里面编译进目标文件的代码,会认为sizeof(_Rep_base)是20,而没有包含这个inc.h的代码(libc.so.6)会认为它是24。而stl的实现部份由内联函数直接在exe中展开,部份由libc.so.6预先提供,所以就会造成malloc/free计算出的地址不配对的问题。
关于stl的string为什么要用一块共享的string data实现,可以自行参考相关文档。
最后再次强调一下,这不是gcc的bug,也不是stl的bug,是人为灾害。
本文的目的在于,如果你遇到类似的错误而找不到原因时,可以参考检查。(当然,另外一个更可能的原因是你使用了包含多个gcc版本的动态库,你应当首先排除这个原因)
另外你或许可以从本文中学到一些调试技巧。
[完]