【问题标题】:Are big endian and little endian values portable?大端和小端值是否可移植?
【发布时间】:2014-12-09 15:47:31
【问题描述】:

你好,我有一个小端和大端的小问题,我知道这个问题已经问过 n 次了,但我无法弄清楚以下几点

让我们将 int i=10 以二进制形式存储为 00000000 00000000 00000000 00001010 在堆栈部分,如下所示:-

00000000 |00000000 |00000000 |00001010   // In case of little endian
MSB-------------------------------------------LSB

大端序

00001010 |00000000 |00000000 |00000000   // In case of in big endian
MSB-------------------------------------------LSB

在这个小端和大端都会给出相同的输出 10 ?

那么小端和大端都有什么用呢?


在我的面试中,我被要求实现可移植到所有大小系统的代码。我回复说:

如果 int i=10 int i=10 in little endian 然后在 big endian 中它也是 10 作为输出,编译器会自行处理

这个答案正确吗?

【问题讨论】:

  • 我很确定你只是切换字节,而不是位顺序。
  • 只切换字节,不切换位!
  • “我被要求实现对所有系统都可移植的代码......” 特别是哪种代码?通过网络发送一些数字,或序列化到文件?任何其他情况通常不需要关心字节序。
  • @StuartLC 能不能给个粗略的图表?
  • 您似乎将 MSB(最高有效字节)与最高内存地址混淆了。在您的两个示例中,该数字都将存储在 LSB(最低有效字节)中。然而,在小端中,LSB 在内存中会更低。

标签: c++ c memory-management endianness


【解决方案1】:
00000000 | 00000000 | 00000000 | 00001010 // big    endian

00001010 | 00000000 | 00000000 | 00000000 // little endian

数据是以大端还是小端模式存储的,只有在您尝试访问内存中变量的较小部分时才重要,通常是通过指针,例如尝试访问 32 位的最低有效字符整数通过指向字符的指针或与字符数组的联合。另一个问题示例是,如果您将文件中的数据直接读取到 32 位整数数组中,或者如果您从 32 位整数数组中写入数据。文件中的数据通常也会以小端或大端模式存储。

据我所知,没有通用的编译时方法来确定 cpu 是在大端模式还是小端模式下运行(特定的编译器可能对此进行了定义)。您可以使用 32 位整数的联合和大小为 4 的字符数组编写测试代码。然后将联合中的整数设置为 10,并检查联合字符数组 [0] 是否包含表示小端模式的 10 ,或者如果联合字符数组[3] 包含 10,则表示大端模式。确定 CPU 是处于小端还是大端模式的其他方法也是可能的。

一旦您确定 cpu 是处于小端模式还是大端模式,您可以包含条件代码来处理这两种情况,例如文件 I/O 到/来自 32 位整数数组。如果您希望文件数据处于大端模式,但您的 cpu 处于小端模式,则必须在写入之前或从文件读取之后反转每个整数的字节。

您还可以编写代码序列以大端模式存储数据,而不管 cpu 模式如何。如果已经在大端模式下会浪费时间,但它适用于大端模式和小端模式:

char     buffer[256];
char *   ptr2char;
uint32_t uint32bit;
/* ... */
    ptr2char = buffer;    /* store uint32bit in big endian mode */
    *ptr2char++ = (uint32bit >> 24)&0xff;
    *ptr2char++ = (uint32bit >> 16)&0xff;
    *ptr2char++ = (uint32bit >>  8)&0xff;
    *ptr2char++ = (uint32bit      )&0xff;

【讨论】:

  • @rcfldr 00000000 | 00000000 | 00000000 | 00001010 // 大端我猜如果编译器从右左开始读取是小端不是吗? 00001010 | 00000000 | 00000000 | 00000000 // 小端序
  • @TomásBadan 你错了。如果您通过联合与char[] 访问int 或只是char * 指向int 的地址,那么在大小端平台和大小端平台上您将获得不同。
  • @TomásBadan 和?你在 little endian 平台上运行它并得到 10 个(我假设 ideone 使用 Intel 或 AMD),尝试在 big endian 平台上运行它。
  • @rcgldr 好的,我的最后一个问题是你的代码,因为两者都产生相同的输出,即 10 作为 bove exmple 那么为什么小端和大端来描述我们为什么需要它?
  • @yes enidaness 由 CPU 为您运行代码的平台定义,为什么它们使用不同的平台,您应该依赖 CPU 创建者文档。
【解决方案2】:

只是为了更正您的图表的整数:int i = 10;

// Big endian
&i <- address of i
00000000 |00000000 |00000000 |00001010 // In case of big endian

MSB---------------------------LSB


// Lower memory -----------------> higher memory


// Little endian

00001010 |00000000 |00000000 |00000000 // In case of in little endian
&i <- address of i
LSB---------------------------MSB

little endian 中,Least Significant Byte (LSB) 存储在 lowest 内存地址中。

big endian 中,Most Significant Byte (MSB) 存储在 lowest 内存地址中。

