记 C++开发中的一个小坑
source link: https://www.v2ex.com/t/835428
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
最近写的功能里,发现一个单元测试在 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 的文档后也发现已经有提示过了。真是学无止境。
wctml 10 小时 31 分钟前
Kasumi20 10 小时 20 分钟前
wuruxu 10 小时 15 分钟前
jones2000 10 小时 8 分钟前
2. 结构体字节对齐
LifStge 9 小时 22 分钟前
关键在于 初始化的问题 主要原因 c++ 隐式的行为太多了 这次的问题 属于开发工具 为了 debug 模式下 查找问题容易些 在 c++标准之外做了额外的处理 导致了 本来未知行为的代码(也就是错误) 变成了固定行为(也就是 memcmp 比较的内存相等 )
https://en.cppreference.com/w/cpp/language/default_initialization#:~:text=otherwise%2C%20no%20initialization%20is%20performed%3A%20the%20objects%20with%20automatic%20storage%20duration%20(and%20their%20subobjects)%20contain%20indeterminate%20values.
https://en.cppreference.com/w/cpp/language/value_initialization
https://en.cppreference.com/w/cpp/language/zero_initialization
很多时候都会说 声明变量的时候 就做主动初始化的操作 就比如 T{} T (...)等 这也是个好的习惯 但是很多时候 这种也是属于多余的操作 这就要看自己对代码的把控了
反正就是 切勿对未知的内存做操作就行了 更多的情况 不要关注 debug 模式下可以 release 模式下又不可以 再比如 gcc 下可以 msvc 下又不可以...... 只以标准规定的来做
c++标准这东西 还是多写代码吧 做的多了 就慢慢熟悉了 不过嘛 平常一定按照标准规定的流程做 总没错的
虽然其他的方法也能解决问题 但是也容易带来很多问题
总结就是 该初始化就初始化 把控好 c++的各种隐式的默认行为 能使用明确的 就不要依赖隐式默认的
Mithril 9 小时 7 分钟前
我见过一个奇葩的就是有个数组每次都访问越界,不过其实越界了多数情况下也没什么问题,两个函数以后就切掉了。
但是就只有越界的时候恰好后面的内存里是某些特殊符号就会出问题。
所以就谁分到那块内存谁倒霉,表现就是随机炸。你最后 Debug 去吧,鬼知道哪天才能复现。
查出这个问题的时候也是运气好,恰好挂着调试器,它恰好炸了。。。
jim9606 9 小时 0 分钟前
lakehylia 8 小时 58 分钟前
bitdepth 8 小时 28 分钟前
newmlp 8 小时 15 分钟前
LifStge 6 小时 40 分钟前
最终原因就未初始化的结果 是未知的 op 也贴了问题所在 这种隐式的默认行为编译器也有差异问题 之所以 debug 模式下没问题 归根就是编译器的特殊处理的结果 其实也没必要讨论的 明显就是用错了 C4700 的警告 最新的 vc++下是视为错误的 g++ clang++ 倒是没错 但是这本身就是未知的行为 所以实现也有各自的差异 但是把个别编译器对这种未知行为的默认实现 当作标准来用就带来的 现在的问题
```c++
#include <iostream>
#pragma pack(8)
struct AA
{
bool m;
int k;
bool d;
};
int main()
{
AA a;
AA b =a;
for (int i = 0; i < sizeof(a); i++)
{
std::cout << std::hex << (unsigned int)(((unsigned char*)&a)[i]) << ",";
}
std::cout << std::endl;
for (int i = 0; i < sizeof(a); i++)
{
std::cout << std::hex << (unsigned int)(((unsigned char*)&b)[i]) << ",";
}
std::cout << std::endl;
return 0;
}
```
a 未初始化结果
g++ -Os main.cpp
0,0,0,0,0,0,0,0,80,22,2,91,
0,0,0,0,0,0,0,0,80,22,2,91,
clang++ -Os main.cpp
b4,34,e2,4b,ff,7f,0,0,0,0,0,0,
c0,12,40,0,0,0,0,0,d0,10,40,0,
如果把 a 做零初始化后 AA a{}; AA b=a;
g++ -Os main.cpp
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
clang++ -Os main.cpp
0,0,0,0,0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,0,0,0,0,
初始化后的结果是一样的
也不是说必须要主动的做零初始化 毕竟很多时候 做零初始化是多余的 不过就是很多时候是依赖编译器优化 还是自己主动优化 自己手动优化就要注意更多的细节
总结就是 对结构跟类用 memcmp 做比较是存在问题的
对未初始化的变量的使用上 是要注意的 不要依赖编译器对这种未知行为的实现 因为本身就是未知的行为 不是语言标准的规定 所以编译器实现上也没有必要的统一实现 结果上不同正常的 但是不要把对这种未知行为的实现 当成语言的标准来使用 (除非限定使用前提 ) 比如 此代码 必须在 g++ 甚至指定版本 指定 debug 下运行....
kirory 5 小时 55 分钟前
jim9606 1 小时 54 分钟前
可能我语序有点问题,我的意思是复制构造(初始化)是没有问题的,比较有问题。一般想到用 memcmp 做比较的起因是懒得手写一个逐成员比较的比较函数,或者想提高性能。如果目的只是前者的话,C++20 可以显式定义默认比较函数。如果成员都是平凡可比较的话用这个默认比较函数的话是没有问题的,不会出现 OP 所提的问题。
https://zh.cppreference.com/w/cpp/language/default_comparisons
dangyuluo 1 小时 36 分钟前
littlewing 1 小时 7 分钟前
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK