记 C++开发中的一个小坑

2022-02-21 16:08:09 +08:00
 dangyuluo

最近写的功能里,发现一个单元测试在 Debug 下可以通过,但是在 RelWithDebInfo 下却报错。错误发生在使用memcmp比较两个内存地址处。抽象出来的代码如下

struct GID{
  MyType m_type; // Member with alignment as 8
  bool used; // bool has alignment of 1
};


GID gid1;
...
GID gid2 = gid1;

assert(memcmp(&gid1, &gid2, sizeof(GID)) == 0); // failed for RelWithDebInfo

后来经过排查,发现 Debug 时gid2.used成员之后内存是干净的,padding 均为 0 ,但是在 RenWithDebInfo 下gid2.used之后内存却有随机值,导致了memcmp失败。经过搜索发现了 auto-generated copy constructor 是不会将 padding 置零。读了 memcmp 的文档后也发现已经有提示过了。真是学无止境。

https://stackoverflow.com/questions/70979077/does-c-standard-guarantee-the-initialization-of-padding-bytes-to-zero-for-non

https://en.cppreference.com/w/cpp/string/byte/memcmp

4346 次点击
所在节点    C++
35 条回复
jackchenly
2022-02-22 09:25:59 +08:00
@wctml 同,我的编程习惯就是会初始化所有变量
3dwelcome
2022-02-22 10:03:41 +08:00
这算什么,隔壁邻居 memcpy 才是巨坑好吧。

https://en.cppreference.com/w/cpp/string/byte/memcpy 里有一行超小字,写着如果输入内存部分有重叠,那么 the behavior of memcpy is not specified and may be undefined.

而且这 undefined 行为,还是和 C++运行时有关系的,不同版本不一样。你本地运行没问题,换台机子就有问题了!被坑了好久。
3dwelcome
2022-02-22 10:07:41 +08:00
大家体会一下官方那句“may be undefined”,用了 may, 没用肯定句。

也就是结果可能正确,可能不正确。

你在 99 台机器上运行正确,换最后 1 台机器就不正确了。
LifStge
2022-02-22 10:31:21 +08:00
@GeruzoniAnsasu 谢谢 确实我比较偏激 结论上也有问题 你说的 3 点 2,3 我没反对的 看到未初始化的地方 然后我表达的就跑题了 对于非 POD 类型 拷贝构造函数 是不会额外的对整体做零初始化的 默认生成的 还是自定义的 结果一样的 所以就是你说的 第二点这个问题 我前面表达的 主要还是针对 gid1 未初始化的情况 不太清楚 OP 的 MyType 是否是 POD 类型 如果是 POD 类型 那么 GID 也就是 POD 类型了 这种情况 就是我前面的例子出现的结果了 在 clang -Os 优化下 问题就表现出来了 这时候 gid1 跟 gid2 都是未知数据 同时 gid2 跟 gid1 也不同 应该就是优化情况下 啥都没干. 此时如果显式的对 gid1 做零初始化 就比如 GID gid1{}; 这个情况下 gcc 跟 clang 的表现就都相同了 clang 下的反汇编 如果 GID 位数不多 gid1 跟 gid2 同时直接就是几条指令 清零了 如果是结构体较大 就转变为 memset 清零 .
ColorfulBoar
2022-02-22 13:26:12 +08:00
@3dwelcome 恭喜你被 cppreference 整蛊了~
我懒得查哪个词是敏感词了所以点击这里查看剩余内容 https://telegra.ph/%E4%BF%84%E7%BD%97%E6%96%AF%E6%96%B9%E5%9D%97-02-22
MatDK
2022-02-22 18:07:48 +08:00
可以 学习了
secondwtq
2022-02-22 19:56:13 +08:00
secondwtq
2022-02-22 20:21:01 +08:00
@GeruzoniAnsasu 因为 gid1 是 indeterminate ,gid2 初始化了也没用,这玩意会传染的。语言律师地说,楼主这一步已经死了。

https://godbolt.org/z/hzenM768K
mingl0280
2022-02-22 20:32:10 +08:00
首先,在 GID2 中楼主不应当期待一个非 POD 的对象 /结构,在未初始化为 0 的情况下内存对齐以后,在对齐的 padding 部分中存在为 0 的数据(实际上这部分数据几乎肯定是随机的)。你要么 pack(1)干掉 padding 要么别搞 memcmp ,去自定义 operator=
GeruzoniAnsasu
2022-02-22 20:46:54 +08:00
@secondwtq 厄。。 那好像大家理解有点歧义, gid1 下面有省略号,我默认它是合法在用的对象,或者说 gid1 从定义到使用不存在问题
secondwtq
2022-02-22 21:06:02 +08:00
@GeruzoniAnsasu gid1 后面还有个分号,也就是说很有可能是没初始化的 ... 虽然他要是省略号里面给他赋了个值应该就没事了,要是没赋值就用还是炸
当然这个帖子主要问题还是在 memcmp 上面 ...
nicebird
2022-02-22 22:29:48 +08:00
确实是这样的,就是中间的空洞部分不会处理呗。
dangyuluo
2022-02-23 01:09:35 +08:00
@secondwtq 这只是简易版的代码,实际上 gid1 是被正常初始化并且填 0 的。gid1 不是问题所在
dangyuluo
2022-02-23 01:13:05 +08:00
@mingl0280 实际上这个类是 POD 的类,只是设计不当导致有 padding 而已
TencentCEO
2022-03-14 15:30:45 +08:00
同意 @wctml 说的 "楼主 你太年轻了" 😃
我现在都习惯类或结构体成员给默认值,struct GID{ bool used=false; };

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

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

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

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

© 2021 V2EX