C++ 项目,出现了匪夷所思的 bug,在 vector 中添加对象,会导致 vector 崩溃,进而整个程序崩溃。

2023-05-18 15:08:30 +08:00
 villivateur

这个项目很大,我修改了其中一部分代码,出现了一个非常匪夷所思的问题:

这个是出问题的函数:

void SetModuleIdentities(std::vector<uint32_t>& identities)
{
	std::vector<ModuleConfig> testVec;
	for (uint32_t const& identity : identities)
	{
		printf("enter... testVec.size=%ld %ld\n", testVec.size(), testVec.capacity());
		ModuleConfig newModule;
		newModule.identity = identity;
		testVec.push_back(newModule);
		printf("leave... testVec.size=%ld %ld\n", testVec.size(), testVec.capacity());
	}
}

这是 ModuleConfig 的定义:

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

执行的输出如下:

enter... testVec.size=0 0
ModuleConfig::constructor
leave... testVec.size=3353953467947191204 1
ModuleConfig::destructor
# 然后就崩溃了

这是部分调用栈信息(来源 sighandler ):

15:03:36  ecpanda exit with 11
crash time:Thu May 18 15:03:36 2023

./base/lib-linux/bin/ecpanda-generic(_Z10sigHandleriP9siginfo_tPv+0x96) [0x56266aff3e9a]
/lib/x86_64-linux-gnu/libpthread.so.0(+0x14420) [0x7f528d9d7420]
/lib/x86_64-linux-gnu/libc.so.6(cfree+0x20) [0x7f528d50b6f0]
./base/lib-linux/bin/ecpanda-generic(_ZN12ModuleConfigD1Ev+0x2c) [0x56266b0075e6]
./base/lib-linux/bin/ecpanda-generic(_ZN15SlaveFileConfig19SetModuleIdentitiesERSt6vectorIjSaIjEE+0x136) [0x56266b017138]
./base/lib-linux/bin/ecpanda-generic(_Z23ESI_SetModuleIdentitiesiRSt6vectorIjSaIjEE+0x42) [0x56266b01692c]

我就 push_back 了一下,怎么就把 vector 干崩了呢?

7406 次点击
所在节点    C++
73 条回复
Hconk
2023-05-18 22:39:09 +08:00
遇到过因为前面代码的 memcpy 一个不同大小的结构体把栈写坏了,导致在其他地方 crash ,一般这种奇怪问题都不是在挂的那行的问题,而是其他地方把内存写坏导致,lz 查清了可以回来更新下后续
MetroWind
2023-05-19 03:08:28 +08:00
是嵌入式么?如果是嵌入式的话有可能和系统处理堆的方式有关。
不是的话就是其他地方有 undefined behavior⋯⋯
billccn
2023-05-19 04:07:05 +08:00
首先楼主应该说一下用的什么编译器和开的哪个语言标准。

我看主要问题应该是那个`std::string pdoMapName`是一个非 POD 类型,在 C++11 之前非 POD 类型不会被默认初始化,楼主的 ModuleConfig()构造函数也没有初始化它,那这个字串里面存的指针就是之前栈里面一个垃圾数值。析构函数会把这个垃圾当成一个真的指针 free ,那肯定要崩溃的。

楼主说“通过 new 创建对象再 push_back 指针,问题消失”那是应为每个新分配的指针指向了清 0 的页面,如果指针原来是有数据的,那也要崩溃。楼主可以自己用 placemnt new 试一下。

写成`std::string pdoMapName{};`试试?
villivateur
2023-05-19 08:46:42 +08:00
@billccn g++ 9.4.0 ,Ubuntu 20.04 ,C++ 11
Plime
2023-05-19 09:05:21 +08:00
@billccn 应该是正解
tusj
2023-05-19 09:10:12 +08:00
是不是有别的线程在改写 identities
shyrock
2023-05-19 09:16:50 +08:00
@codehz #24 所以有时候我觉得调试 C++内存错误真的是在浪费生命。。。
seanwhy
2023-05-19 09:49:27 +08:00
作为一个过来人劝告结构体莫用 std::string ,应该是这里的问题,用 char 吧
dnks
2023-05-19 09:51:29 +08:00
这段代码可能会导致崩溃的原因是在 SetModuleIdentities 函数中,使用了一个 std::vector 容器来存储 ModuleConfig 结构体对象。在每次循环迭代中,都会向 testVec 容器中添加一个新的 ModuleConfig 对象。

然而,ModuleConfig 结构体中包含一个 std::string 类型的成员变量 pdoMapName ,而在默认构造函数中,并没有对 pdoMapName 进行初始化。这会导致在每次循环迭代时,都会创建一个新的 ModuleConfig 对象,并使用默认构造函数进行初始化,但由于 pdoMapName 没有被正确初始化,可能会导致悬空指针或访问未定义的内存区域,从而引发崩溃。

为了解决这个问题,你可以在 ModuleConfig 结构体的默认构造函数中对 pdoMapName 进行初始化。
---By ChatGPT
coreki
2023-05-19 12:29:43 +08:00
试试预先分配空间
yh5DC6Y7u7TdcT9s
2023-05-19 14:30:03 +08:00
1 内存对齐问题,检查哪里是不是设置了相关的参数
2 std::string 符号被覆盖, 最好的办法, 在这个 for 循环里,调用 std::string 的一个函数,然后 gdb step into 里面去, 确定是不是 libstdc++里面的函数
3 其他线程踩内存了, 直接开 sanitizer 跑下
v2Mark
2023-05-31 19:59:16 +08:00
@billccn 感觉这个老哥 说的很合理,
1. 非 POD 类型,string 没有初始化,结合 op 说的 string 成员取消了就不会有问题。建议给 string 一个初始值再试试。
2. 这个 size 是在是太大了,崩溃在 ModuleConfig 析构,那么 pushback 的时候,vector 的缓冲区应该分配了一段无效内存。 主要还是在 vector 扩容的时候,一般都是 2N 的,存在大量的 ModuleConfig 复制,感觉在这个时间段内存分配失败或者被其他地方占用了,也会崩溃。这个还需进一步验证;

建议:
1. 如果 string 必须是成员的话,可以每次 push 的时候看下 vector 的 capacity 和 size ,如果 capacity 不够,先进行 reverse 吧。
2. C++11 可以用 emplace_back ,直接构建吧,避免了复制移动这些操作。(感觉这个 OK)
villivateur
2023-05-31 22:00:12 +08:00
@v2Mark 看下附言,已经找到问题了

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

https://ex.noerr.eu.org/t/941007

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

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

© 2021 V2EX