【讨论】:

  • 第三行应该是LSB--​​--MSB?
  • @yes 没有。 LSB 表示最低有效字节。它包含数字的最小值部分。
  • 我们怎么能说左边只有 LSB 而不是 MSB,因为我读了一些编译器从右到左开始读取指令的地方?
  • @yes 左边不是only LSBlittle endian 中为 LSBbig endian 中为 MSB。这与编译器 读取指令的方式无关。它是关于 CPU 如何读取数据的。它虽然会影响一些编译器(或汇编器)功能。
  • @Galik “只是为了更正你的图表的整数:......”虽然你在技术上是正确的,但关于 OP 中给出的错误样本,那并不当您需要关心可移植性时不回答。
【解决方案3】:

字节顺序在以下情况下很重要:

  1. 您正在直接检查/操作多字节类型的字节
  2. 您正在序列化二进制数据,或在不同架构之间传输二进制数据

直接检查/操作多字节类型的字节

例如,假设您要拆分并显示 32 位 IEEE 浮点数的二进制表示。下面显示了浮点数的布局以及大端和小端架构中相应字节的地址:

A        A+1      A+2      A+3        Big endian
-------- -------- -------- --------   s = sign bit
seeeeeee efffffff ffffffff ffffffff   e = exponent bit
-------- -------- -------- --------   f = fraction bit
A+3      A+2      A+1      A          Little Endian
-------- -------- -------- --------
A+1      A        A+3      A+2        "Middle" Endian (VAX)

符号位在浮点数的最高有效字节 (MSB) 中。在大端系统上,MSB 为字节A;在 little-endian 系统上,它位于字节 A+3 中。在一些奇怪的东西上,比如旧的 VAX F float,它卡在字节 A+1 的中间。

因此,如果您想屏蔽符号位,可以执行以下操作:

float val = some_value();
unsigned char *p = (unsigned char *) &val; // treat val as an array of unsigned char

// Assume big-endian to begin with
int idx = 0;

if ( little_endian() )
  idx = 3;

int sign = (p[idx] & 0x80) >> 7

序列化或传输二进制数据

再举一个例子,您希望保存二进制(不是文本)数据,以便它可以被大端或小端系统读取,或者您要将二进制数据从一个系统传输到另一个系统。 Internet 传输的约定是 big-endian(MSB 优先),因此在通过 'net 发送消息之前,您将使用像 htonl(主机到网络长)和 htons(主机到-network short)在发送数据之前执行任何必要的字节交换:

uint32_t host_value = some_value();
uint32_t network_value = htonl( host_value ); 
send( sock, &network_value, sizeof network_value, 0 ); 

在 x86 这样的 little-endian 系统上,htonl 会将 host_value 的字节从 0,1,2,3 重新排序为 3,2,1,0,并将结果保存到 network_value。在大端系统上,htonl 基本上是一个空操作。逆运算为ntohlntohs

如果您没有执行上述操作,那么您通常根本不必担心字节顺序。

【讨论】:

    【解决方案4】:

    首先:您实际上混淆了大端和小端字节顺序,正如@rcgldr's@Galik's 答案中所指出的那样。正如您在示例中显示的那样,字节顺序正好相反:

    00000000 | 00000000 | 00000000 | 00001010 // big endian
    
    00001010 | 00000000 | 00000000 | 00000000 // little endian
    

    至于你的假设和问题:

    “在这个小端和大端都会给出相同的输出 10 ?”

    这取决于您所指的输出类型

    1. 无论主机的字节顺序如何,以下代码都是可移植的,在任何情况下,输出都是格式化文本 ("10"):

    int i = 10;
    
    std::cout << i << std::endl;
    

    1. 以下代码不可移植。由于值是以二进制形式写入的,因此字节顺序将保持逐字:

    int i = 10;
    
    std::ofstream binfile("binaryfile.bin");
    binfile.write((const char*)&i,sizeof(int));
    

    如果文件应该在具有不同字节序的主机上读取,则后一个示例将不起作用。

    htonl(), ntohl() 函数族可以解决这类问题。通常同意使用网络字节序(big-endian)格式来存储二进制数据或通过网络发送。

    这是一个简短的示例,如何使用提到的字节顺序转换函数:


    int i = 10;
    int sendValue = htonl(i); // convert the value of i to network byte order
    
    std::ofstream binfile("binaryfile.bin");
    binfile.write((const char*)&sendValue,sizeof(int)); // write the adapted value
    

    std::ifstream binfile("binaryfile.bin");
    int recvValue = 0;
    binfile.read((char*)&recvValue,sizeof(int)); // read the value in network byte order
    int i = ntohl(recvValue); // convert the value of recvValue to host byte order
    

    “那这两个小端和大端有什么用呢?”

    不同格式的原因(使用)是,有不同的 CPU 架构,它们使用不同的方式来表示内存中的整数值,具体取决于针对特定硬件设计访问它们的最有效方式。
    这些架构差异没有更坏/更好的地方,这就是为什么它被称为endianess。这种造币的起源来自于 Johnatan Swift 的小说“格列佛游记”,并在 Daniel Cohen 的文章 "ON HOLY WARS AND A PLEA FOR PEACE" 中首次(?)提及。


    “如果 int i=10 int i=10 in little endian 然后在 big endian 中它也是 10 作为输出,编译器会自己做”

    嗯,从上面的例子可以看出,这个答案是错误的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-18
      • 2017-03-11
      相关资源
      最近更新 更多