我有一些现有的C ++代码,它们通过网络发送接收uint32_t
数组。由于协议的更改,我想用两个两个uint16_t
替换该数组中的每个条目,并且如果可能的话,我希望不更改通过网络发送的位数。将两个uint16_t
值组合成一个32位宽的值的一种显而易见的方法是将低级位打包到uint32_t
中,并保持数组定义不变。因此,发件人的代码应如下所示:
uint32_t items[ARR_SIZE];
for(std::size_t i = 0; i < ARR_SIZE; ++i) {
//get uint16_t field1 and field2 somehow
items[i] = static_cast<uint32_t>(static_cast<uint32_t>(field2) << 16)
| static_cast<uint32_t>(field1));
}
接收方的代码如下:
//receive items
for(std::size_t i = 0; i < ARR_SIZE; ++i) {
uint16_t field1 = static_cast<uint16_t>(items[i] & 0xffff);
uint16_t field2 = static_cast<uint16_t>(items[i] >> 16);
//do something with field1 and field2
}
但是,这很丑陋,类型不安全,并且依赖于硬编码的幻数。我想知道是否有可能通过定义2个成员结构来实现相同的目的,即“应该”与uint32_t
的大小完全相同:
struct data_item_t {
uint16_t field1;
uint16_t field2;
};
然后,发件人的代码如下:
data_item_t items[ARR_SIZE];
for(std::size_t i = 0; i < SIZE; ++i) {
//get uint16_t field1 and field2 somehow
items[i] = {field1, field2};
}
接收方的代码如下:
//receive items
for(std::size_t i = 0; i < ARR_SIZE; ++i) {
uint16_t curr_field1 = items[i].field1;
uint16_t curr_field2 = items[i].field2;
//do something with field1 and field2
}
这项工作等效于位uint32_t
的位吗?换句话说,使用struct data_item_t
时和使用uint32_t
并进行位打包时,items数组将包含相同的位吗?基于the rules of structure padding,我认为包含两个uint16_t
的结构永远不需要任何内部填充即可正确对齐。还是实际上取决于我的编译器,并且我需要像__attribute__((__packed__))
这样的东西来保证它?
答案 0 :(得分:3)
不应存在任何实现定义的填充问题,但是根据字节顺序, 之间会有所不同。还要注意对齐方式会有所不同-例如,如果您将值嵌入到另一个结构中,则对齐变得很重要。
更一般地说,您不清楚要达到什么级别的协议兼容性。我建议您要么决定要允许版本之间的协议兼容性中断,要么非常明确地以可扩展和版本化的方式放下协议,以使软件的不同版本可以通信。在这种情况下,您应该设计协议,以便独立于C ++实现对其进行良好定义,并以字节为单位编写发送/接收代码,以避免字节顺序问题。
我看不出在更改表示形式时试图保持相等数据大小的实现方式。
答案 1 :(得分:0)
这很丑陋,类型不安全,并且依赖于硬编码的幻数。
这是一个众所周知的习惯用法,这是自C以来我们获得位操作运算符的原因之一。这些数字中没有“魔术”。
另一种选择是,只要您知道自己的字节序,就可以致电std::memcpy
。如果您担心的话,这也更容易概括。
我想知道是否可以通过定义2个成员结构来实现相同的目的,即“应该”与uint32_t的大小完全相同。
不是2成员结构,但是您可以使用2个uint16_t
数组来做,这将确保它们之间没有填充。
您也可以根据需要使用2个成员,但是断言该大小是最小的。至少以这种方式,可以确保它在编译后仍能正常工作(在当今的大多数平台上都可以):
static_assert(sizeof(T) == 2 * sizeof(std::uint16_t));
这项工作等效于位
uint32_t
的位吗?换句话说,当我使用struct data_item_t时,和使用uint32_t
并进行位打包时,items数组包含相同的位吗?
否,编译器可能会添加填充。
或者这实际上取决于我的编译器,我需要像
__attribute__((__packed__))
这样的东西来保证它吗?
那是该属性的存在理由(特别是对于不同的类型)。 :-)
答案 2 :(得分:0)
只需编写适当的访问器:
struct data_item_t {
uint32_t field;
uint16_t get_field1() const { return field; }
uint16_t get_field2() const { return field >> 16; }
void set_field1(uint16_t v) { field = (field & 0xffff0000) | v; }
void set_field2(uint16_t v) { field = (field & 0x0000ffff) | v << 16; }
};
static_assert(std::is_trivially_copyable<data_item_t>::value == true, "");
static_assert(sizeof(data_item_t) == sizeof(uint32_t), "");
static_assert(alignof(data_item_t) == alignof(uint32_t), "");
is_trivially_copyable
到位,因此您可以根据需要任意选择memcpy
或memmove
类。因此,通过使用指向char
,unsigned char
或std::byte
的指针的api接收它是有效的。
除了第一个成员的前面,编译器可以在任何地方插入填充。因此,即使只有一个字段,它也可以在结构的末尾插入填充-可能我们可以找到一个奇怪的实现,其中sizeof(data_item_t) == sizeof(uint64_t)
。解决此问题的正确方法是编写正确的static_assertions。