重新发一贴,关于上一篇 C++ 代码中,结构体析构导致内存异常的问题,我做了更多的测试 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
villivateur
1.56D
V2EX    C++

重新发一贴,关于上一篇 C++ 代码中,结构体析构导致内存异常的问题,我做了更多的测试

  •  
  •   villivateur 2023-05-19 12:22:57 +08:00 3166 次点击
    这是一个创建于 908 天前的主题,其中的信息可能已经有所发展或是发生改变。

    上一篇是 https://v2ex.com/t/941007

    结构体定义:

    struct ModuleConfig { ModuleConfig() { printf("ModuleConfig::constructor\n"); } ~ModuleConfig() { printf("ModuleConfig::destructor\n"); } uint32_t identity; std::string pdoMapName; uint32_t pdoMapInOffset; uint32_t pdoMapOutOffset; }; 

    调用并崩溃的代码:

    void ESI_SetModuleIdentities(int slaveId, std::vector<uint32_t>& moduleIdentities) { ModuleConfig* newModule = new ModuleConfig; printf("line: %d\n", __LINE__); // 917 newModule->identity = 243423; printf("EEEEE %d\n", newModule->identity); delete newModule; printf("line: %d\n", __LINE__); // 921 newModule = NULL; printf("line: %d\n", __LINE__); // 923 SlaveFileConfig* cOnfig= database[slaveId]; config->SetModuleIdentities(moduleIdentities); } 

    直接运行后的打印输出:

    ModuleConfig::constructor line: 917 EEEEE 243423 ModuleConfig::destructor 

    (只有这么多,打完这些就崩溃)

    使用 valgrind 调试,相关信息如下:

    ModuleConfig::constructor line: 917 EEEEE 243423 ModuleConfig::destructor ==8820== Conditional jump or move depends on uninitialised value(s) ==8820== at 0x49D65CE: std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28) ==8820== by 0x170615: ModuleConfig::~ModuleConfig() (SlaveConfigParser.h:26) ==8820== by 0x17F992: ESI_SetModuleIdentities(int, std::vector<unsigned int, std::allocator<unsigned int> >&) (SlaveConfigParser.cpp:920) ==8820== by 0x161637: EcatAdapter::InitSlaveFileConfig() (EcatAdapter.cpp:1321) ==8820== by 0x15F583: EcatAdapter::ConfigSlaves() (EcatAdapter.cpp:762) ==8820== by 0x15DB78: EcatAdapter::Connect() (EcatAdapter.cpp:247) ==8820== by 0x15C4D4: AdapterManager::Connect() (AdapterManager.cpp:250) ==8820== by 0x15C0F6: AdapterManager::Run() (AdapterManager.cpp:151) ==8820== by 0x15CA7F: StartAdapter (CExport.cpp:22) ==8820== by 0x18F8CA: main (main.c:5) ==8820== Uninitialised value was created by a heap allocation ==8820== at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==8820== by 0x17F939: ESI_SetModuleIdentities(int, std::vector<unsigned int, std::allocator<unsigned int> >&) (SlaveConfigParser.cpp:916) ==8820== by 0x161637: EcatAdapter::InitSlaveFileConfig() (EcatAdapter.cpp:1321) ==8820== by 0x15F583: EcatAdapter::ConfigSlaves() (EcatAdapter.cpp:762) ==8820== by 0x15DB78: EcatAdapter::Connect() (EcatAdapter.cpp:247) ==8820== by 0x15C4D4: AdapterManager::Connect() (AdapterManager.cpp:250) ==8820== by 0x15C0F6: AdapterManager::Run() (AdapterManager.cpp:151) ==8820== by 0x15CA7F: StartAdapter (CExport.cpp:22) ==8820== by 0x18F8CA: main (main.c:5) ==8820== ==8820== Conditional jump or move depends on uninitialised value(s) ==8820== at 0x483CF75: operator delete(void*) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==8820== by 0x170615: ModuleConfig::~ModuleConfig() (SlaveConfigParser.h:26) ==8820== by 0x17F992: ESI_SetModuleIdentities(int, std::vector<unsigned int, std::allocator<unsigned int> >&) (SlaveConfigParser.cpp:920) ==8820== by 0x161637: EcatAdapter::InitSlaveFileConfig() (EcatAdapter.cpp:1321) ==8820== by 0x15F583: EcatAdapter::ConfigSlaves() (EcatAdapter.cpp:762) ==8820== by 0x15DB78: EcatAdapter::Connect() (EcatAdapter.cpp:247) ==8820== by 0x15C4D4: AdapterManager::Connect() (AdapterManager.cpp:250) ==8820== by 0x15C0F6: AdapterManager::Run() (AdapterManager.cpp:151) ==8820== by 0x15CA7F: StartAdapter (CExport.cpp:22) ==8820== by 0x18F8CA: main (main.c:5) ==8820== Uninitialised value was created by a heap allocation ==8820== at 0x483BE63: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==8820== by 0x17F939: ESI_SetModuleIdentities(int, std::vector<unsigned int, std::allocator<unsigned int> >&) (SlaveConfigParser.cpp:916) ==8820== by 0x161637: EcatAdapter::InitSlaveFileConfig() (EcatAdapter.cpp:1321) ==8820== by 0x15F583: EcatAdapter::ConfigSlaves() (EcatAdapter.cpp:762) ==8820== by 0x15DB78: EcatAdapter::Connect() (EcatAdapter.cpp:247) ==8820== by 0x15C4D4: AdapterManager::Connect() (AdapterManager.cpp:250) ==8820== by 0x15C0F6: AdapterManager::Run() (AdapterManager.cpp:151) ==8820== by 0x15CA7F: StartAdapter (CExport.cpp:22) ==8820== by 0x18F8CA: main (main.c:5) ==8820== ==8820== Invalid free() / delete / delete[] / realloc() ==8820== at 0x483CFBF: operator delete(void*) (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_memcheck-amd64-linux.so) ==8820== by 0x170615: ModuleConfig::~ModuleConfig() (SlaveConfigParser.h:26) ==8820== by 0x17F992: ESI_SetModuleIdentities(int, std::vector<unsigned int, std::allocator<unsigned int> >&) (SlaveConfigParser.cpp:920) ==8820== by 0x161637: EcatAdapter::InitSlaveFileConfig() (EcatAdapter.cpp:1321) ==8820== by 0x15F583: EcatAdapter::ConfigSlaves() (EcatAdapter.cpp:762) ==8820== by 0x15DB78: EcatAdapter::Connect() (EcatAdapter.cpp:247) ==8820== by 0x15C4D4: AdapterManager::Connect() (AdapterManager.cpp:250) ==8820== by x15C0F6: AdapterManager::Run() (AdapterManager.cpp:151) ==8820== by 0x15CA7F: StartAdapter (CExport.cpp:22) ==8820== by 0x18F8CA: main (main.c:5) ==8820== Address 0x4ff42e800000000 is not stack'd, malloc'd or (recently) free'd ==8820== line: 921 line: 923 

    环境:g++ 9.4.0 / Ubuntu 20.04 / c++11

    std::string pdoMapName 改成 std::string pdoMapName{} 也不行

    第 1 条附言    2023-05-19 13:47:37 +08:00

    修改结构体:

    struct ModuleConfig { ModuleConfig() { printf("ModuleConfig::constructor\n"); printf("%p %p\n", this, &pdoMapName); } ~ModuleConfig() { printf("ModuleConfig::destructor\n"); printf("%p %p\n", this, &pdoMapName); } uint32_t identity; std::string pdoMapName; uint32_t pdoMapInOffset; uint32_t pdoMapOutOffset; }; 

    打印:

    ModuleConfig::constructor 0x55d602b78670 0x55d602b78678 line: 916 EEEEE 243423 ModuleConfig::destructor 0x55d602b78670 0x55d602b78674 

    震惊!构造函数和析构函数里的变量地址不一样!

    第 2 条附言    2023-05-19 14:31:43 +08:00
    查出来了,如上一帖 @lovelylain 所说的一样,在调用栈上层的上层的源文件包含的一个头文件里,存在 #pragma pack(push, 1) 但是忘了 pop 。

    之前居然没有出过问题……也可能之前偶发的 bug 跟这个有关。

    感谢所有人的热心
    28 条回复    2023-05-22 15:54:06 +08:00
    yolee599
        1
    yolee599  
       2023-05-19 12:58:46 +08:00 via Android
    你重新起一个只有这部分的代码,其他代码全删掉的工程看看呢?
    hefish
        2
    hefish  
       2023-05-19 13:00:13 +08:00
    看起来,结构体里面的 std::string 是个奇怪的东西。。。
    liuguangxuan
        3
    liuguangxuan  
       2023-05-19 13:05:28 +08:00
    可以按 1#说的,起一个单独的工程,把问题复现出来,发出来让大家调试一下。
    cnbatch
        4
    cnbatch  
       2023-05-19 13:09:34 +08:00
    旧版本的 lisbtdc++ 有 bug ,表现为 std::string 析构出错:
    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82172

    这个 bug report 里面的版本是 libstdc++.so.6 ,而你给出的 error log 刚好也是链接到 libstdc++.so.6
    villivateur
        5
    villivateur  
    OP
       2023-05-19 13:37:25 +08:00
    @yolee599 只有这部的代码是正常的

    $ cat header.h
    #pragma once

    #include <stdio.h>
    #include <stdint.h>
    #include <string>

    struct ModuleConfig
    {
    ModuleConfig()
    {
    printf("ModuleConfig::constructor\n");
    }

    ~ModuleConfig()
    {
    printf("ModuleConfig::destructor\n");
    }

    uint32_t identity;
    std::string pdoMapName;
    uint32_t pdoMapInOffset;
    uint32_t pdoMapOutOffset;
    };

    $ cat main.cpp
    #include "header.h"

    int main()
    {
    ModuleConfig* newModule = new ModuleConfig;
    printf("line: %d\n", __LINE__);
    newModule->identity = 243423;
    printf("EEEEE %d\n", newModule->identity);
    delete newModule;
    printf("line: %d\n", __LINE__);
    newModule = NULL;
    printf("line: %d\n", __LINE__);

    return 0;
    }
    villivateur
        6
    villivateur  
    OP
       2023-05-19 13:43:24 +08:00
    @cnbatch Ubuntu20.04 已经确定修复了这个 bug
    roycestevie6761
        7
    roycestevie6761  
       2023-05-19 13:52:27 +08:00
    用 clang 或者其他编译器试一下不就行了
    leonshaw
        8
    leonshaw  
       2023-05-19 14:05:51 +08:00
    地址不一样只能怀疑编译器了,反汇编出来看看
    xiatwhu
        9
    xiatwhu  
       2023-05-19 14:13:53 +08:00
    是不是构造函数和析构函数不在一个源文件里面。比如构造函数在 a.cpp 里面,析构函数在 b.cpp 里面。编译 a.cpp 和 b.cpp 的时候传的编译选项不一样,导致 a 和 b 里面看到的 ModuleConfig 声明实际上不一样。重点检查一下 -fno-exceptions, -fno-rtti 编译选项。
    xiatwhu
        10
    xiatwhu  
       2023-05-19 14:20:19 +08:00
    也可能构造函数和析构函数都把实现放在了头文件里面。在 a.cpp 和 b.cpp 里面都包含了这个头文件,然后编译 a.cpp 和 b.cpp 的时候使用了不同的编译选项导致两个源码中看到的 ModuleConfig 对象声明实际上不一致。然后在 a.cpp 的里面 new 了 ModuleConfig 对象, 在 b.cpp 里面 delete ModuleConfig 对象。最后链接生成的可执行文件运行时就可能会报错。
    leonshaw
        11
    leonshaw  
       2023-05-19 14:22:17 +08:00
    @xiatwhu 有道理,看起来是两边对齐不一样。
    kkhaike
        12
    kkhaike  
       2023-05-19 14:28:20 +08:00
    你不给完整代码,没法帮你排查。首先你也测试了提出的代码是没问题的。

    c++的莫名问题,有时候都是和崩溃地方不相关的地方引起的,特别是 stackoverflow 这种
    villivateur
        13
    villivateur  
    OP
       2023-05-19 14:32:23 +08:00
    @xiatwhu
    @leonshaw
    @kkhaike

    感谢回复,已经排查到,见附言
    tomychen
        14
    tomychen  
       2023-05-19 14:38:26 +08:00
    testcpp g++ -o def main.cpp
    testcpp ./def
    ModuleConfig::constructor
    0x2587c20 0x2587c28
    line: 5
    EEEEE 243423
    ModuleConfig::destructor
    0x2587c20 0x2587c28
    line: 9
    line: 11
    g++ (GCC) 4.8.5 20150623 (Red Hat 4.8.5-44)
    Copyright (C) 2015 Free Software Foundation, Inc.



    /usr/local/bin/g++ --version
    g++ (GCC) 9.2.0
    Copyright (C) 2019 Free Software Foundation, Inc.
    This is free software; see the source for copying conditions. There is NO
    warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

    testcpp /usr/local/bin/g++ main.cpp -o g9x
    testcpp ./g9x
    ModuleConfig::constructor
    0x19fbc20 0x19fbc28
    line: 5
    EEEEE 243423
    ModuleConfig::destructor
    0x19fbc20 0x19fbc28
    line: 9
    line: 11

    clang++ --version
    clang version 10.0.1 ( https://github.com/llvm/llvm-project.git ef32c611aa214dea855364efd7ba451ec5ec3f74)
    Target: x86_64-unknown-linux-gnu
    Thread model: posix
    InstalledDir: /usr/local/clang-10/bin
    testcpp ./ll
    ModuleConfig::constructor
    0x2262c20 0x2262c28
    line: 5
    EEEEE 243423
    ModuleConfig::destructor
    0x2262c20 0x2262c28
    line: 9
    line: 11


    感觉,你可以换台机器测测了...
    ohwind
        15
    ohwind  
       2023-05-19 14:50:47 +08:00
    > 存在 #pragma pack(push, 1) 但是忘了 pop 。

    我觉得这编译器应该给警告出来
    villivateur
        16
    villivateur  
    OP
       2023-05-19 14:52:14 +08:00
    @ohwind 然而并没有……
    saturn7
        17
    saturn7  
       2023-05-19 15:32:56 +08:00
    都使用到 c++, 还要魔改编译器结构体内存对齐,真没必要,浪费时间。
    villivateur
        18
    villivateur  
    OP
       2023-05-19 15:46:57 +08:00
    @saturn7 嵌入式设备,要做硬件通讯,没办法
    newmlp
        19
    newmlp  
       2023-05-19 16:07:05 +08:00
    @ohwind msvc 会有警告...巨硬牛逼!
    loveumozart
        20
    loveumozart  
       2023-05-19 16:16:25 +08:00
    写 c++太辛苦。。。排查这么一个 bug 都到编译器水平了,这鸟行业还 35 优化,程序员太苦逼了
    cnbatch
        21
    cnbatch  
       2023-05-19 17:01:28 +08:00 via Android
    其实可以换个没这种 bug 的编译器吧,嵌入式设备的环境又不是必须固定使用 Ubuntu 20.04
    Rothschild
        22
    Rothschild  
       2023-05-19 17:30:01 +08:00
    @saturn7 恰恰是 c++需要的开发地方,对齐内存是很常见的事儿,这不算魔改算日常操作
    exch4nge
        23
    exch4nge  
       2023-05-19 17:42:50 +08:00
    按理来说这种情况编译器应该有 warning 的,我也没环境无法确定,也可能是用参数忽略掉了这种 warning ,也可能是编译过程有大量 warning 输出大家都不看。如果这版本 gcc 真没有(可能性很小)那建议升级或换 clang 。
    junmoxiao
        24
    junmoxiao  
       2023-05-19 18:14:47 +08:00
    @loveumozart 有一说一不是 c++的锅,自己改了内存对齐能怪谁呢
    saturn7
        25
    saturn7  
       2023-05-19 18:31:51 +08:00
    @Rothschild 都用到 stl ,class ,object 了,修改内存对齐肯定不是日常操作呀!!!正常项目代码编程时,注意减少对象内存复制,基本可以减少硬件 90%内存压力。如果真是要求内存极限使用场景,正确是做法是根据自己公司业务,用 C/C++封装一套自己的数据容器才是明智的做法。我认为用到 c++,还去结构体还要修改内存对齐的方式,肯定算是铤而走险做法。
    Rothschild
        26
    Rothschild  
       2023-05-19 19:09:09 +08:00
    @saturn7 这只能说明你压根就没在过什么 C++的组里干过,内存对齐在网络传输序列化反序列化以及嵌入式行业是必须的,而且无处不在,你在任何大点 c++项目的代码里都能看到这些
    saturn7
        27
    saturn7  
       2023-05-22 09:51:11 +08:00
    @Rothschild 用过 google protobuf2/3 ,modbus, focas, 没见过那一个协议官方文档建议开发者手动改 pragma 对齐。不过既然是用网络流通信了,发送 /接收和内存对齐有毛关系,通用做法不都是大端字节序编码?
    Rothschild
        28
    Rothschild  
       2023-05-22 15:54:06 +08:00
    @saturn7 所以说你见得太少了啊,你为什么以为大型项目里只是那点 crud 的代码?你不会在 github 里搜一下#pragma pack(push,1)看看用的多频繁?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2985 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 27ms UTC 12:57 PVG 20:57 LAX 04:57 JFK 07:57
    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