C++什么情况下会出现类 static 成员析构错误? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
zhiqiang
V2EX    C

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

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

    我的一个程序,其中一个类有一个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     Solana     5275 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 07:13 PVG 15:13 LAX 23:13 JFK 02:13
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86