公 告

欢迎各位网友添加友情链接,在您添加本博客:http://linux0818.blogspot.com/ 做为链接后, E-mail:linux0818@gmail.com给我,我将将您的网址添加到本博客。

2009年9月10日星期四

转载:Endianness一点通

原文:http://packetmania.bokee.com/

Endianness 的问题实质就是关于计算机如何存储大的数值的问题。

我们知道一个基本存储单元可以保存一个字节,每个存储单元对应一个地址。对于大于十进制255(16进制0xff)的整数,需要多个存储单元。例如,4660对应于0x1234,需要两个字节。不同的计算机系统使用不同的方法保存这两个字节。在我们常用的PC机中,低位的字节0x34保存在低地址的存储单元,高位的字节0x12保存在高地址的存储单元;而在Sun工作站中,情况恰恰相反,0x34位于高地址的存储单元,0x12位于低地址的存储单元。前一种就被称为Little Endian,后一种就是Big Endian。

如何记住这两种存储模式?其实很简单。首先记住我们所说的存储单元的地址总是由低到高排列。对于多字节的数值,如果先见到的是低位的字节,则系统就是Little Endian的,Little 就是"小,少"的意思,也就对应"低"。相反就是Big Endian,这里 Big "大"对应"高"。

为了加深对Endianness的理解,让我们来看下面的C程序例子:

 char a = 1;

char b = 2;
                                                  地址偏移量     内存映像

short c = 255; /* 0x00ff */          0x0000:          01 02 FF 00

 long d = 0x44332211;                 0x0004:          11 22 33 44

在右侧我们可以见到在基于Intel 80x86的系统上的内存映像,显然我们可以马上判定这一系统是Little Endian的。对于16位的整形数(short)c,我们先见到其低位的0xff,下一个才是0x00。同样对于32位长整形数(long)d,在最低的地址0x0004存的是最低位字节0x11。如果是在Big Endian的计算机中,则地址偏移量从0x0000到0x0007的整个内存映像将为:01 02 00 FF 44 33 22 11。

所有计算机处理器都必须在这两种Endian间作出选择。但某些处理器(如MIPS和IA-64)支持两种模式,可由编程者通过软件或硬件设置一种Endian。以下是一个处理器类型与对应的Endian的简表:

  • 纯Big Endian: Sun SPARC, Motorola 68000Java Virtual Machine
  • Bi-Endian, 运行Big Endian模式: MIPS运行IRIX, PA-RISC,大多数PowerPowerPC系统
  • Bi-Endian, 运行Little Endian模式: MIPS  运行Ultrix,大多数DEC Alpha, IA-64运行Linux
  • Little Endian: Intel x86AMD64,DEC VAX
     如何在程序中检测本系统的Endianess?可调用下面的函数来快速验证,如果返回值为1,则为Little Endian;为0则是Big Endian:
    int testendian() {
        int x = 1;
        return *((char *)&x);
    }

    Endianness对于网络通信也很重要。试想当Little Endian系统与Big Endian的系统通信时,如果不做适当处理,接收方与发送方对数据的解释将完全不一样。比如对以上C程序段中的变量d,Little Endian发送方发出11 22 33 44四个字节,Big Endian接收方将其转换为数值0x11223344。这与原始的数值大相径庭。为了解决这个问题,TCP/IP协议规定了专门的"网络字节次序",即无论计算机系统支持何种Endian,在传输数据时,总是数值最高位的字节最先发送。从定义可以看出,网络字节次序其实是对应Big Endian的。

    为了避免因为Endianness造成的通信问题,及便于软件开发者编写易于平台移植的程序,特别定义了一些C语言预处理的宏来实现网络字节与主机字节次序之间的相互转换。htons()和htonl()用来将主机字节次序转成网络字节次序,前者应用于16位无符号数,后者应用于32位无符号数。ntohs()和ntohl()实现反方向的转换。这四个宏的原型定义可参考如下(Linux系统中可在netinet/in.h文件里找到):

    #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."  
    
  •