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

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

6054 次点击
所在节点    C++
73 条回复
villivateur
362 天前
@tkhmy 单线程也会崩溃
ichao1214
362 天前
@villivateur 可能的,看看你修改的代码。回退是不是就好了哈哈
mybyons
362 天前
代码需要 refactor 的地方 chatGPT 已经说的很好了 参考 12 楼 @Rothschild

你这里给出的信息不够 没有描述上下文的情况 简单来看最后的日志 size()[3353953467947191204] 和 cap()[1] 明显有问题

你可以在 vector 模版那里 用自定义的 Allocator 在 allocate/deallocate 加一些打印消息 看看有没有思路
codehz
362 天前
c++调试最麻烦的地方就在于,一些错误可以悄悄的传播到一个无关的代码上(几乎很难发现原始错误的地方)
并且有些错误一旦挂了调试器 /santizer 就会消失(
zizon
362 天前
看看生成的代码?
异常堆栈里这个_ZN12ModuleConfigD1Ev 应该是析构函数里 free 某个东西的时候 segment fault 了.
leonshaw
362 天前
用了什么魔改的库?
villivateur
362 天前
@mybyons

目前做的测试:

1. 把 ModuleConfig 里面的 std::string pdoMapName 删除,问题消失;
2. 把 testVec 定义为 std::vector<ModuleConfig*> 然后通过 new 创建对象再 push_back 指针,问题消失
villivateur
362 天前
@leonshaw 没有魔改,都是标准库,g++ 9.4.0
zizon
362 天前
string 的问题的话那可能是 ModuleConfig 的编译器生成的 copy constructor 有问题,double free 了?
leonshaw
362 天前
@villivateur 有没有重载 operator new/delete
wanglufeifei
362 天前
std::string 作为成员在结构体中是个地址,不是内存块,结构体字符串成员最好用 char[256]这种固定内存大小写法
cnbatch
362 天前
如果不用 push_back ,而是用 emplace_back 呢?
doraf
362 天前
用 Valgrind 这种试试,能有帮助没?
cnbatch
361 天前
@cnbatch 忘了一个,emplace_back 的同时 std::move
imagecap
361 天前
看看是不是 printf 的问题,感觉这个地方 %ld 和 size_t 类型不匹配可能会破坏栈
felixlong
361 天前
用 compile explorer 里的 g++9.4.0 不能重现你的问题。大概率是运行到这个点的时候 heap 已经被弄坏了,可能哪段代码指针写出界了。
loveumozart
361 天前
如果能复现就好解决了,不能复现就难解决了。
能复现的话,就去把 core dump 文件拿出来,往下分析堆栈信息,具体崩溃原因是什么,这里说的崩溃原因至少要到 C++内存分配和释放源码级别,比如说某三个内存块之间在析构操作时被连接成同一个内存块的时候,如果中间那个内存块里保存的前后内存块的 size 和前后内存块 size 保存的自己的 size 对不上的话就会崩溃。(这是我之前遇到过的一个很难复现的 cpp 程序的一个例子)

如果代码本身很简单,没什么问题,大概率就是内存被写坏了,用了不该用的指针,直接做了写操作,这在 C++很常见,和多线程没什么关系,写坏的时间点到崩溃的时间点中间有几分钟都很正常
e7
361 天前
从 size 看你 push 了很多很多很多下,栈溢出了,入参多大啊
pkk007
361 天前
或许可以尝试一下在类声明那里加一个`__declspec(align(64)) `
loveumozart
361 天前
coredump 分析的时候,经常是要打印每个对象的每一个字节的内容是什么,最后才能验证结果,个人不是很喜欢 cpp 的这一点就是这个,第一次分析这种问题用了小一个周的时间才定位和验证出来

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

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

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

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

© 2021 V2EX