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++…..

© - 2024 · 月光下的旅行。 禁止转载