V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zhiqiang
V2EX  ›  C

C++什么情况下会出现类 static 成员析构错误?

  •  
  •   zhiqiang · 2018-12-04 08:01:46 +08:00 · 3811 次点击
    这是一个创建于 1941 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我的一个程序,其中一个类有一个static const std::vector<std::string>static成员,在程序结束时__run_exit_handlers里面出现该成员的析构错误。

    gdb 错误信息如下:

    #0  0x00007ffff6a426c3 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
    #1  0x000000000058e63d in std::_Destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > (__pointer=0x99f1f0) at /usr/include/c++/5/bits/stl_construct.h:93
    #2  0x000000000058dfcd in std::_Destroy_aux<false>::__destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*> (__first=0x99f1f0, __last=0x99f370)
        at /usr/include/c++/5/bits/stl_construct.h:103
    #3  0x000000000058d890 in std::_Destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*> (__first=0x99f1f0, __last=0x99f370)
        at /usr/include/c++/5/bits/stl_construct.h:126
    #4  0x000000000058cfd3 in std::_Destroy<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > (__first=0x99f1f0, __last=0x99f370) at /usr/include/c++/5/bits/stl_construct.h:151
    #5  0x000000000058bf15 in std::vector<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::allocator<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > > >::~vector (this=0x8e6730 <expr_parser::Token::type_descriptions[abi:cxx11]>, __in_chrg=<optimized out>) at /usr/include/c++/5/bits/stl_vector.h:424
    #6  0x00007ffff5c34ff8 in __run_exit_handlers (status=0, listp=0x7ffff5fbf5f8 <__exit_funcs>, run_list_atexit=run_list_atexit@entry=true) at exit.c:82
    #7  0x00007ffff5c35045 in __GI_exit (status=<optimized out>) at exit.c:104
    #8  0x00007ffff5c1b837 in __libc_start_main (main=0x577548 <main()>, argc=1, argv=0x7fffffffdd88, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>,
        stack_end=0x7fffffffdd78) at ../csu/libc-start.c:325
    #9  0x00000000005773e9 in _start ()
    
    第 1 条附言  ·  2018-12-04 16:49:43 +08:00
    最后解决方法:

    在所有的`static`成员变量前面加上 `__attribute__ ((visibility ("hidden")))`即可。
    28 条回复    2018-12-05 08:39:56 +08:00
    zmj1316
        1
    zmj1316  
       2018-12-04 08:05:15 +08:00 via Android
    你程序执行的时候把里面的 string 写坏了就挂了,比如强行把它析构掉或者把它内存 free 了什么的。。。
    zhiqiang
        2
    zhiqiang  
    OP
       2018-12-04 08:10:55 +08:00
    @zmj1316 类的 static 变量应该放在单独的内存位置,其析构是程序在结束时自动析构的。我没听说可以主动析构。

    写坏这个我就不清楚了,不知道用什么东西可以查出来。我用 valgrind 也是到上面显示的地方才出错,前面没有提示 double free 之类的错误。
    geelaw
        3
    geelaw  
       2018-12-04 08:23:45 +08:00 via iPhone
    典型的 psychic debugging 问题,楼主甚至连错误类型都没贴,甚至连出错行附近的代码(我是指 STL 实现的部分)的代码都没贴,也没有 minimal reproduction。

    现在是超能力表演时间(很容易失败):你可能想要检查自己有没有不小心踩了不是自己分配的内存。
    zynlp
        4
    zynlp  
       2018-12-04 08:27:44 +08:00 via iPhone
    你是想让我们猜呢还是猜呢
    mingl0280
        5
    mingl0280  
       2018-12-04 08:36:19 +08:00 via Android
    根据这个错误我怀疑题主在删除 vector 之前已经删除了 vector 里面的 string
    zhiqiang
        6
    zhiqiang  
    OP
       2018-12-04 08:41:23 +08:00
    @geelaw 这是一个超大型程序,里面有好几个 so 文件。就这个问题而言,我不确定有 minimal reproduction。这种在`_exit`时出现问题的情况我还是第一次遇到,`gdb`里显示的代码都不是我的代码。

    错误类型就是典型的 core dump。

    我也感觉是 double free 或者类似的问题。但我用 valgrind 没检查出来。在这里想问问可能是什么原因,以及用什么工具可以检查到这个错误。
    k9982874
        7
    k9982874  
       2018-12-04 09:04:41 +08:00
    你这是析构字符串的时候崩溃了,就像前面讨论的,字符串的内存出现问题。
    被提前析构了,或者越界了。
    geelaw
        8
    geelaw  
       2018-12-04 09:32:48 +08:00
    @zhiqiang #6 “踩到”是指写别的内存的时候下标越界,导致 std::string 的内存被破坏。

    即使不是你的代码,知道是释放内存还是修改内存的时候出问题也是有帮助的。

    很复杂的程序不代表不可以简化——你可以把整个 project 复制一份,然后慢慢删除。
    zmj1316
        9
    zmj1316  
       2018-12-04 09:55:53 +08:00
    @zhiqiang vector 是 static 的,里面的 string 可不是,LS 也说了,有可能是在运行的时候改了 vector 里面的 string,比如拿了 c_str 然后 free 这种...
    zhiqiang
        10
    zhiqiang  
    OP
       2018-12-04 10:30:20 +08:00
    @zmj1316 拿 c_str 去 free 不太可能,这个变量里面保存的是字符常量。可能是删除或修改别的导致堆内存损害。
    hitmanx
        11
    hitmanx  
       2018-12-04 10:38:45 +08:00   ❤️ 1
    感觉像是 corrupted data ?可以设个 exithandler 在 exit 时把 strings print 出来看看是不是在退出前就已经被破坏了。或者你可以用个自己的 allocator 去进行分配,然后设置个内存断点之类
    zhiqiang
        12
    zhiqiang  
    OP
       2018-12-04 12:33:13 +08:00
    我把这个类静态成员`static const std::vector<std::string>`换成一个自定义的`statc const StaticMember`,然后在`StaticMember`的初始化和析构函数里打印出 log,包含 StaticMember 所在的地址(std::cout << (void*)this)。

    然后发现一个神奇的现象,它进行了 4 次初始化和多次析构!并且地址是同一个地址。

    ```
    init, addr:0x8e77a8
    init, addr:0x8e77a8
    init, addr:0x8e77a8
    init, addr:0x8e77a8
    deinit, addr:0x8e77a8
    deinit, addr:0x8e77a8
    ```

    我的程序结构大约这样的:1 个`exe`文件通过`dlopen`打开了若干个`so`文件,这些 so 文件都链接了`Token`类。这个`Token`类就是包含这个出错的静态成员的类。在编译时,我先把`Token`编译成了`token.a`,然后`exe`和这些`so`文件链接时都链接了`token.a`。

    另外,我这个程序以前是能跑的,但只要加一行很简单的代码(这行代码与引起问题的类完全无关),就会导致上面的问题。我才加的这一行代码可能引起了链接程序的一些变化。

    我试图简化程序,但还没复现出同样的结果。

    @geelaw @hitmanx @zmj1316
    geelaw
        13
    geelaw  
       2018-12-04 13:27:32 +08:00
    @zhiqiang #12 *nix 世界不用 exe 扩展名。不过这无关紧要。

    这里的问题是你的 .a 是静态链接的,但是即使这样也不应该有这样的现象。如果你不希望各个 so/bin 里面有自己的 Token,你应该动态链接 Token
    zhiqiang
        14
    zhiqiang  
    OP
       2018-12-04 13:30:07 +08:00
    我用 boost::stacktrace 把静态变量的初始化和析构的 stack 都打了出来。发现了问题。

    原始没出问题的代码:会有多次初始化和析构,但初始化和析构都用的是各自 dll 里面的代码。

    出问题的代码:有多次初始化和析构,但初始化和析构都用的是 exe 程序里面的代码。

    现在看,问题主要出现在上面链接的问题。不知道为什么,出问题的程序中,dll 在调用函数时,没用自己的实现,都用的 exe 主程序的实现。( dll 和 exe 在变异时都链接了 token.a,因此都包含了 Token 类的实现)。
    zhiqiang
        15
    zhiqiang  
    OP
       2018-12-04 13:33:41 +08:00
    @geelaw Token 只是其中一个类,这里面其实有很多东西,甚至有好几个.a,不太方便都做成.so再说用.so 就不能链接内联优化了。
    hitmanx
        16
    hitmanx  
       2018-12-04 14:25:50 +08:00
    @zhiqiang 你试试链接时加上-Bsymbolic,或者你已经加上了?
    zhiqiang
        17
    zhiqiang  
    OP
       2018-12-04 14:51:04 +08:00
    @hitmanx 我没加这个。不过我试了下加这个编译参数,还是会运行出错。

    我用 nm 命令检查了符号,发现原始没出问题的代码,exe 程序里没有`Token`类的实现,所有 so 文件在运行时都使用各自文件里的实现。

    但出问题的代码里,exe 程序里有 Token 类的实现,so 文件在运行时直接用了主程序里的实现。

    其中 so 文件我没有重新编译过,所以出问题是发现在运行而不是编译时的函数链接上。
    v2qwsdcv
        19
    v2qwsdcv  
       2018-12-04 16:21:04 +08:00
    如果不是 STL 的 bug,那么可能和我以前碰到的一个问题一样:由于源文件编码导致。你的源文件有的用 gbk 有的用 utf8. C++编译单元的编码和该编译单元的源文件相同。
    lcdtyph
        20
    lcdtyph  
       2018-12-04 16:43:56 +08:00 via iPhone
    @zhiqiang 最后链接 exe 时候不要链接那个静态库,让链接器直接从 so 里找 token 的符号
    zhiqiang
        21
    zhiqiang  
    OP
       2018-12-04 16:45:02 +08:00
    @lcdtyph 这不行的。不链编译通不过。
    lcdtyph
        22
    lcdtyph  
       2018-12-04 16:48:38 +08:00 via iPhone   ❤️ 1
    @zhiqiang 那你的编译器支持 hidden 属性吗,可以把整个 token 类标记成 hidden
    zhiqiang
        23
    zhiqiang  
    OP
       2018-12-04 16:50:18 +08:00
    @lcdtyph 我已经解决了,把那些 static 成员标记为 hidden 即可。
    zhiqiang
        24
    zhiqiang  
    OP
       2018-12-04 16:50:53 +08:00
    @lcdtyph 你是最接近答案的人 :)
    lcdtyph
        25
    lcdtyph  
       2018-12-04 16:52:13 +08:00 via iPhone
    @zhiqiang
    怎么会编译通不过呢,是 exec 引用了在动态库没引用的符号吗,
    另外你现在链接时库的顺序是什么,可以的话把那个静态库放在动态库的后面,这样链接器会先在 so 里找到符号
    lcdtyph
        26
    lcdtyph  
       2018-12-04 16:52:56 +08:00 via iPhone
    @zhiqiang 哈哈因为我自己的项目也出国这个情况
    zhiqiang
        27
    zhiqiang  
    OP
       2018-12-04 16:53:24 +08:00
    @lcdtyph so 文件是 dlopen 打开的,不是编译时链接。链接时只链接了.a 文件。

    现在看 dlopen 的 runtime link 有些难以琢磨的东西。
    czhou
        28
    czhou  
       2018-12-05 08:39:56 +08:00 via iPhone
    dlopen 有全局命名空间和独立命名空间,可以设置,多次打开相似或相同 so 它会重叠,也就是会多次调用同一个函数。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1752 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 34ms · UTC 16:36 · PVG 00:36 · LAX 09:36 · JFK 12:36
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.