这个项目很大,我修改了其中一部分代码,出现了一个非常匪夷所思的问题:
这个是出问题的函数:
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 干崩了呢?
1
nuk 2023-05-18 15:21:08 +08:00 1
因为你的 newModule 是分配在栈上的吧
|
2
villivateur OP @nuk 分配在栈上,也没啥影响吧? push_back 会做一次 copy 的
|
3
C47CH 2023-05-18 15:33:50 +08:00
用 compiler explorer 跑了下,没问题
|
4
pagxir 2023-05-18 15:34:48 +08:00 via Android
这里的拷贝构造函数不能省吧。
|
5
Rothschild 2023-05-18 15:41:39 +08:00
compiler explorer 没有问题,所以是其他部分 bug
|
6
villivateur OP |
7
awinds 2023-05-18 15:51:10 +08:00
代码看不出问题,可能是别的地方引起的
|
8
nightwitch 2023-05-18 15:51:11 +08:00 via Android
把参数 identities 的引用先去掉试试,这样传参的时候会复制一份。 如果你的是个多线程程序,在用 range based for 的时候其他线程对 vector 发生了增加或者删除,可能会 crash
|
9
inhzus 2023-05-18 15:52:27 +08:00 2
感觉是其他地方把栈写坏了...
|
10
jones2000 2023-05-18 15:55:06 +08:00
把 pdb 和 source 绑上去,报错的时候调用堆栈怎么都没有具体是对应哪一个文件里的哪一行代码, 调试的时候设置捕获所有异常,感觉是其他地方内存溢出或空指针,导致你这里报错了, 原始的错误的地方应该不在这里。
|
11
diveIntoWork 2023-05-18 15:57:46 +08:00
identity 这里,const 赋值给非 const ,应该有问题吧.....
|
12
Rothschild 2023-05-18 16:04:12 +08:00
这段代码定义了一个 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 在函数内部创建并填充,但在函数结束时被销毁,它的内容在函数外部无法使用。你可能需要将它作为函数的返回值或输出参数,使得其他函数或者代码块可以使用它的内容。 |
13
ichao1214 2023-05-18 16:06:40 +08:00
gdb 运行。然后崩溃的地方看下 info threads ,看下是不是多线程操作 vector 了
|
14
blacktail 2023-05-18 16:19:24 +08:00
你第二行打印的 log ,size 是那么大个数,这已经是很有问题了。感觉并不是这里引起的崩溃,只是这里触发了而已。
|
15
villivateur OP @blacktail @jones2000 @inhzus @nightwitch 我也这么怀疑的,但是我用 gdb 看了,这个程序只有两个线程,另一个线程崩溃时处于 sleep 函数(而且业务逻辑上,这两个线程不会操作同一块内存区域)。
有可能是在之前某个操作导致堆区已经坏了,然后这里触发的吗? @ichao1214 看了,并没有多线程操作 |
16
sparklee 2023-05-18 16:25:06 +08:00
size 怎么这么大
|
17
liuguangxuan 2023-05-18 16:34:40 +08:00 via Android
如果怀疑是多线程的问题的话,链接上 asan 再跑一下试试。
|
18
doraf 2023-05-18 16:35:48 +08:00
@villivateur 去掉另外一个线程的话,还会崩溃吗?
|
19
tkhmy 2023-05-18 16:39:17 +08:00
代码没有问题,看 vector size 应该是多线程操作的锅了
|
20
villivateur OP @doraf 会崩溃的
|
21
villivateur OP @tkhmy 单线程也会崩溃
|
22
ichao1214 2023-05-18 16:45:30 +08:00
@villivateur 可能的,看看你修改的代码。回退是不是就好了哈哈
|
23
mybyons 2023-05-18 16:51:02 +08:00
代码需要 refactor 的地方 chatGPT 已经说的很好了 参考 12 楼 @Rothschild
你这里给出的信息不够 没有描述上下文的情况 简单来看最后的日志 size()[3353953467947191204] 和 cap()[1] 明显有问题 你可以在 vector 模版那里 用自定义的 Allocator 在 allocate/deallocate 加一些打印消息 看看有没有思路 |
24
codehz 2023-05-18 16:51:41 +08:00
c++调试最麻烦的地方就在于,一些错误可以悄悄的传播到一个无关的代码上(几乎很难发现原始错误的地方)
并且有些错误一旦挂了调试器 /santizer 就会消失( |
25
zizon 2023-05-18 16:52:28 +08:00
看看生成的代码?
异常堆栈里这个_ZN12ModuleConfigD1Ev 应该是析构函数里 free 某个东西的时候 segment fault 了. |
26
leonshaw 2023-05-18 16:59:29 +08:00
用了什么魔改的库?
|
27
villivateur OP @mybyons
目前做的测试: 1. 把 ModuleConfig 里面的 std::string pdoMapName 删除,问题消失; 2. 把 testVec 定义为 std::vector<ModuleConfig*> 然后通过 new 创建对象再 push_back 指针,问题消失 |
28
villivateur OP @leonshaw 没有魔改,都是标准库,g++ 9.4.0
|
29
zizon 2023-05-18 17:01:55 +08:00
string 的问题的话那可能是 ModuleConfig 的编译器生成的 copy constructor 有问题,double free 了?
|
30
leonshaw 2023-05-18 17:05:01 +08:00
@villivateur 有没有重载 operator new/delete
|
31
wanglufeifei 2023-05-18 17:08:33 +08:00
std::string 作为成员在结构体中是个地址,不是内存块,结构体字符串成员最好用 char[256]这种固定内存大小写法
|
32
cnbatch 2023-05-18 17:27:24 +08:00
如果不用 push_back ,而是用 emplace_back 呢?
|
33
doraf 2023-05-18 17:29:22 +08:00
用 Valgrind 这种试试,能有帮助没?
|
35
imagecap 2023-05-18 17:38:44 +08:00
看看是不是 printf 的问题,感觉这个地方 %ld 和 size_t 类型不匹配可能会破坏栈
|
36
felixlong 2023-05-18 17:41:51 +08:00
用 compile explorer 里的 g++9.4.0 不能重现你的问题。大概率是运行到这个点的时候 heap 已经被弄坏了,可能哪段代码指针写出界了。
|
37
loveumozart 2023-05-18 17:49:42 +08:00
如果能复现就好解决了,不能复现就难解决了。
能复现的话,就去把 core dump 文件拿出来,往下分析堆栈信息,具体崩溃原因是什么,这里说的崩溃原因至少要到 C++内存分配和释放源码级别,比如说某三个内存块之间在析构操作时被连接成同一个内存块的时候,如果中间那个内存块里保存的前后内存块的 size 和前后内存块 size 保存的自己的 size 对不上的话就会崩溃。(这是我之前遇到过的一个很难复现的 cpp 程序的一个例子) 如果代码本身很简单,没什么问题,大概率就是内存被写坏了,用了不该用的指针,直接做了写操作,这在 C++很常见,和多线程没什么关系,写坏的时间点到崩溃的时间点中间有几分钟都很正常 |
38
e7 2023-05-18 17:51:34 +08:00 1
从 size 看你 push 了很多很多很多下,栈溢出了,入参多大啊
|
39
pkk007 2023-05-18 17:52:18 +08:00
或许可以尝试一下在类声明那里加一个`__declspec(align(64)) `
|
40
loveumozart 2023-05-18 17:52:39 +08:00
coredump 分析的时候,经常是要打印每个对象的每一个字节的内容是什么,最后才能验证结果,个人不是很喜欢 cpp 的这一点就是这个,第一次分析这种问题用了小一个周的时间才定位和验证出来
|
41
corhuan 2023-05-18 17:53:18 +08:00
ModuleConfig 的定义贴全了吗?
|
42
loveumozart 2023-05-18 17:54:00 +08:00
还有就是在指针操作前后的地方多打日志,从日志角度运气好的话也可以看到在崩溃之前,就有一些写坏了的数据
|
43
tkhmy 2023-05-18 17:55:16 +08:00
@villivateur 试试在构造里把 pdoMapName 初始化一下
|
44
anerevol 2023-05-18 17:57:20 +08:00
|
45
kkkbbb 2023-05-18 18:58:55 +08:00
原因找到了么?
|
46
tomychen 2023-05-18 19:13:07 +08:00
@villivateur #2
如果我没记错,push_back 是个 shallow copy ,也就是说...其实还是指针,这也解释了为什么后改为 new 了不会触发。而如果没有估计错,#4 说的,加个拷贝构造,应该也不会触发。 |
47
786375312123 2023-05-18 19:21:07 +08:00
callstack 报错怎么说?
|
48
786375312123 2023-05-18 19:22:15 +08:00
刚看到了,你这信息也太少了,在 vs 里跑跑试试。
|
49
ashong 2023-05-18 19:28:46 +08:00
leave... testVec.size=3353953467947191204 1
应该是 push 出错, 导致返回 size 异常 加个异常处理看看 |
50
MrEatChicken 2023-05-18 19:49:34 +08:00
|
51
documentzhangx66 2023-05-18 20:21:52 +08:00
不建议用 std::vector ,这玩意在设计阶段就有问题,该提供的功能没有,不需要的特性给你强塞一堆。
建议自己实现一个纯粹的 queue 或 list 。 要不去 github 找个 java 风格的。 |
52
lovelylain 2023-05-18 20:59:06 +08:00 via Android 4
检查下代码里有没有#pragma pack(n)对齐指令但没有#pragma pack()恢复默认,多年前遇到过这样一个案例,同事在他的头文件里 pack 对齐但没取消,我的结构体定义在我的头文件里,在不同 cpp 文件,先包含他的头文件再包含我的,和先包含我的或者不包含他的,我那个结构体的大小会不一样,于是也出现了匪夷所思的 crash ,害我 gdb 怼了小半天。
|
53
villivateur OP @imagecap printf 是因为出问题才加了调试的
|
54
GeruzoniAnsasu 2023-05-18 21:48:59 +08:00 1
@villivateur
> 如果是其他部分的 bug ,但我这里的 testVec 是局部变量。难道是其他地方把堆区破坏了吗? vector 实际用到的内存一律在堆上,跟对象本身分配在栈上还是堆上无关。 这种崩溃 bug 基本上都是 heap corruption ,建议还是开一下 santinizer 或者 valgrind 看看有没有帮助。 heap corruption 是最难调的…… 因为导致污染的地方和触发 crash 的地方可能相差十万八千里…… 祝好运 |
55
villivateur OP |
56
leonshaw 2023-05-18 22:19:04 +08:00
@villivateur 还没解决?直接 gdb watch vector 实现里的 _M_impl._M_start 和 _M_impl._M_finish ,看什么时候写坏的
|
57
villivateur OP @leonshaw 哈哈,早下班了,明天再看。话说看下附言,已经确定跟 vector 没关系了,是那个 ModuleConfig 的析构函数的问题
|
58
felixlong 2023-05-18 22:21:47 +08:00
@villivateur 看看你 ModuleConfig 定义的头文件里是不是有什么宏。导致 ModuleConfig 实现文件里的析构函数看到的 ModuleConfig 和 SetModuleIdentities 这里看到的 size 其实不一样。
|
59
leonshaw 2023-05-18 22:22:44 +08:00 1
@villivateur 但是错误的 size 是析构之前打出来的,析构只是踩到野地址挂了,之前应该就有问题。
|
60
mingl0280 2023-05-18 22:29:54 +08:00 via Android
高度怀疑你的 std::string 不是 std::string 而是其它的什么东西。
|
61
Hconk 2023-05-18 22:39:09 +08:00 via iPhone
遇到过因为前面代码的 memcpy 一个不同大小的结构体把栈写坏了,导致在其他地方 crash ,一般这种奇怪问题都不是在挂的那行的问题,而是其他地方把内存写坏导致,lz 查清了可以回来更新下后续
|
62
MetroWind 2023-05-19 03:08:28 +08:00
是嵌入式么?如果是嵌入式的话有可能和系统处理堆的方式有关。
不是的话就是其他地方有 undefined behavior⋯⋯ |
63
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{};`试试? |
64
villivateur OP @billccn g++ 9.4.0 ,Ubuntu 20.04 ,C++ 11
|
66
tusj 2023-05-19 09:10:12 +08:00
是不是有别的线程在改写 identities
|
68
seanwhy 2023-05-19 09:49:27 +08:00
作为一个过来人劝告结构体莫用 std::string ,应该是这里的问题,用 char 吧
|
69
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 |
70
coreki 2023-05-19 12:29:43 +08:00
试试预先分配空间
|
71
yh5DC6Y7u7TdcT9s 2023-05-19 14:30:03 +08:00
1 内存对齐问题,检查哪里是不是设置了相关的参数
2 std::string 符号被覆盖, 最好的办法, 在这个 for 循环里,调用 std::string 的一个函数,然后 gdb step into 里面去, 确定是不是 libstdc++里面的函数 3 其他线程踩内存了, 直接开 sanitizer 跑下 |
72
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) |
73
villivateur OP @v2Mark 看下附言,已经找到问题了
|