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

346 天前
 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 干崩了呢?

6019 次点击
所在节点    C++
73 条回复
nuk
346 天前
因为你的 newModule 是分配在栈上的吧
villivateur
346 天前
@nuk 分配在栈上,也没啥影响吧? push_back 会做一次 copy 的
C47CH
346 天前
用 compiler explorer 跑了下,没问题
pagxir
346 天前
这里的拷贝构造函数不能省吧。
Rothschild
346 天前
compiler explorer 没有问题,所以是其他部分 bug
villivateur
346 天前
@C47CH
@Rothschild

如果是其他部分的 bug ,但我这里的 testVec 是局部变量。难道是其他地方把堆区破坏了吗?
awinds
346 天前
代码看不出问题,可能是别的地方引起的
nightwitch
346 天前
把参数 identities 的引用先去掉试试,这样传参的时候会复制一份。 如果你的是个多线程程序,在用 range based for 的时候其他线程对 vector 发生了增加或者删除,可能会 crash
inhzus
346 天前
感觉是其他地方把栈写坏了...
jones2000
346 天前
把 pdb 和 source 绑上去,报错的时候调用堆栈怎么都没有具体是对应哪一个文件里的哪一行代码, 调试的时候设置捕获所有异常,感觉是其他地方内存溢出或空指针,导致你这里报错了, 原始的错误的地方应该不在这里。
diveIntoWork
346 天前
identity 这里,const 赋值给非 const ,应该有问题吧.....
Rothschild
346 天前
这段代码定义了一个 ModuleConfig 结构,并使用 std::vector<ModuleConfig> 来存储多个 ModuleConfig 实例。函数 SetModuleIdentities 则根据传入的 identities 列表创建一组 ModuleConfig 并存储在 testVec 中。以下是我对这段代码的评审和建议。

构造函数: 在 ModuleConfig 结构中,你定义了默认构造函数和析构函数,它们打印出一些信息。这在进行调试时可能有帮助,但在实际的项目中,我建议使用更正式的日志系统,而不是直接打印到控制台。

成员初始化: ModuleConfig 结构的成员变量没有在构造函数中被初始化,这可能导致未定义行为。应当为每个成员变量提供默认值。

预留容量: 当你知道要添加多少元素时,预先为 std::vector 保留足够的空间可以提高性能。你可以在创建 testVec 之后,使用 testVec.reserve(identities.size()) 来实现。

使用 emplace_back 代替 push_back: 使用 emplace_back 可以在容器中直接构造对象,避免了临时对象的创建和拷贝。

未使用的变量: testVec 在函数内部创建并填充,但在函数结束时被销毁,它的内容在函数外部无法使用。你可能需要将它作为函数的返回值或输出参数,使得其他函数或者代码块可以使用它的内容。
ichao1214
346 天前
gdb 运行。然后崩溃的地方看下 info threads ,看下是不是多线程操作 vector 了
blacktail
346 天前
你第二行打印的 log ,size 是那么大个数,这已经是很有问题了。感觉并不是这里引起的崩溃,只是这里触发了而已。
villivateur
346 天前
@blacktail @jones2000 @inhzus @nightwitch 我也这么怀疑的,但是我用 gdb 看了,这个程序只有两个线程,另一个线程崩溃时处于 sleep 函数(而且业务逻辑上,这两个线程不会操作同一块内存区域)。

有可能是在之前某个操作导致堆区已经坏了,然后这里触发的吗?

@ichao1214 看了,并没有多线程操作
sparklee
346 天前
size 怎么这么大
liuguangxuan
346 天前
如果怀疑是多线程的问题的话,链接上 asan 再跑一下试试。
doraf
346 天前
@villivateur 去掉另外一个线程的话,还会崩溃吗?
tkhmy
346 天前
代码没有问题,看 vector size 应该是多线程操作的锅了
villivateur
346 天前
@doraf 会崩溃的

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

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

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

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

© 2021 V2EX