上一篇是 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 0x15C0F6: 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{}
也不行
修改结构体:
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
震惊!构造函数和析构函数里的变量地址不一样!
1
yolee599 2023-05-19 12:58:46 +08:00 via Android
你重新起一个只有这部分的代码,其他代码全删掉的工程看看呢?
|
2
hefish 2023-05-19 13:00:13 +08:00
看起来,结构体里面的 std::string 是个奇怪的东西。。。
|
3
liuguangxuan 2023-05-19 13:05:28 +08:00
可以按 1#说的,起一个单独的工程,把问题复现出来,发出来让大家调试一下。
|
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 |
5
villivateur OP @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; } |
6
villivateur OP @cnbatch Ubuntu20.04 已经确定修复了这个 bug
|
7
roycestevie6761 2023-05-19 13:52:27 +08:00
用 clang 或者其他编译器试一下不就行了
|
8
leonshaw 2023-05-19 14:05:51 +08:00
地址不一样只能怀疑编译器了,反汇编出来看看
|
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 编译选项。
|
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 对象。最后链接生成的可执行文件运行时就可能会报错。
|
12
kkhaike 2023-05-19 14:28:20 +08:00
你不给完整代码,没法帮你排查。首先你也测试了提出的代码是没问题的。
c++的莫名问题,有时候都是和崩溃地方不相关的地方引起的,特别是 stackoverflow 这种 |
13
villivateur OP |
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 感觉,你可以换台机器测测了... |
15
ohwind 2023-05-19 14:50:47 +08:00
> 存在 #pragma pack(push, 1) 但是忘了 pop 。
我觉得这编译器应该给警告出来 |
16
villivateur OP @ohwind 然而并没有……
|
17
saturn7 2023-05-19 15:32:56 +08:00
都使用到 c++, 还要魔改编译器结构体内存对齐,真没必要,浪费时间。
|
18
villivateur OP @saturn7 嵌入式设备,要做硬件通讯,没办法
|
20
loveumozart 2023-05-19 16:16:25 +08:00
写 c++太辛苦。。。排查这么一个 bug 都到编译器水平了,这鸟行业还 35 优化,程序员太苦逼了
|
21
cnbatch 2023-05-19 17:01:28 +08:00 via Android
其实可以换个没这种 bug 的编译器吧,嵌入式设备的环境又不是必须固定使用 Ubuntu 20.04
|
22
Rothschild 2023-05-19 17:30:01 +08:00
@saturn7 恰恰是 c++需要的开发地方,对齐内存是很常见的事儿,这不算魔改算日常操作
|
23
exch4nge 2023-05-19 17:42:50 +08:00
按理来说这种情况编译器应该有 warning 的,我也没环境无法确定,也可能是用参数忽略掉了这种 warning ,也可能是编译过程有大量 warning 输出大家都不看。如果这版本 gcc 真没有(可能性很小)那建议升级或换 clang 。
|
24
junmoxiao 2023-05-19 18:14:47 +08:00
@loveumozart 有一说一不是 c++的锅,自己改了内存对齐能怪谁呢
|
25
saturn7 2023-05-19 18:31:51 +08:00
@Rothschild 都用到 stl ,class ,object 了,修改内存对齐肯定不是日常操作呀!!!正常项目代码编程时,注意减少对象内存复制,基本可以减少硬件 90%内存压力。如果真是要求内存极限使用场景,正确是做法是根据自己公司业务,用 C/C++封装一套自己的数据容器才是明智的做法。我认为用到 c++,还去结构体还要修改内存对齐的方式,肯定算是铤而走险做法。
|
26
Rothschild 2023-05-19 19:09:09 +08:00
@saturn7 这只能说明你压根就没在过什么 C++的组里干过,内存对齐在网络传输序列化反序列化以及嵌入式行业是必须的,而且无处不在,你在任何大点 c++项目的代码里都能看到这些
|
27
saturn7 2023-05-22 09:51:11 +08:00
@Rothschild 用过 google protobuf2/3 ,modbus, focas, 没见过那一个协议官方文档建议开发者手动改 pragma 对齐。不过既然是用网络流通信了,发送 /接收和内存对齐有毛关系,通用做法不都是大端字节序编码?
|
28
Rothschild 2023-05-22 15:54:06 +08:00
@saturn7 所以说你见得太少了啊,你为什么以为大型项目里只是那点 crud 的代码?你不会在 github 里搜一下#pragma pack(push,1)看看用的多频繁?
|