关于std::string出现在_M_dispose发生SIGABRT错误的问题

注意:这不是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=, argv=) at main.cc:15

(gdb) p prefix
$1 = {static npos = 18446744073709551615, _M_dataplus = {> = {<__gnu_cxx::new_allocator> = {}, },
    _M_p = 0x601058 "abc"}}


(释放prefix的_M_dataplus指向的区域时出现问题。看一下代码:)

(gdb) f 6
#6  ~basic_string (this=, __in_chrg=) at /usr/lib/gcc/x86_64-redhat-linux/4.1.2/../../../../include/c++/4.1.2/bits/basic_string.h:478
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, std::allocator >::_Rep)
$4 = 20

(看一下这个结构体的详细信息:)

(gdb) ptype std::basic_string, std::allocator >::_Rep
type = struct std::basic_string, std::allocator >::_Rep
        : public std::basic_string, std::allocator >::_Rep_base {
    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, std::allocator >::_Rep & _S_empty_rep(void);
    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 &, const std::allocator &);
    static std::basic_string, std::allocator >::_Rep * _S_create(unsigned long, unsigned long, const std::allocator &);
    void _M_dispose(const std::allocator &);
    void _M_destroy(const std::allocator &);
    char * _M_refcopy(void);
    char * _M_clone(const std::allocator &, unsigned long);
}
(继承的部份应该没有占空间,看一下基类:)

(gdb) ptype std::basic_string, std::allocator >::_Rep_base
type = struct std::basic_string, std::allocator >::_Rep_base {
    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, std::allocator >::_Rep_base*)(0x601058-20)
$9 = {_M_length = 12884901888, _M_capacity = 0, _M_refcount = -1}
(gdb) p *(std::basic_string, std::allocator >::_Rep_base*)(0x601058-24)
$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版本的动态库,你应当首先排除这个原因)

另外你或许可以从本文中学到一些调试技巧。


[完]




你可能感兴趣的:(关于std::string出现在_M_dispose发生SIGABRT错误的问题)