什么时候Endianness成为一个因素

When does Endianness become a factor?

本文关键字:一个 Endianness 什么时候      更新时间:2023-10-16

根据我的理解,是指组成多字节字的字节顺序不同,至少在最典型的情况下是这样。因此,一个16位整数可以存储为0xHHLL0xLLHH

假设我没有错,我想知道的是,当两台计算机之间发送信息时,端序何时成为一个主要因素,其中端序可能是不同的,也可能不是。

  • 如果我发送一个短整数1,在一个字符数组的形式,没有校正,它是接收和解释为256?

  • 如果我用下面的代码分解和重组短整数,是否字节序不再是一个因素?

     // Sender:
     for(n=0, n < sizeof(uint16)*8; ++n) {
         stl_bitset[n] = (value >> n) & 1;
     };
     // Receiver:
     for(n=0, n < sizeof(uint16)*8; ++n) {
         value |= uint16(stl_bitset[n] & 1) << n;
     };
    
  • 是否有一种标准的方式来补偿端序?

非常抽象地说,端序性是将变量重新解释为字符数组的属性。

实际上,当您从read()write()到外部字节流(如文件或套接字)时,这一点非常重要。或者,抽象地说,当序列化数据时,端序很重要(本质上是因为序列化的数据没有类型系统,只是由哑字节组成);在编程语言中,无关,因为该语言只对进行操作,而不对表示进行操作。从一个到另一个是你需要深入研究细节的地方。

with - writing:

uint32_t n = get_number();
unsigned char bytesLE[4] = { n, n >> 8, n >> 16, n >> 24 };  // little-endian order
unsigned char bytesBE[4] = { n >> 24, n >> 16, n >> 8, n };  // big-endian order
write(bytes..., 4);

这里我们可以只写reinterpret_cast<unsigned char *>(&n),结果将取决于系统的端序。

和阅读:

unsigned char buf[4] = read_data();
uint32_t n_LE = buf[0] + buf[1] << 8 + buf[2] << 16 + buf[3] << 24; // little-endian
uint32_t n_BE = buf[3] + buf[2] << 8 + buf[1] << 16 + buf[0] << 24; // big-endian

同样,这里我们可以说,uint32_t n = *reinterpret_cast<uint32_t*>(buf),结果将取决于机器的端序。


可以看到,对于整型,如果使用代数输入和输出操作,您不必知道自己系统的端序,只需知道数据流的端序。对于其他数据类型(如double),问题更为复杂。

作为记录,如果您在设备之间传输数据,您应该几乎总是使用网络字节排序与ntohl, htonl, ntohs, htons。无论您的系统和目标系统使用什么,它都将转换为网络字节顺序标准。当然,这两个系统都应该这样编程——但它们通常是在网络场景中。

  1. 不,虽然你的大致想法是对的。你忽略了一个事实,即使它通常是一个串行连接,网络连接(至少大多数网络连接)仍然保证在八位字节(字节)级别上正确的端序——也就是说,如果你在小端序机器上发送一个值为0x12的字节,它在大端序机器上仍然会被接收为0x12。

    看一个简短的,如果你看十六进制的数字,可能会有帮助。它从0x0001开始。将其分解为两个字节:0x00 0x01。在接收时,它将被读取为0x0100,结果是256。

  2. 由于网络在八位字节级别处理端序,所以通常只需要补偿字节的顺序,而不是字节中的位。

  3. 可能最简单的方法是在发送时使用htons/htonl,在接收时使用ntohs/ntohl。当/如果这还不够,还有许多替代方案,如XDR, ASN.1, CORBA IIOP, Google协议缓冲区等。

补偿的"标准方式"是"网络字节顺序"的概念已经定义,几乎总是(AFAIK)为大端序。

发送方和接收方都知道有线协议,如果有必要,会在发送前和接收后进行转换,以给应用程序正确的数据。但是这种转换发生在网络层中,而不是在应用程序中。

两个字符都有我所知道的优势:

    大端序在概念上更容易理解,因为它类似于我们的位置数字系统:从最高有效到最低有效。当为多个内存大小重用内存引用时,
  1. Little-endian很方便。简单地说,如果你有一个指向小端unsigned int*的指针,但你知道那里存储的值是<你可以将指针强制转换为unsigned char*。>

序位总是一个问题。有人会说,如果你知道连接到网络的每台主机都运行相同的操作系统,那么你就不会有问题。这是真的,直到它不是。您总是需要发布一个详细说明在线数据的EXACT格式的规范。它可以是你想要的任何格式,但每个端点都需要理解该格式并能够正确地解释它。

通常,协议使用大端位数表示数值,但如果每个人都不兼容IEEE 754等,这有局限性。如果您可以承担开销,那么使用XDR(或您最喜欢的解决方案)并确保安全。

这里有一些关于C/c++端中立代码的指导原则。显然,这些都是"避免规则"……所以,如果代码具有这些"特性",那么它可能容易出现与尾端相关的bug !!(这是我在Dr Dobbs上发表的关于Endianness的文章)

  1. 避免使用联合来组合不同的多字节数据类型。

  2. 避免访问字节数据类型以外的字节数组。

  3. 避免使用位域和字节掩码(由于存储的布局依赖于端序,字节的屏蔽和位字段的选择是端序敏感的)

  4. 避免将指针从多字节类型转换为其他字节类型。
    (当指针从一种类型强制转换为另一种类型时,源的端序(即:原始目标)丢失,后续处理可能不正确)

您不必担心,除非您处于系统的边界。通常情况下,如果你在谈论stl,你已经越过了那个边界。

序列化协议的任务是指示/确定如何将一系列字节转换为您要发送的类型,是内置类型还是自定义类型。

如果您只讨论内置的,那么您可以使用由您的环境提供的工具提供的机器抽象]