我的同事想通过T
网络发送一些由某种类型表示的数据。他通过使用带有套接字的调用将其转换T
为char*
并发送它来执行传统方式™ :write(2)
auto send_some_t(int sock, T const* p) -> void
{
auto buffer = reinterpret_cast<char const*>(p);
write(sock, buffer, sizeof(T));
}
到目前为止,一切都很好。这个简化的例子,除了去掉任何错误检查,应该是正确的。假设该类型T
是可简单复制的,我们可以使用std::mempcy()
(根据[basic.types]
C++17 标准 [1] 中的 6.7 第 3 点)在对象之间复制这种类型的值,所以我猜write(2)
它也应该可以工作,因为它会盲目地复制二进制数据。
棘手的地方是接收方。
假设有T
问题的类型如下所示:
struct T {
uint64_t foo;
uint8_t bar;
uint16_t baz;
};
它有一个对齐要求为 8 个字节 ( foo
) 的字段,因此整个类型需要 8 个字节的严格对齐(参见 6.6.5[basic.align]
第 2 点的示例)。这意味着类型值的存储T
必须仅分配在合适的地址上。
现在,下面的代码呢?
auto receive_some_t(int sock, T* p) -> void
{
read(sock, p, sizeof(T));
}
// ...
T value;
receive_some_t(sock, &T);
看起来很阴暗,但应该可以正常工作。接收到的字节确实表示 type 的有效值,T
并且被盲目地复制到 type 的有效对象中T
。
但是,如何使用char
如下代码中的原始缓冲区:
char buffer[sizeof(T)];
read_some_t(sock, buffer);
T* value = reinterpret_cast<T*>(buffer);
这是我的编码员大脑触发红色警报的地方。我们绝对不能保证对齐char[sizeof(T)]
匹配T
是一个问题。我们也不会往返指向有效T
对象的指针,因为我们的内存中没有有效类型的对象。T
而且我们不知道另一边使用了什么编译器和选项(也许另一边的结构被打包而我们的不是)。
简而言之,我看到了将原始char
缓冲区转换为其他类型的一些潜在问题,并且会尽量避免编写上述代码。但显然它有效,并且是“每个人都这样做”的方式。
我的问题是char
:根据 C++17 标准,恢复通过网络发送并接收到适当大小的缓冲区中的结构是否合法?
如果没有,如何使用std::aligned_storage<sizeof(T), alignof(T)>
来接收这样的结构?如果std::aligned_storage
也不合法,是否有任何合法的方式通过网络发送原始结构,或者只是碰巧工作是一个坏主意......直到它不起作用?
我将结构视为一种表示数据类型的方式,并将编译器在内存中布置它们的方式视为实现细节,而不是作为数据交换所依赖的有线格式,但我愿意犯错。
[1] www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/n4713.pdf