Big-endian和Little-endian

简而言之:
Big endian machine: It thinks the first byte it reads is the biggest.
Little endian machine: It thinks the first byte it reads is the littlest.
举个例子,从内存地址0x0000开始有以下数据
0x0000      0x12
0x0001      0x34
0x0002      0xab
0x0003      0xcd
如果我们去读取一个地址为0x0000的四个字节变量,若字节序为big-endian,则读出
结果为0x1234abcd;若字节序位little-endian,则读出结果为0xcdab3412.
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
                 big-endian      little-endian
0x0000      0x12               0xcd
0x0001      0x23               0xab
0x0002      0xab               0x34
0x0003      0xcd               0x12
x86系列CPU都是little-endian的字节序.

-------------------------------------------------------------

再网上查阅了很多资料(包括中英版的MSDN),反复体会,才基本弄明白一些Big-Endian和Little-Endian的含义,先总结如下:

Big-Endian 和 Little-Endian 字节排序

字节排序 含义 Big-Endian 一个Word中的高位的Byte放在内存中这个Word区域的低地址处。 Little-Endian 一个Word中的低位的Byte放在内存中这个Word区域的低地址处。
必须注意的是:表中一个Word的长度是16位,一个Byte的长度是8位。如果一个数超过一个Word的长度,必须先按Word分成若干部分,然后每一部分(即每个Word内部)按Big-Endian或者Little-Endian的不同操作来处理字节。

一个例子:
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
                big-endian     little-endian
0x0000     0x12              0xcd
0x0001     0x34              0xab
0x0002     0xab              0x34
0x0003     0xcd              0x12
(注意:0xab换算成2进制是10101011,是个8位的数。我刚才居然当成4位了,自己把自己搞晕了,shit。)

-----------
疑问:为什么要以一个Word为基础单位来分割而不是一个DoubleWord或者Byte?究竟就是这么定义的还是跟具体的CPU有关,跟具体的模式(比如说16位模式、32位模式、64位模式的LE和BE定义会不同)有关?我实在是不清楚,望知道的大侠不吝指点,也希望有这方面资料(文章、代码)的朋友贴一些上来。

 

------------------------------------------------------------------

什么是字节序?
   字节序,顾名思义字节的顺序,再多说两句就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。其实大部分人在实际的开发中都很少会直接和字节序打交道。唯有在跨平台以及网络程序中字节序才是一个应该被考虑的问题。在所有的介绍字节序的文章中都会提到字节序分为两类:Big-Endian和Little-Endian。引用标准的Big-Endian和Little-Endian的定义如下:
a) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
b) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
c) 网络字节序:TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。
PS:有些文章中称低位字节为最低有效位,高位字节为最高有效位。
Big endian means that the most significant byte of any multibyte data field is stored at the lowest memory address, which is also the address of the larger field.
Little endian means that the least significant byte of any multibyte data field is stored at the lowest memory address, which is also the address of the larger field.

   什么是高/低地址端 什么是高/低字节
   首先我们要知道我们C程序映像中内存的空间布局情况:在《C专家编程》中或者《Unix环境高级编程》中有关于内存空间布局情况的说明,大致如下图:
----------------------- 最高内存地址 0xffffffff
| 栈底
.
. 栈
.
栈顶
-----------------------
|
|
\|/

NULL (空洞)

/|\
|
|
-----------------------

-----------------------
未初始化的数据
----------------(统称数据段)
初始化的数据
-----------------------
正文段(代码段)
----------------------- 最低内存地址 0x00000000

以上图为例如果我们在栈上分配一个unsigned char buf[4],那么这个数组变量在栈上是如何布局的呢?看下图:
栈底 (高地址)
----------
buf[3]
buf[2]
buf[1]
buf[0]
----------
栈顶 (低地址)
现在我们弄清了高/低地址,接着考虑高/低字节。如果我们有一个32位无符号整型0x12345678,那么高位是什么,低位又是什么呢?其实很简单。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿 0x12345678来说,从高位到低位的字节依次是0x12、0x34、0x56和0x78。
高/低地址端和高/低字节都弄清了。我们再来回顾一下Big-Endian和Little-Endian的定义,并用图示说明两种字节序:
以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) -- 低位
--------------
栈顶 (低地址)

   现有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。那么在跨平台或网络程序中如何实现字节序的转换呢?这个通过C语言的移位操作很容易实现,例如下面的宏:

#if defined(BIG_ENDIAN) && !defined(LITTLE_ENDIAN)

#define htons(A)   (A)
#define htonl(A)     (A)
#define ntohs(A)   (A)
#define ntohl(A)    (A)

#elif defined(LITTLE_ENDIAN) && !defined(BIG_ENDIAN)

#define htons(A)     ((((uint16)(A) & 0xff00) >> 8) | \
                               (((uint16)(A) & 0x00ff) << 8))
#define htonl(A)     ((((uint32)(A) & 0xff000000) >> 24) | \
                              (((uint32)(A) & 0x00ff0000) >> 8) | \
                              (((uint32)(A) & 0x0000ff00) << 8) | \
                              (((uint32)(A) & 0x000000ff) << 24))
#define ntohs htons
#define ntohl htohl

#else

#error "Either BIG_ENDIAN or LITTLE_ENDIAN must be #defined, but not both."

#endif

 

   如何检查处理器是big-endian还是little-endian?
   由于联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性就可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。例如:
   int checkCPUendian(){
        union {
             unsigned int a;
             unsigned char b;            
        }c;
        c.a = 1;
        return (c.b == 1);       
   }   /*return 1 : little-endian, return 0:big-endian*/
   


------------------------------------------------------------------------------------------
最近接触到网络字节序的概念 查了查资料 不是很明白 先引用一段材料:

