0

我的同事想通过T网络发送一些由某种类型表示的数据。他通过使用带有套接字的调用将其转换Tchar*并发送它来执行传统方式™ :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

4

1 回答 1

1

冒险的部分与其说是内存对齐,不如说是T对象的生命周期。当您将reinterpret_cast<>内存作为T指针时,这不会创建对象的实例,并且像使用它一样使用它会导致未定义行为。

在 C++ 中,所有对象都必须生成并停止存在,从而定义它们的生命周期。int这甚至适用于和等基本数据类型float。唯一的例外是char.

换句话说,合法的是将缓冲区中的字节复制到已经存在的对象中,如下所示:

char buffer[sizeof(T)];

// fill the buffer...

T value;
std::memcpy(&value, buffer, sizeof(T));

不用担心性能。编译器将优化所有这些。

于 2021-06-01T00:01:46.290 回答