C++初始化与内存大端对齐导致Memory Hash算法错误问题
先前遇到一个Memory Hash计算错误的问题,该结构体如下:
struct BufferIdentifier
{
uint64_t bufferSize;
uint32_t bufferUsage;
uint32_t type;
uint32_t vmaUsage;
};
我们都知道C++的结构体会选择最大的成员来做内存对齐,因此,该结构体的对齐内存如下:
struct BufferIdentifier
{
uint64_t bufferSize; // 8
uint32_t bufferUsage; // 4 + 4
uint32_t type; //
uint32_t vmaUsage; // 4 + 4
// uint32_t pad
};
// sizeof(BufferIdentifier) == 8 + (4 + 4) + (4 + 4) == 24
我使用内存计算其Hash值,如果使用C++20最新的成员初始化方式,或者非显式初始化的方式,Pad部分的内存不会被清零:
BufferIdentifier a
{
.bufferSize = 1,
.bufferUsage = 2,
.type = 3,
.vmaUsage = 4,
};
BufferIdentifier b;
{
b.bufferSize = 1;
b.bufferUsage = 2;
b.type = 3;
b.vmaUsage = 4;
}
BufferIdentifier c
{
.bufferSize,
.bufferUsage,
.type,
.vmaUsage,
};
BufferIdentifier d
{
.bufferSize,
.bufferUsage,
.type,
.vmaUsage,
}
const auto hashId_a = crc::crc32(&a, sizeof(a));
const auto hashId_b = crc::crc32(&b, sizeof(b));
const auto hashId_c = crc::crc32(&c, sizeof(c));
const auto hashId_d = crc::crc32(&d, sizeof(d));
事实上,根据发布模式的不同,abcd计算出来的值都不太一样,在Debug模式下能正常得到相同的Hash值,但是Release模式中则会随机得到不同的值。
打印出abcd的内存值与其Hash值:
在debug模式下,Pad部分的内存都被填充为cc,所以hash值计算相同,结果正确:
a: 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 cc cc cc cc; crc 119766573
b: 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 cc cc cc cc; crc 119766573
c: 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 cc cc cc cc; crc 119766573
d: 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 cc cc cc cc; crc 119766573
在Release模式下,Pad部分的内存没有被清除过,hash值会随机计算出来不同的值:
a: 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 f7 7f 00 00; crc 311187229
b: 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 00 00 00 00; crc 1452299886
c: 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 f7 7f 00 00; crc 311187229
d: 01 00 00 00 00 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 00 00 00 00; crc 1452299886
有三种解决方法:基本都是初始化时清空整个结构体的内存:
// 显式初始化
BufferIdentifier a = { };
BufferIdentifier b { };
// 或者在构造函数里把内存清空。
struct BufferIdentifier
{
BufferIdentifier()
{
memset(this, 0, sizeof(BufferIdentifier));
}
uint64_t bufferSize;
uint32_t bufferUsage;
uint32_t type;
uint32_t vmaUsage;
};
但开发中很难限制开发者写法,现在,没有开发者在使用C++20时,还会去关心一个POD Struct的初始化问题;而除了C++20中的成员初始化方式会导致这个问题外,使用Initialize,未显式初始化等写法都会有这种问题。并且这种bug,一但发生就非常的难查原因。
操蛋的C++…..