Handles的可移植位字段

Portable bit fields for Handles

本文关键字:字段 可移植 Handles      更新时间:2023-10-16

我想在对象缓冲区中使用并存储数据的"Handles",以减少分配开销。句柄只是一个带对象的数组的索引。然而,我需要在重新分配后检测使用情况,因为这可能很容易出现。常见的方法似乎是使用位字段。然而,这导致了两个问题:

  1. 位字段由实现定义
  2. 位移位在大/小端序机器之间是不可移植的

我需要什么:

  • 存储文件句柄(文件句柄可以管理整数类型(字节交换)或字节数组)
  • 在句柄中以最小空间存储2个值

我得到的:

template<class T_HandleDef, typename T_Storage = uint32_t>
struct Handle
{
    typedef T_HandleDef HandleDef;
    typedef T_Storage Storage;
    Handle(): handle_(0){}
private:
    const T_Storage handle_;
};
template<unsigned T_numIndexBits = 16, typename T_Tag = void>
struct HandleDef{
    static const unsigned numIndexBits = T_numIndexBits;
};
template<class T_Handle>
struct HandleAccessor{
    typedef typename T_Handle::Storage Storage;
    typedef typename T_Handle::HandleDef HandleDef;
    static const unsigned numIndexBits = HandleDef::numIndexBits;
    static const unsigned numMagicBits = sizeof(Storage) * 8 - numIndexBits;
    /// "Magic" struct that splits the handle into values
    union HandleData{
        struct
        {
            Storage index : numIndexBits;
            Storage magic : numMagicBits;
        };
        T_Handle handle;
    };
};

例如,一种用法是:

typedef Handle<HandleDef<24> > FooHandle;
FooHandle Create(unsigned idx, unsigned m){
    HandleAccessor<FooHandle>::HandleData data;
    data.idx = idx;
    data.magic = m;
    return data.handle;
}

我的目标是保持句柄尽可能不透明,添加bool检查,但不添加其他内容。句柄的用户应该不能对它做任何事情,只能四处传递它。

所以我遇到的问题:

  • 并集是UB->用Storage替换其T_Handle,并从存储添加一个ctor到Handle
  • 编译器如何布局位字段?我填充了整个联合/类型,所以不应该有填充。因此,可能唯一不同的是,根据endianes,哪种类型排在第一位,对吗
  • 如何将handle_存储到一个文件中,并从可能不同的endianes机器加载它,同时indexmagic仍然正确?我想我可以存储包含的Storage"endian-correct"并获得正确的值,IF两个成员正好占据了一半的空间(uint中有2个Shorts)。但我总是希望索引的空间比魔术值的空间大

注意:关于位域和并集已经存在问题。摘要:

  • 位字段可能有意外的填充(此处不可能为整个类型占用)
  • "成员"的顺序取决于编译器(这里只有两种可能的方式,应该保存为假设顺序完全取决于endianes,所以这在这里可能有帮助,也可能没有帮助)
  • 比特的特定二进制布局可以通过手动移位(或例如包装器)来实现http://blog.codef00.com/2014/12/06/portable-bitfields-using-c11/)->这里没有答案。我还需要IN位字段的值的特定布局。因此,我不确定我得到了什么,例如,如果我创建一个句柄为handle = (magic << numIndexBits) | index,并将其保存/加载为二进制(无字节序转换)缺少一个用于测试的BigEndian机器

注意:没有C++11,但允许升压。

答案很简单(基于另一个问题,我忘记了@Jeremy Friesner的链接和评论):

由于"数字"在C++中已经是一种抽象,当变量在CPU寄存器中时(当它用于任何类似的计算时),可以确保始终具有相同的位表示。C++中的位偏移也是以独立于endian的方式定义的。这意味着x << 1总是等于x * 2(因此big-endian)只有在保存到文件、通过网络发送/recv或以不同方式(例如通过指针…)从内存访问文件时,才会出现endianes问题

这里不能使用C++位字段,因为不能100%确定"条目"的顺序。如果比特字段容器允许以"数字"形式访问数据,那么它们可能是可以的。

Savest(仍然)使用比特移位,在这种情况下非常简单(只有2个值)。在存储/序列化过程中,数字必须以endian不可知的方式存储。