C++抽象的字节序是中立的吗?

is C++ abstraction Endian neutral?

本文关键字:抽象的 字节 C++      更新时间:2023-10-16

假设我有一个客户端和一个服务器,它们通过一些网络协议(例如ModbusTCP(相互通信16位数字,但该协议在这里无关紧要。

现在我知道,客户端的字节序很小(我的PC(,服务器的字节序很大(一些PLC(,客户端完全是用Boost Asio套接字C++编写的。通过此设置,我认为我必须交换从服务器接收的字节才能将数字正确存储在uint16_t变量中,但是这是错误的,因为我读取的值不正确。

到目前为止,我的理解是,我的C++抽象是将值正确地存储到变量中,而不需要我真正关心交换或字节序。请考虑以下代码片段:

// received 0x0201  (513 in big endian)
uint8_t high { 0x02 };  // first byte
uint8_t low { 0x01 };   // second byte
// merge into 16 bit value (no swap)
uint16_t val = (static_cast<uint16_t>(high)<< 8) | (static_cast<uint16_t>(low));
std::cout<<val;   //correctly prints 513

这有点让我感到惊讶,也因为如果我用指针查看内存表示,我发现它们实际上存储在客户端上的小端序中:

// take the address of val, convert it to uint8_t pointer
auto addr = static_cast<uint8_t*>(&val);
// take the first and second bytes and print them 
printf ("%d ", (int)addr[0]);   // print 1
printf ("%d", (int)addr[1]);    // print 2

所以问题是:

只要我不弄乱内存地址和指针,C++可以保证我从网络读取的值是正确的,无论服务器的字节序如何,对吗?还是我在这里错过了什么?

编辑:感谢您的回答,我想补充一点,我目前正在使用boost::asio::write(socket, boost::asio::buffer(data))将数据从客户端发送到服务器,数据是一个std::vector<uint8_t>。所以我的理解是,只要我按网络顺序填充数据,我就不应该关心我的系统(甚至 16 位数据的服务器的字节序(,因为我是在"值"上运行,而不是直接从内存中读取字节,对吧?

要使用htons系列函数,我必须更改我的底层 TCP 层以使用memcpy或类似层以及一个uint8_t*数据缓冲区,这更像是 C 式的而不是 C++式的,我为什么要这样做? 有没有我没有看到的优势?

无论字节序如何,(static_cast<uint16_t>(high)<< 8) | (static_cast<uint16_t>(low))都具有相同的行为,数字的"左"端将始终是最高有效位,字节序只会更改该位是在第一个字节还是最后一个字节中。

例如:

uint16_t input = 0x0201;
uint8_t leftByte = input >> 8; // same result regardless of endianness
uint8_t rightByte = input & 0xFF; // same result regardless of endianness
uint8_t data[2];
memcpy(data, &input, sizeof(input)); // data will be {0x02, 0x01} or {0x01, 0x02} depending on endianness

这同样适用于另一个方向:

uint8_t data[] = {0x02, 0x01};
uint16_t output1;
memcpy(&output1, data, sizeof(output1)); // will be 0x0102 or 0x0201 depending on endianness
uint16_t output2 = data[1] << 8 | data[0]; // will be 0x0201 regardless of endianness

为了确保您的代码在所有平台上都能正常工作,最好使用htonsntohs系列函数:

uint16_t input = 0x0201; // input is in host order
uint16_t networkInput = htons(input);
uint8_t data[2];
memcpy(data, &networkInput , sizeof(networkInput));
// data is big endian or "network" order
uint16_t networkOutput;
memcpy(&networkOutput, &data, sizeof(networkOutput));
uint16_t output = ntohs(networkOutput);  // output is in host order

代码的第一个片段工作正常,因为您不直接使用字节地址。由于C++语言定义了运算符"<<"和"|",因此此类代码被编译为具有独立于平台ENDIANness的正确操作结果。

代码的第二个片段证明了这一点,显示了小端系统上单独字节的实际值。

TCP/IP 网络标准化了大端格式的使用,并提供以下实用程序:

  • 在发送多字节数值之前,请使用标准函数:HTONL("主机到网络长"(和HTON("主机到网络短"(将您的值转换为网络表示形式,
  • 接收多字节数值后,使用标准函数:NTOHL("网络到主机长"(和 NTOHS("网络到主机短"(将您的值转换为特定于平台的表示形式。

(实际上,这 4 个实用程序仅在小端平台上进行转换,而在大端平台上不执行任何操作。但是一直使用它们会使您的代码独立于平台(。

使用 ASIO,您可以使用以下方法访问这些实用程序:#include <boost/asio.hpp>

您可以在Google中查找主题"man htonl"或"msdn htonl"阅读更多内容。

关于 Modbus :

对于 16 位字,Modbus 首先发送最高有效字节,这意味着它使用 Big-Endian,然后如果客户端或服务器使用 Little-Endian,它们将不得不在发送或接收时交换字节。

另一个问题是Modbus没有定义32位类型发送16位寄存器的顺序。

有些 Modbus 服务器设备首先发送最重要的 16 位寄存器,而其他设备则相反。为此,唯一的解决方案是在客户端配置中交换 16 位寄存器的可能性。

当传输字符串时,也会发生类似的问题,一些服务器而不是发送abcdef发送badcfe