C++抽象的字节序是中立的吗?
is C++ abstraction Endian neutral?
假设我有一个客户端和一个服务器,它们通过一些网络协议(例如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
为了确保您的代码在所有平台上都能正常工作,最好使用htons
和ntohs
系列函数:
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
- 在UNIX系统中使用DIR查找文件的字节大小
- 如何使用Crypto++并为RSA返回可打印的字节/字符数组
- luaL_dofile在已知良好的字节码上失败,可以使用未编译的版本
- 抽象类错误,请参阅声明" "是抽象的
- C++抽象的字节序是中立的吗?
- Valgrind:可以处理更多可能丢失的字节吗?
- QDataStream 读取和写入的字节数比 QFile::length() 报告要多
- 如何从保存在 Java 中C++的字节数组中读取数字?
- C++/Qt Valgrind 未初始化的字节
- Async_read_until限制读取的字节大小(Boost::asio)
- 为什么 sizeof 在 C++ 中给出不正确的字节数?
- 将对象的字节复制到数组并再次复制回来是否安全
- 如何获取 BITMAPFILEHEADER 和 BITMAPINFOHEADER 的字节表示形式?
- 如何将带有空字符的字节数组馈送到 std::iostream 中?
- Qt:从固定数量的字节到整数
- 如果我向一个12字节的缓冲区写入的字节数少于12,会发生什么情况
- 将以 null 结尾的字节字符串转换为原始字符串文本
- ReadFile() 读取的字节数少于其参数中的指定值
- C++命名管道客户端读取的字节不会超过 4096 字节
- 优雅和最短的方法,只保存一半的字节