【问题标题】:C++ how to handle alignment padding when casting raw data to class objectsC ++如何在将原始数据转换为类对象时处理对齐填充
【发布时间】:2020-07-24 08:29:27
【问题描述】:

我将文件的大部分内容作为“blob”数据读入char 数组。 我知道这些 blob 的结构,并为不同的结构创建了类。 然后我想将读取的char 数组转换为适当类对象的数组。

这在某些情况下效果很好,但我遇到了类成员的对齐/填充是一个问题的情况。

这是一个最小的示例,但我不是从文件中获取数据,而是在data_i1data_d1data_i2 中定义数据,然后将其转换为c_datac_data代表从文件中读取的数据,包含data_i1data_d1data_i2两次。

如果没有对齐问题,如果我将c_data 转换为Data 的数组,我应该得到Data[0]Data[1] 中的初始数据。

#include <iostream>

class Data {
public:
    int     i1[2];
    double  d1[3];
    int     i2[3];
};


int main()
{
    //Setting some data for the example:
    int     data_i1[2] = {  1,   100};          //2 * 4 =  8 bytes
    double  data_d1[3] = {0.1, 100.2, 200.3 };  //3 * 8 = 24 bytes
    int     data_i2[3] = {  2,   200, 305   };  //3 * 4 = 12 bytes
                                                //total = 44 bytes

    //As arrays the data is 44 bytes, but size of Data is 48 bytes:
    printf("sizeof(data_i1) = %d\n",    sizeof(data_i1));
    printf("sizeof(data_d1) = %d\n",    sizeof(data_d1));
    printf("sizeof(data_i2) = %d\n",    sizeof(data_i1));
    printf("total size      = %d\n\n",  sizeof(data_i1) + sizeof(data_d1) + sizeof(data_i2));
    printf("sizeof(Data)    = %d\n",    sizeof(Data));


    //This can hold the above that of 44 bytes, twice:
    char c_data[88];

    //Copying the data from the arrays to a char array
    //In reality the data is read from a binary file to the char array
    memcpy(c_data +  0, data_i1,  8);
    memcpy(c_data +  8, data_d1, 24);
    memcpy(c_data + 32, data_i2, 12); //c_data contains data_i1, data_d1, data_i2
    memcpy(c_data + 44,  c_data, 44); //c_data contains data_i1, data_d1, data_i2 repeated twice

    //Casting the char array to a Data array:
    Data* data = (Data*)c_data;

    //The first Data object in the Data array gets the correct values:
    Data data1 = data[0];
    //The second Data object gets bad data:
    Data data2 = data[1];

    printf("data1 : [%4d, %4d] [%4.1f, %4.1f, %4.1f] [%4d, %4d, %4d]\n", data1.i1[0], data1.i1[1], data1.d1[0], data1.d1[1], data1.d1[2], data1.i2[0], data1.i2[1], data1.i2[2]);
    printf("data2 : [%4d, %4d] [%4.1f, %4.1f, %4.1f] [%4d, %4d, %4d]\n", data2.i1[0], data2.i1[1], data2.d1[0], data2.d1[1], data2.d1[2], data2.i2[0], data2.i2[1], data2.i2[2]);

    return 0;
}

代码输出为:

sizeof(data_i1) = 8
sizeof(data_d1) = 24
sizeof(data_i2) = 8
total size      = 44

sizeof(Data)    = 48
data1 : [   1,  100] [ 0.1, 100.2, 200.3] [   2,  200,  305]
data2 : [ 100, -1717986918] [-92559653364574087271962722384372548731666605007261414794985472.0, -0.0,  0.0] [-390597128,  100, -858993460]

我应该如何正确处理这个问题? 我可以以某种方式禁用此填充/对齐(如果这是正确的术语)?是否可以为该类创建一个成员函数来指定如何进行转换?

【问题讨论】:

  • 查看数据的对齐方式。
  • @Jarod42 那个重复的帖子有一些有用的信息,但它不是重复的。我不是在问为什么尺寸不同,或者对齐方式是什么。我在问,鉴于在这种情况下对齐是一个问题,我应该如何处理这个问题。
  • 您似乎在寻找__attribute__((packed))pragma pack
  • 只是不要将字节转换为其他类型,甚至不要转换为 POD 结构。 C++ 不保证它会工作。定义一个实际的Data[2] 并以通常的方式命名其成员,例如Data[0].i1[0]
  • 您的 c_data 数组中有 48 个字节,但您复制 88 并尝试读取 96。这会导致未定义的行为。 data[1] 将从这个越界数据中读取。

标签: c++ class padding memory-alignment


【解决方案1】:

在 C++20 之前,如果您尚未实际创建目标类型的对象,则不允许您将指针强制转换为不同类型并使用它。

自 C++20 起,这在您的特定情况下是允许的,因为当对象开始其生命周期并且对象具有 implicit-lifetime 类型时,对象将在 char 数组中隐式创建,您的 @ 987654323@正好有。

但即使在 C++20 中,您也无法保证结构成员之间不会有任何填充,因此仅强制转换指针或 memcpy 整个结构是不安全的。即使您验证没有填充问题,您还需要使用alignas 额外提供与存储阵列的正确对齐:

alignas(alignof(Data)) char c_data[sizeof(Data)*2];

您可能还需要在指针上调用std::launder 以使其指向隐式创建的Data 对象:

Data* data = std::launder(reinterpret_cast<Data*>(c_data));

不要做所有这些,而是​​直接创建一个Data(或其数组)类型的对象(这也解决了对齐问题)和memcpy单个成员一个接一个以避免填充问题:

Data data[2];

// Loop through array and `memcpy` each member individually

此外,不要对大小和偏移量使用明确的数字常量。始终在正确的类型上使用sizeof,以确保不会意外导致代码中已经存在的不匹配,从而导致对存储阵列的访问越界。


作为一种不可移植的替代方案,编译器通常提供属性来强制打包类成员而不留下任何填充空间,请参阅this question。但是,这可能会带来显着的性能损失,因为 CPU 通常会假定某些类型的某些对齐方式,如果数据不对齐,则操作将花费更长的时间或可能根本不允许,具体取决于架构。

另外,即使你打包了 Data 结构,我上面关于强制转换的观点仍然适用,但是它可能允许你声明

Data data[2];

从文件开始并直接读入这个data。 (如果Data 是可简单复制的,则允许转换reinterpret_cast&lt;char*&gt;(data) 并通过该指针进行写入,并且假设您读取的数据实际上具有Data 的正确布局。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-12-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-01-12
    • 2011-05-01
    • 1970-01-01
    相关资源
    最近更新 更多