【问题标题】:Alignment and padding of data inside a blobblob 内数据的对齐和填充
【发布时间】:2012-07-27 21:29:12
【问题描述】:

我正在使用大块(分配的内存)将数据连续存储在内存中。

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

| data1 类型 |数据1 | data2 类型 |数据2 | dataN 类型 |数据N |

dataN type 是一个int,我在开关中使用它来将dataN 转换为适当的类型。

问题是我想保持数据正确对齐,所以我想强制 blob 内的所有数据都是 8 字节打包(我选择 8 字节进行打包,因为它可能会保持数据正确对齐?),这个数据将紧密打包的方式(数据->数据类型之间不会因为对齐而出现空洞)。

我试过了:

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

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

P.S:在 x86 或 x64 架构中是否有任何大于 8 字节的数据类型?

【问题讨论】:

  • C/C++ 标准没有指定数据类型的实际大小(除了 sizeof(char) == 1 和 char
  • 对于可能大于 8 字节的矢量化数据有编译器特定的扩展。另外,如果您无法控制数据,如何确保
  • 我的编译器上的 long double 是 16 字节长。显然结构数据类型可以大于 8 个字节。

标签: c++ visual-c++


【解决方案1】:

这个答案假设了两件事:

  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() << " bytes\n";
    }

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 blob\n";
                assert(false);
        }
    }
}

如果您可以使用 Boost,您可能希望将 Boost.Serialization 与二进制内存流一起使用,例如 answer


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

【讨论】:

  • 您的代码看起来很有用,但对我的问题没有用。我需要非常快速地访问 blob 内的对象,这样我就不能到处复制对象了。在您的代码中,blob 内的对象未正确对齐,无法直接从 blob 中读取。顺便说一句,有什么理由不使用 memcpy() 而不是循环?
  • @TiagoCosta : 当sizeof(T) 很小时,memcpy 可能会更慢。使用显式循环,编译器可以展开和内联代码。这是需要进行基准测试(或对组件进行分析)才能确定的事情。
  • @TiagoCosta,您必须意识到访问未按照编译器所需方式对齐的成员很慢。通过从打包到“松散”的重新编组,您只需支付一次性能损失,但如果您足够频繁地访问成员,您将获得回报(甚至更多)。您的问题似乎是矛盾的——您希望 blob 被紧紧地包装(没有孔),但您希望您的 struct 成员自然对齐以便快速访问。你不能同时拥有它,除非你像我上面展示的那样进行某种编组/解组。
  • 我在一年前的一个项目中做了相反的事情。我写了一个模板类来获取博客中的设置位置。当我创建实例时,我在 blob 中查询索引,随后我在 INDEX*8 处进行了 memcpy()'ed。我只会另外指出,我不受空间限制。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    您还应该重新考虑您对“紧密包装”数据的假设。对于上述结构,结构中有 个孔。假设 32 位 int 和 16 位 short,在 short 之后有一个两个字节的洞,在 char 之后有一个 3 个字节的洞。但没关系,因为这个空间在结构内部。

    换句话说,要么你得到一个紧凑的数据结构,要么你得到一个对齐的数据结构,但不能两者兼而有之。

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

    【讨论】:

    • 是的,我不关心结构内部的洞,因为它们会影响 sizeof() 的结果。所以"pragma pack()应该只用于改变结构的内部对齐方式。
    【解决方案4】:

    你试过了吗?

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

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

    【讨论】:

      猜你喜欢
      • 2013-11-18
      • 2010-12-13
      • 2016-08-17
      • 1970-01-01
      • 2018-07-27
      • 1970-01-01
      • 1970-01-01
      • 2020-07-26
      • 2019-02-19
      相关资源
      最近更新 更多