大小端介绍

一.大小端名字的由来:

在乔纳森·斯威夫特的著名讽刺小说《格列夫游记》中,小人国内部分裂成Big-endian和Little-endian两派,区别在于一派要求从鸡蛋的大头把鸡蛋打破,另一派要求从鸡蛋的小头把鸡蛋打破。斯威夫特借以讽刺英国的政党之争,在计算机工业中指数据储存顺序的分歧。

二.什么是大小端:

1.定义:大端是指数据的高位字节保存在低位地址中,低位字节保存在高位地址中。

          小端是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中。

其实看到上面这个在百度上查到的定义的时候,并不能让我理解什么是大小端。下面就来看几个例子,让我们更好的理解大小端到底是什么:(这些例子也是我在百度上查到的,并非自创!因为通过这些让自己更好的理解了大小端的问题,所以拿出来记录一下,也和大家分享。)

2.举例理解

(1)比如数字0x12 34 56 78在内存中的表示形式。

       1.大端模式:(其实大端模式才是我们直观上认为的模式,和字符串存储的模式差类似)

            低地址 --------------------> 高地址
            0x12  |  0x34  |  0x56  |  0x78

      2.小端模式:

           低地址 --------------------> 高地址
           0x78  |  0x56  |  0x34  |  0x12

2)再看一下在数组中的存储情况:

  以unsigned int value = 0x12345678为例,分别看看在两种字节序下其存储情况,我们可以用unsigned char buf[4]来表value:

Big-Endian: 低地址存放高位,如下:
高地址
        ---------------
        buf[3] (0x78) -- 低位
        buf[2] (0x56)
        buf[1] (0x34)
        buf[0] (0x12) -- 高位
        ---------------

低地址


Little-Endian: 低地址存放低位,如下:
高地址
        ---------------
        buf[3] (0x12) -- 高位
        buf[2] (0x34)
        buf[1] (0x56)
        buf[0] (0x78) -- 低位
        --------------

低地址

一般我们在编写代码时用到数组的存取都是以大端的思想来理解运用的。

(3)大端小端没有谁优谁劣,各自优势便是对方劣势:

小端模式 :强制转换数据不需要调整字节内容
大端模式 :符号位的判定固定为第一个字节,容易判断正负。

相信通过上述例子大家对大小端的概念应该有了初步的了解。至少要会区分大端和小端。

(4)编写两个小的测试程序来判断机器的字节序:(以下代码均已运行通过)

 1 ) 代码实现 :编译环境 VC++6.0  结果输出为  小端存储

#include "stdafx.h"
bool IsBigEndian1()  
{  int a = 1;  //00 00 00 01//通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分char b =  *(char *)&a; /* char b = *(char*)&a;这句话到底干了什么事呢?详解:&a是通过'&'取地址符获取到了a的地址或者可以认为是个指向int类型数据的指针(char *)&a 则把&a强制转换成 char *类型的指针,本句重点就是这个时候发生了截断!<因为还未深入学习指针所以此处对为何发生截断不予详细解释,等后续系统学习后再做补充>截断后,指针(char *)&a 内存放的是存放‘1’的低地址,也就是说此处指向的数值 即b仅为原 始数据的一部分。因为已经确定 是低地址,所以可以通过此处地址指向的数值来判断此环境的存储模式是大端还是小端如果b = 0 即 低地址存放高位数据 则为大端模式如果b = 1 即 低地址存放低位数据 则为小端模式*///printf("b=%0x\n",b); 此句如果被注释  将会输出 b=34 以此更直观的看到效果if( b == 0)  {  return true;  }  return false;  
}
int main(int argc, char* argv[])
{if(IsBigEndian1()==true){printf("大端存储\n");}else{printf("小端存储\n");}return 0;
}

2)联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性可以轻松地获得了CPU对内存采用Little-endian还是 Big-  endian模式读写:

#include "stdafx.h"
/*
联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性
可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写:
*/
bool IsBigEndian2()  
{  union NUM  {  int a;  char b;  }num;  num.a = 0x1234;  if( num.b == 0x12 )  {  return true;  }  return false;  
}
int main(int argc, char* argv[])
{if(IsBigEndian2()==true){printf("Big-endian\n");}else{printf("Little-endian\n");}return 0;
}

三.为什么要进行端之分

1.百科解析

