句柄的可移植位域
Portable bit fields for Handles
我想使用 "Handles" 数据并将其存储在对象缓冲区中以减少分配开销。句柄只是包含对象的数组的索引。但是我需要检测 use-after-reallocations,因为这很容易滑入。常见的方法似乎是使用位域。然而,这会导致 2 个问题:
- 位字段是实现定义的
- 位移位不可跨 big/little endian 机器移植。
我需要的:
- 存储文件句柄(文件处理程序可以管理整数类型(字节交换)或字节数组)
- 在句柄中存储 2 个最小值 space
我得到的:
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;
}
我的目标是使句柄尽可能不透明,添加布尔检查,但仅此而已。句柄的用户除了传递它之外不能对它做任何事情。
所以问题我运行变成了:
- Union 是 UB -> 将其
T_Handle
替换为 Storage
并添加一个 ctor 到 Handle from Storage
- 编译器是如何布局位域的?我填满了整个 union/type 所以应该没有填充。所以可能唯一不同的是哪种类型首先取决于字节顺序,对吗?
- 如何将
handle_
存储到一个文件并从可能不同的字节序机器加载它并且仍然使 index
和 magic
正确?我想我可以存储包含 Storage
'endian-correct' 并获得正确的值,IF 两个成员恰好占据了 space 的一半(一个 uint 中的 2 个短裤) 但我总是想要更多 space 的索引而不是魔法值。
注意:已经有关于位域和联合的问题。摘要:
- 位域可能有意外的填充(这里不可能,因为整个类型都被占用)
- "members" 的顺序取决于编译器(这里只有 2 种可能的方式,应该保存以假设顺序完全取决于字节序,所以这可能对这里有帮助,也可能没有帮助)
- 可以通过手动移位(或例如包装器 http://blog.codef00.com/2014/12/06/portable-bitfields-using-c11/)实现位的特定二进制布局 -> 这里不是答案。我还需要值 IN 位域的特定布局。所以我不确定我得到了什么,例如创建一个句柄作为
handle = (magic << numIndexBits) | index
和 save/load 作为二进制(无字节顺序转换)缺少用于测试的 BigEndian 机器。
注意:没有 C++11,但允许 boost。
答案非常简单(基于另一个问题,我忘记了 link 和@Jeremy Friesner 的评论):
因为 "numbers" 在 C++ 中已经是一种抽象,当变量在 CPU 寄存器中时(当它用于任何计算时,可以确保始终具有相同的位表示) C++ 中的位移位也是以与字节序无关的方式定义的。这意味着 x << 1
总是等于 x * 2
(因此是大端)
只有在保存到文件时,send/recv 通过网络或从内存中以不同方式访问它(例如通过指针...)
这里不能使用 C++ 位域,因为不能 100% 确定 "entries" 的顺序。位域容器可能没问题,如果它们允许以 "number".
的形式访问数据
Savest(仍然)使用移位,在这种情况下非常简单(只有 2 个值)在 storing/serialization 期间,数字必须以字节序不可知的方式存储。
我想使用 "Handles" 数据并将其存储在对象缓冲区中以减少分配开销。句柄只是包含对象的数组的索引。但是我需要检测 use-after-reallocations,因为这很容易滑入。常见的方法似乎是使用位域。然而,这会导致 2 个问题:
- 位字段是实现定义的
- 位移位不可跨 big/little endian 机器移植。
我需要的:
- 存储文件句柄(文件处理程序可以管理整数类型(字节交换)或字节数组)
- 在句柄中存储 2 个最小值 space
我得到的:
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;
}
我的目标是使句柄尽可能不透明,添加布尔检查,但仅此而已。句柄的用户除了传递它之外不能对它做任何事情。
所以问题我运行变成了:
- Union 是 UB -> 将其
T_Handle
替换为Storage
并添加一个 ctor 到 Handle from Storage - 编译器是如何布局位域的?我填满了整个 union/type 所以应该没有填充。所以可能唯一不同的是哪种类型首先取决于字节顺序,对吗?
- 如何将
handle_
存储到一个文件并从可能不同的字节序机器加载它并且仍然使index
和magic
正确?我想我可以存储包含Storage
'endian-correct' 并获得正确的值,IF 两个成员恰好占据了 space 的一半(一个 uint 中的 2 个短裤) 但我总是想要更多 space 的索引而不是魔法值。
注意:已经有关于位域和联合的问题。摘要:
- 位域可能有意外的填充(这里不可能,因为整个类型都被占用)
- "members" 的顺序取决于编译器(这里只有 2 种可能的方式,应该保存以假设顺序完全取决于字节序,所以这可能对这里有帮助,也可能没有帮助)
- 可以通过手动移位(或例如包装器 http://blog.codef00.com/2014/12/06/portable-bitfields-using-c11/)实现位的特定二进制布局 -> 这里不是答案。我还需要值 IN 位域的特定布局。所以我不确定我得到了什么,例如创建一个句柄作为
handle = (magic << numIndexBits) | index
和 save/load 作为二进制(无字节顺序转换)缺少用于测试的 BigEndian 机器。
注意:没有 C++11,但允许 boost。
答案非常简单(基于另一个问题,我忘记了 link 和@Jeremy Friesner 的评论):
因为 "numbers" 在 C++ 中已经是一种抽象,当变量在 CPU 寄存器中时(当它用于任何计算时,可以确保始终具有相同的位表示) C++ 中的位移位也是以与字节序无关的方式定义的。这意味着 x << 1
总是等于 x * 2
(因此是大端)
只有在保存到文件时,send/recv 通过网络或从内存中以不同方式访问它(例如通过指针...)
这里不能使用 C++ 位域,因为不能 100% 确定 "entries" 的顺序。位域容器可能没问题,如果它们允许以 "number".
的形式访问数据Savest(仍然)使用移位,在这种情况下非常简单(只有 2 个值)在 storing/serialization 期间,数字必须以字节序不可知的方式存储。