字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端、大端两种字节顺序。小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处;大端字节序是高字节数据存放在低地址处,低字节数据存放在高地址处。基于X86平台的PC机是小端字节序的,而有的嵌入式平台则是大端字节序的。因而对int、uint16、uint32等多于1字节类型的数据,在这些嵌入式平台上应该变换其存储顺序。通常我们认为,在空中传输的字节的顺序即网络字节序为标准顺序,考虑到与协议的一致以及与同类其它平台产品的互通,在程序中发数据包时,将主机字节序转换为网络字节序,收数据包处将网络字节序转换为主机字节序。

在本LINUX的书里介绍到INTEL的CPU使用的小端字节序 其他比MOTOROLA
68000系列CPU使用的是大端字节序 如果不转换 将数据通过网络发出时 比如MOTOROLA发一个16位数据:0X1234 传送到INTEL时
就被INTEL解释为0X3412 也就是4660成了13330 所以有时候需要一些函数来进行大小端字节序的转换

关于这大小字节序的概念不是很想的明白 数据在
内存里是具体怎么存放的形式?为什么会有CPU解释的不同?数据不是按12345678……这样的顺序一直排列的么?希望大人赐教 谢谢

xuechao 回复于:2003-11-20 17:11:57 没人顶吗?各位给条路啊 流氓无产者 回复于:2003-11-20 18:57:00 不就是大小印地安记法吗
1)从低到高存 (liittle edian)
例:0x1234
内存中是0x34 0x12
2)从高到低存 (big edian)
例:0x1234
内存中是0x12 0x34 sky-walker 回复于:2003-11-20 19:43:46 如:    一个多字节值 0xFECDBA98,内存从地址100开始存放

降序:       FE | CD | BA   |   98---->对应地址100 |   101   | 102 | 103

升序:      98 |   BA   |   CD   |   FE ---->same above

注意,我们的书写表示法是从低字节位--->高字节位


至于为什么CPU解释不同,可能是由于不同的体系构架在起始竞争时人为地制造

和对手不兼容性......害的我们这么惨,一遇到移植就要注意这个 :twisted:

C代码的移植相对简单原因之一就是由于C的连续存储数据永远保持从低地址到高

地址的索引........ xuechao 回复于:2003-11-20 22:32:27 小端字节序就是升序排列那种?
我们的书写表示法是从低字节位--->高字节位    这个是什么意思呢?难道FECDBA98是从低到高(从左到右)吗?

还有它排列是按单个字节来 还是按数据类型的?比如说是INT型就按两个两个排 就象0X1234 和0X3412 而不是0X1234 和0X4321? sky-walker 回复于:2003-11-20 23:32:56 "我们的书写表示法是从低字节位--->高字节位 这个是什么意思呢?难道FECDBA98是从低到高(从左到右)吗?"




"还有它排列是按单个字节来 还是按数据类型的"

对于多字节数据才有这么一出

这样理解吧
譬如:
内存地址生长方向为:   从左到右   由低到高(这是不变的)

数据为:                     0x89ABCDEF

降序(Big-endian)大端字节序      存储时         由左到右

升序(Little-endian)小端字节序    存储时         由右向左

可以自己编一个小程序验证一下(用C的数组)

更简单的调用VC里的checkEndian() xuechao 回复于:2003-11-21 13:22:25 understood
thanx!! wqch 回复于:2004-04-27 15:05:29 关于网络字节序和主机字节序的转换     ytjia(原作)  
  
关键字      网络字节序,Socket
  


主机和网络字节序的转换

最近使用C#进行网络开发,需要处理ISO8583报文,由于其中有些域是数值型的,于是在传输的时候涉及到了字节序的转换。字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有两种字节顺序,根据他们所处的位置我们分别称为主机节序和网络字节序。

通常我们认为网络字节序为标准顺序,封包的时候,将主机字节序转换为网络字节序,拆包的时候要将网络字节序转换为主机字节序。原以为还要自己写函数,其实网络库已经提供了。

主机到网络:short/int/long IPAddress.HostToNetworkOrder(short/int/long)

网络到主机:short/int/long IPAddress.NetworkToHostOrder(short/int/long)



主机字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处,如:

int x=1;     //此时x为主机字节序:[1][0][0][0] 低位到高位

int y=65536 //此时y为主机字节序:[0][0][1][0] 低位到高位

我们通过主机到网络字节序的转换函数分别对x和y进行转换得到他们对应的网络字节序值,网络节序是高字节数据存放在低地址处,低字节数据存放在高地址处,如:

int m=IPAddress.HostToNetworkOrder(x);

//此时m为主机[[color=red:4d5c53bbac]改为网络[/color:4d5c53bbac]]字节序:[0][0][0][1] 高位到低位

int n=IPAddress.HostToNetworkOrder(y);

//此时n为主机[[color=red:4d5c53bbac]改为网络[/color:4d5c53bbac]]字节序:[0][1][0][0] 高位到低位



经过转换以后,我们就可以通过

byte[]btValue=BitConverter.GetBytes(m);

得到一个长度为4的byte数组,然后将这个数组设置到报文的相应位置发送出去即可。

同样,收到报文后,可以将报文按域拆分,得到btValue,使用

int m=BitConverter.ToInt32(btValue,0);//从btValue的第0位开始转换

得到该域的值,此时还不能直接使用,应该再用网络到主机字节序的转换函数进行转换:

int x=IPAddress.NetworkToHostOrder(m);

这时得到的x才是报文中的实际值。

Little-Endian主要用在我们现在的PC的CPU中,Big-Endian则应用在目前的Mac机器中(注意:是指Power系列 处理器)