这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如何将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型x,在内存中的地址为0x0010,x的值为0x1122,那么0x11为高字节,0x22为低字节。对于 大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以随时在程序中(在ARM Cortex 系列使用REV、REV16、REVSH指令 [1]  )进行大小端的切换。

2.常见字节序:

  常见CPU的字节序

Big Endian : PowerPC、IBM、Sun
Little Endian : x86、DEC
ARM既可以工作在大端模式,也可以工作在小端模式。

  常见文件的字节序

Adobe PS – Big Endian
BMP – Little Endian
DXF(AutoCAD) – Variable
GIF – Little Endian
JPEG – Big Endian
MacPaint – Big Endian
RTF – Little Endian
 
另外,在C语言中,默认是小端(但在一些对于单片机的实现中却是基于大端,比如Keil 51C),Java是平台无关的,默认是大端。在网络上传输数据普遍采用的都是大端。

四.多角度理解

1.从软件的角度上,不同端模式的处理器进行数据传递时必须要考虑端模式的不同。如进行网络数据传递时,必须要考虑端模式的转换。 在对普通文件进行处理也需要考虑端模式问题。在大端模式的处理器下对文件的32,16位读写操作所得到的结果与小端模式的处理器不同。单纯从软件的角度理解上远远不能真正理解大小端模式的区别。事实上,真正的理解大小端模式的区别,必须要从系统的角度,从指令集,寄存器和数据总线上深入理解,大小端模式的区别。

2.从系统的角度上,处理器在硬件上由于端模式问题在设计中有所不同。从系统的角度上看,端模式问题对软件和硬件的设计带来了不同的影响,当一个处理器系统中大小端模式同时存在时,必须要对这些不同端模式的访问进行特殊的处理。如果从实际应用的角度说,采用小端模式的处理器需要在软件中处理端模式的转换,因为采用小端模式的处理器在与小端外设互连时,不需要任何转换。而采用大端模式的处理器需要在硬件设计时处理端模式的转换。大端模式处理器需要在寄存器,指令集,数据总线及数据总线与小端外设的连接等等多个方面进行处理,以解决与小端外设连接时的端模式转换问题。在寄存器和数据总线的位序定义上,基于大小端模式的处理器有所不同。

3.实际例子

     虽然很多时候,字节序的工作已由编译器完成了,但是在一些小的细节上,仍然需要去仔细揣摩考虑,尤其是在以太网通讯、MODBUS通讯、软件移植性方面。这里,举一个MODBUS通讯的例子。在MODBUS中,数据需要组织成数据报文,该报文中的数据都是大端模式,即低地址存高位,高地址存低位。假设有一16位缓冲区m_RegMW[256],因为是在x86平台上,所以内存中的数据为小端模式:m_RegMW[0].low、m_RegMW[0].high、m_RegMW[1].low、m_RegMW[1].high……
为了方便讨论,假设m_RegMW[0] = 0x3456; 在内存中为0x56、0x34。
      现要将该数据发出,如果不进行数据转换直接发送,此时发送的数据为0x56,0x34。而Modbus是大端的,会将该数据解释为0x5634而非原数据0x3456,此时就会发生灾难性的错误。所以,在此之前,需要将小端数据转换成大端的,即进行高字节和低字节的交换,此时可以调用步骤五中的函数BigtoLittle16(m_RegMW[0]),之后再进行发送才可以得到正确的数据。

五.个人总结

   我是一名软件工程专业的在校大学生,因为在最近的一次课程中遇到了大小端的问题,并且通过课上老师的讲解,我并没有很好的理解这个内容。因此今天上午专门用了几个小时来查询这方面的资料。最后决定再花一部分的时间,将我自己查到的和理解到的一些东西整理一下。以便在自己后续学习中再次遇到类似问题的时候回顾,也可以给和我一样需要这方面资料信息的朋友做一个参考。另外,此篇博客虽然算是我写的,但是其中大部分内容还是在百度上查询到的。可能我只是将帮助我学习的一些信息进行了一次汇总。其中部分也是按我个人的理解进行的阐述。仅供参考!

最后对我个人来说其实这块内容在当下的学习中用到的不多,可能就像老师说的仅是为了帮助我们更好的理解,编写代码时,数据在内存中的存储问题。但是知识是一点点积累的。希望在后续的学习和工作中还能遇见。

  

本文链接:https://my.lmcjl.com/post/10291.html

展开阅读全文

4 评论

留下您的评论.