blob内部数据的对齐和填充

Alignment and padding of data inside a blob

本文关键字:填充 对齐 内部 数据 blob      更新时间:2023-10-16

我使用一个大blob(已分配的内存)在内存中连续存储数据。

我希望blob中的数据组织如下:

|data1类型|data1|data2类型|data2|dataN类型|dataN|

CCD_ 1是我在交换机中用来将CCD_ 3转换为适当类型的CCD_。

问题是,我想保持数据正确对齐,所以我想强制blob中的所有数据都打包8字节(我选择8字节进行打包,因为它可能会保持数据正确排列?),这样数据就会被紧密打包(因为对齐,数据->数据类型之间不会有漏洞)。

我试过这个:

#pragma pack(8)
class A
{
public:
    short b;
    int x;
    char v;
};

但它不起作用,因为使用sizeof(A),我得到的是12个字节,而不是预期的16个字节。

p.S:x86或x64体系结构中是否存在大于8字节的数据类型?

这个答案假设两件事:

  1. 您希望二进制blob被紧密地封装(没有孔)
  2. 您不希望以未对齐的方式访问数据成员(与访问按编译器默认方式对齐的数据成员相比,访问速度较慢)

如果是这种情况,那么您应该考虑将大型"blob"视为面向字节的流的设计。在这个流中,可以编组/解编组标记/值对,它们填充具有自然对齐的对象。

有了这个方案,你可以两全其美。您得到了一个紧凑的blob,但一旦从blob中提取对象,由于自然对齐,访问对象成员会很快。它也是可移植的1并且不依赖于编译器扩展。缺点是需要为可以放入blob中的每种类型编写样板代码。

初步示例:

#include <cassert>
#include <iomanip>
#include <iostream>
#include <stdint.h>
#include <vector>
enum BlobKey
{
    kBlobKey_Widget,
    kBlobKey_Gadget
};
class Blob
{
public:
    Blob() : cursor_(0) {}
    // Extract a value from the blob. The key associated with this value should
    // already have been extracted.
    template <typename T>
    Blob& operator>>(T& value)
    {
        assert(cursor_ < bytes_.size());
        char* dest = reinterpret_cast<char*>(&value);
        for (size_t i=0; i<sizeof(T); ++i)
            dest[i] = bytes_[cursor_++];
        return *this;
    }
    // Insert a value into the blob
    template <typename T>
    Blob& operator<<(const T& value)
    {
        const char* src = reinterpret_cast<const char*>(&value);
        for (size_t i=0; i<sizeof(T); ++i)
            bytes_.push_back(src[i]);
        return *this;
    }
    // Overloads of << and >> for std::string might be useful
    bool atEnd() const {return cursor_ >= bytes_.size();}
    void rewind() {cursor_ = 0;}
    void clear() {bytes_.clear(); rewind();}
    void print() const
    {
        using namespace std;
        for (size_t i=0; i<bytes_.size(); ++i)
            cout << setfill('0') << setw(2) << hex << int(bytes_[i]) << " ";
        std::cout << "n" << dec << bytes_.size() << " bytesn";
    }
private:
    std::vector<uint8_t> bytes_;
    size_t cursor_;
};
class Widget
{
public:
    explicit Widget(int a=0, short b=0, char c=0) : a_(a), b_(b), c_(c) {}
    void print() const
    {
        std::cout << "Widget: a_=" << a_ << " b=" << b_
                  << " c_=" << c_ << "n";
    }
private:
    int a_;
    short b_;
    long c_;
    friend Blob& operator>>(Blob& blob, Widget& widget)
    {
        // Demarshall members from blob
        blob >> widget.a_;
        blob >> widget.b_;
        blob >> widget.c_;
        return blob;
    };
    friend Blob& operator<<(Blob& blob, Widget& widget)
    {
        // Marshall members to blob
        blob << kBlobKey_Widget;
        blob << widget.a_;
        blob << widget.b_;
        blob << widget.c_;
        return blob;
    };
};
class Gadget
{
public:
    explicit Gadget(long a=0, char b=0, short c=0) : a_(a), b_(b), c_(c) {}
    void print() const
    {
        std::cout << "Gadget: a_=" << a_ << " b=" << b_
                  << " c_=" << c_ << "n";
    }
private:
    long a_;
    int b_;
    short c_;
    friend Blob& operator>>(Blob& blob, Gadget& gadget)
    {
        // Demarshall members from blob
        blob >> gadget.a_;
        blob >> gadget.b_;
        blob >> gadget.c_;
        return blob;
    };
    friend Blob& operator<<(Blob& blob, Gadget& gadget)
    {
        // Marshall members to blob
        blob << kBlobKey_Gadget;
        blob << gadget.a_;
        blob << gadget.b_;
        blob << gadget.c_;
        return blob;
    };
};
int main()
{
    Widget w1(1,2,3), w2(4,5,6);
    Gadget g1(7,8,9), g2(10,11,12);
    // Fill blob with widgets and gadgets
    Blob blob;
    blob << w1 << g1 << w2 << g2;
    blob.print();
    // Retrieve widgets and gadgets from blob
    BlobKey key;
    while (!blob.atEnd())
    {
        blob >> key;
        switch (key)
        {
            case kBlobKey_Widget:
                {
                    Widget w;
                    blob >> w;
                    w.print();
                }
                break;
            case kBlobKey_Gadget:
                {
                    Gadget g;
                    blob >> g;
                    g.print();
                }
                break;
            default:
                std::cout << "Unknown object type in blobn";
                assert(false);
        }
    }
}

如果您可以使用Boost,您可能希望使用二进制内存流的Boost.Serialization,如下面的答案所示。


(1)可移植意味着源代码应该在任何地方编译。如果将生成的二进制blob传输到具有不同字节序和整数大小的其他计算机,则它将不可移植

在这种情况下,#pragma pack(8)似乎没有效果。

在MS编译器文档中,pack的参数描述如下:指定要用于打包的值(以字节为单位)。n的默认值为8。有效值为1、2、4、8和16。成员的对齐将在的边界上,要么是n的倍数,要么是成员大小的倍数,,以较小者为准。

因此,#pragma pack指令不能增加成员的对齐,而是可以减少对齐(例如使用#pragma pack(1))。在您的情况下,选择整个结构对齐以使其最大元素自然对齐(int,在32位和64位CPU上通常为4字节)。因此,总大小为4*3=12个字节。

@Negai解释了为什么得到观察到的大小。

您还应该重新考虑您对"紧密封装"数据的假设。在上述结构中,在结构中存在孔。假设32位int和16位short,short后面有一个2字节的孔,char后面有一一个3字节的孔。但这并不重要,因为这个空间在结构内部。

换句话说,要么你得到了一个紧密封装的数据结构,要么你获得了一个对齐的数据结构。

通常,您不需要做任何特殊的事情来获得编译器默认执行的"对齐"行为#pragma pack如果您希望数据"打包"而不是对齐,即消除编译器为保持数据对齐而引入的一些漏洞,那么它非常有用。

你试过这个吗?

class A {
public:
    union {
        uint64_t dummy;
        int data;
    };
};

A及其dataN type0成员的实例现在将始终对齐到8个字节。当然,如果你在前面压缩一个4字节的数据类型,这是毫无意义的,它也必须是8字节。