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

2023-05-19 12:22:57 +08:00
 villivateur

上一篇是 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{} 也不行

2630 次点击
所在节点    C++
28 条回复
yolee599
2023-05-19 12:58:46 +08:00
你重新起一个只有这部分的代码,其他代码全删掉的工程看看呢?
hefish
2023-05-19 13:00:13 +08:00
看起来,结构体里面的 std::string 是个奇怪的东西。。。
liuguangxuan
2023-05-19 13:05:28 +08:00
可以按 1#说的,起一个单独的工程,把问题复现出来,发出来让大家调试一下。
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
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
2023-05-19 13:43:24 +08:00
@cnbatch Ubuntu20.04 已经确定修复了这个 bug
roycestevie6761
2023-05-19 13:52:27 +08:00
用 clang 或者其他编译器试一下不就行了
leonshaw
2023-05-19 14:05:51 +08:00
地址不一样只能怀疑编译器了,反汇编出来看看
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
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
2023-05-19 14:22:17 +08:00
@xiatwhu 有道理,看起来是两边对齐不一样。
kkhaike
2023-05-19 14:28:20 +08:00
你不给完整代码,没法帮你排查。首先你也测试了提出的代码是没问题的。

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

感谢回复,已经排查到,见附言
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
2023-05-19 14:50:47 +08:00
> 存在 #pragma pack(push, 1) 但是忘了 pop 。

我觉得这编译器应该给警告出来
villivateur
2023-05-19 14:52:14 +08:00
@ohwind 然而并没有……
saturn7
2023-05-19 15:32:56 +08:00
都使用到 c++, 还要魔改编译器结构体内存对齐,真没必要,浪费时间。
villivateur
2023-05-19 15:46:57 +08:00
@saturn7 嵌入式设备,要做硬件通讯,没办法
newmlp
2023-05-19 16:07:05 +08:00
@ohwind msvc 会有警告...巨硬牛逼!
loveumozart
2023-05-19 16:16:25 +08:00
写 c++太辛苦。。。排查这么一个 bug 都到编译器水平了,这鸟行业还 35 优化,程序员太苦逼了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/941209

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX