首页 > 代码库 > 我如何确定一个机器的字节顺序是大端还是小端?

我如何确定一个机器的字节顺序是大端还是小端?

通常的技巧是使用一个指针:

  int x = 1;  if(*(char *)&x == 1)    printf("little-endian\n");  else  printf("big-endian\n");

或者一个union:

  union {    int i;    char c[sizeof(int)];  } x;  x.i = 1;  if(x.c[0] == 1)    printf("little-endian\n");  else  printf("big-endian\n");

(注意在简单的大端和小端外还有其它字节顺序的可能。)

同时参见问答10.16和20.9b。

参考:H&S Sec. 6.1.2 pp. 163-4

Origin : http://www.c-faq.com/misc/endiantest.html

http://www.c-faq.com/index.html

 

定义一联合体.union A{int a;char b[4];}a; a.a=1;if (a.b[0] == 0) {    //大端} else {    //小端}




方法一:
const int endian = 1;
#define is_bigendian() ( (*(char*) &endian) == 0 )
#define is_littlendbian() ( (*(char*) &endian) == 1 )

 方法二:

bool    IsLittleEndian()
{
union   
{    
long    val;
char    Char[sizeof(long)];
}u;
//    1-小端(Intel);    0-大端(Motor)
u.val = 1;  
if ( u.Char[0] == 1 )
{
// 小端
return true;
}  
else if ( u.Char[sizeof(long)-1] == 1 )
{
// 大端
return false;  

throw( "Unknown!" );
}



大小端的问题,指的是字长大于8bit的处理器,在处理一个字的时候,将其拆分成多个字节的表示方法。对于大端处理器,高位在低地址,低位在高地址。如0x12345678,在内存中这样表示:
地址 00 01 02 03
数据 12 34 56 78
一般,ARM/MIPS/PPC都是大端处理器。

而对于小端处理器,低位在低地址,高位在高地址。0x12345678这样排列:
地址 00 01 02 03
数据 78 56 34 12
x86属于小端。

这两种排列方法没有好坏之分,但是,对于不同体系结构的处理器之间通讯的时候,有可能产生问题。如MIPS机器发送0x12345678,到了x86接收的时候,就成了0x78563412。

因此,人们制定了网络字节序——和大端字节序一致,高字节先发,低字节后发。这样,对于x86和MIPS/ARM/PPC处理器,对数据的处理流程就不一样了。x86需要将收到的数据按字节序颠倒以后处理,而MIPS/ARM/PPC可以直接处理。编写程序的工程师们为了让代码可移植,定义了以下的宏:
htonl /× 主机序转网络序,long int类型 ×/
ntohl /× 网络序转主机序,long int类型 ×/
htons /× 主机序转网络序,short int类型 ×/
ntohs /× 网络序转主机序,short int类型 ×/


对于小端处理器,这个宏返回颠倒字节序后的值,而对于大端处理器什么都不做。





端模式(Endian)的这个词出自Jonathan Swift书写的《格列佛游记》。这本书根据将鸡蛋敲开的方法不同将所有的人分为两类,从圆头开始将鸡蛋敲开的人被归为Big Endian,从尖头开始将鸡蛋敲开的人被归为Littile Endian。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。在计算机业Big Endian和Little Endian也几乎引起一场战争。在计算机业界,Endian表示数据在存储器中的存放顺序。下文举例说明在计算机中大小端模式的区别。

如果将一个32位的整数0x12345678存放到一个整型变量(int)中,这个整型变量采用大端或者小端模式在内存中的存储由下表所示。为简单起见,本书使用OP0表示一个32位数据的最高字节MSB(Most Significant Byte),使用OP3表示一个32位数据最低字节LSB(Least Significant Byte)。

地址偏移

大端模式

小端模式

0x00

12(OP0)

78(OP3)

0x01

34(OP1)

56(OP2)

0x02

56(OP2)

34(OP1)

0x03

78(OP3)

12(OP0)

如果将一个16位的整数0x1234存放到一个短整型变量(short)中。这个短整型变量在内存中的存储在大小端模式由下表所示。

地址偏移

大端模式

小端模式

0x00

12(OP0)

34(OP1)

0x01

34(OP1)

12(OP0)

由上表所知,采用大小模式对数据进行存放的主要区别在于在存放的字节顺序,大端方式将高位存放在低地址,小端方式将高位存放在高地址。采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。到目前为止,采用大端或者小端进行数据存放,其孰优孰劣也没有定论。

有的处理器系统采用了小端方式进行数据存放,如Intel的奔腾。有的处理器系统采用了大端方式进行数据存放,如IBM半导体和Freescale的PowerPC处理器。不仅对于处理器,一些外设的设计中也存在着使用大端或者小端进行数据存放的选择。

因此在一个处理器系统中,有可能存在大端和小端模式同时存在的现象。这一现象为系统的软硬件设计带来了不小的麻烦,这要求系统设计工程师,必须深入理解大端和小端模式的差别。大端与小端模式的差别体现在一个处理器的寄存器,指令集,系统总线等各个层次中。

编写一段程序判断系统中的CPU 是Little endian 还是Big endian 模式?
分析:
作 为一个计算机相关专业的人,我们应该在计算机组成中都学习过什么叫Little endian 和Big endian。Little endian 和Big endian 是CPU 存放数据的两种不同顺序。对于整型、长整型等数据类型,Big endian 认为第一个字节是最高位字节(按照从低地址到高地址的顺序存放数据的高位字节到低位字节);而Little endian 则相反,它认为第一个字节是最低位字节(按照从低地址到高地址的顺序存放数据的低位字节到高位字节)。
例如,假设从内存地址0x0000 开始有以下数据:
0x12 0x34 0xab 0xcd
如 果我们去读取一个地址为0x0000 的四个字节变量,若字节序为big-endian,则读出结果为0x1234abcd;若字节序位little-endian,则读出结果为 0xcdab3412。如果我们将0x1234abcd 写入到以0x0000 开始的内存中,则Little endian 和Big endian 模式的存放结果如下:
地址               0x0000 0x0001 0x0002 0x0003
big-endian         0x12   0x34   0xab   0xcd
little-endian      0xcd   0xab 0x34   0x12
一般来说,x86 系列CPU 都是little-endian 的字节序,PowerPC 通常是Big endian,还有的CPU 能通过跳线来设置CPU 工作于Little endian 还是Big endian 模式。
解答:
显然,解答这个问题的方法只能是将一个字节(CHAR/BYTE 类型)的数据和一个整型数据存放于同样的内存
开始地址,通过读取整型数据,分析CHAR/BYTE 数据在整型数据的高位还是低位来判断CPU 工作于Little
endian 还是Big endian 模式。得出如下的答案:

  1. typedef unsigned char BYTE;
  2. int main(int argc, char* argv[])
  3. {
  4.    unsigned int num,*p;
  5.    p = #
  6.    num = 0;
  7.    *(BYTE *)p = 0xff;
  8.    if(num == 0xff)
  9.    {
  10.       printf("little\n");
  11.    }
  12.    else //num == 0xff000000
  13.    {
  14.       printf("big\n");
  15.    }
  16.    return 0;
  17. }

 

除了上述方法(通过指针类型强制转换并对整型数据首字节赋值,判断该赋值赋给了高位还是低位)外,还有没有更好的办法呢?我们知道,union 的成员本身就被存放在相同的内存空间(共享内存,正是union 发挥作用、做贡献的去处),因此,我们可以将一个CHAR/BYTE 数据和一个整型数据同时作为一个union 的成员,得出如下答案:

  1. int checkCPU()
  2. {
  3.    {
  4.    union w
  5.    {
  6.       int a;
  7.       char b;
  8.    } c;
  9.    c.a = 1;
  10.    return (c.b == 1);
  11.    }
  12. }

 

实现同样的功能,我们来看看Linux 操作系统中相关的源代码是怎么做的:
static union { char c[4]; unsigned long mylong; } endian_test = {{ ‘l‘, ‘?‘, ‘?‘, ‘b‘ } };

#define ENDIANNESS ((char)endian_test.mylong)
Linux 的内核作者们仅仅用一个union 变量和一个简单的宏定义就实现了一大段代码同样的功能!由以上一段代码我们可以深刻领会到Linux 源代码的精妙之处!(如果ENDIANNESS=’l’表示系统为little endian,
为’b’表示big endian )

 

 

 

 

 

如何判断CPU是大端还是小端模式

http://blog.sina.com.cn/s/blog_66a61f310100i7m0.html

MSB:Most Significant Bit  ------- 最高有效位
    LSB:Least Significant Bit ------- 最低有效位

  大端模式(big-edian)
  big-endian:MSB存放在最低端的地址上。

举例,双字节数0x1234以big-endian的方式存在起始地址0x00002000中:

        | data |<-- address
        | 0x12 |<-- 0x00002000
        | 0x34 |<-- 0x00002001

  在Big-Endian中,对于bit序列中的序号编排方式如下(以双字节数0x8B8A为例):

        ----+---------------------------------------------------+
        bit | 00 01 02 03 04 05 06 07 | 08 09 10 11 12 13 14 15 |
        ----+MSB---------------------------------------------LSB+
        val |  1  0  0  0  1  0  1  1 |  1  0  0  0  1  0  1  0 |
        ----+---------------------------------------------------+
        = 0x8B8A

  小端模式(little-endian)

  little-endian:LSB存放在最低端的地址上。

  举例,双字节数0x1234以little-endian的方式存在起始地址0x00002000中:

        | data |<-- address
        | 0x34 |<-- 0x00002000
        | 0x12 |<-- 0x00002001

  在Little-Endian中,对于bit序列中的序号编排和Big-Endian刚好相反,其方式如下(以双字节数0x8B8A为例):

        ----+---------------------------------------------------+
        bit | 15 14 13 12 11 10 09 08 | 07 06 05 04 03 02 01 00 |
        ----+MSB---------------------------------------------LSB+
        val |  1  0  0  0  1  0  1  1 |  1  0  0  0  1  0  1  0 |
        ----+---------------------------------------------------+
        = 0x8B8A

c) 网络字节序:TCP/IP各层协议将字节序定义为Big-Endian,因此TCP/IP协议中使用的字节序通常称之为网络字节序。

网络字节顺序是“所见即所得”的顺序。而Intel类型的CPU的字节顺序与此相反。

比如上面的 short B=0102H(十六进制,每两位表示一个字节的宽度)。所见到的是“0102”,按一般数学常识,数轴从左到右的方向增加,即内存地址从左到右增加的话,在内存中这个 short B的字节顺序是:

01 02

这就是网络字节顺序。所见到的顺序和在内存中的顺序是一致的!

 
    在ARM体系中,每个字单元包含4个字节单元或者两个半字单元。在字单元中,4个字节哪一个是高位字节,哪一个是低位字节则有两种不同的格式:big-endian和little-endian格式。在小端模式中,低位字节放在低地址,高位字节放在高地址;在大端模式中,低位字节放在高地址,高位字节放在低地址。

在C语言中,不同于结构体,共用体(联合体)中的几种不同类型的变量存放在同一段内存单元中。利用这一特点,可以用联合体变量判断ARM或x86环境下,存储系统是是大端还是小端模式。

#include "stdio.h"
int main()
{
  union w
 {
  int a;  //4 bytes
  char b; //1 byte
 } c;
  c.a=1;
  if (c.b==1)
  printf("It is Little_endian!\n");
  else
  printf("It is Big_endian!\n");
  return 1;
}
说明:
1  在c中,联合体(共用体)的数据成员都是从低地址开始存放。
2  若是小端模式,由低地址到高地址c.a存放为0x01 00 00 00,c.b被赋值为0x01;

  ————————————————————————————

   地址 0x00000000 0x00000001 0x00000002 0x00000003

   c.a  01         00         00         00

   c.b  01         00        

  ————————————————————————————  
3  若是大端模式,由低地址到高地址c.a存放为0x00 00 00 01,c.b被赋值为0x0;

  ————————————————————————————

   地址 0x00000000 0x00000001 0x00000002 0x00000003

   c.a  00         00         00         01

   c.b  00         00                 

  ————————————————————————————  


4  根据c.b的值的情况就可以判断cpu的模式了,现在XP环境下的intel CPU是小端模式,不信你可测试下!

 

的确判别一个 系统 是大头序还是小头序的方法是有的。 
比如: 
-BEGIN- 
int x=1; // 0x00000001 
if (*(char*)&x) { 
 
}else{ 
 

-END- 
内存数据(0x00000001)-〉大头/小头字符序-〉数值(1) 
这里判别的前提条件是知道内存数据及所对应的数值,所以可以判定出大小头字符序。   

 

 
 
有一小段C程序看大端、小端模式。
前几天在论坛上看到一篇求助贴(http://bbs.bccn.net/viewthread.php?tid=304088&page=1#pid1760319),发帖者的疑问是:下面程序的结果为何是266??代码如下:
union ss
{short int i;
char c[2];
};
void main()
{union ss x;
    x.c[0]=10;
    x.c[1]=1;
    pirntf("%d",x.i);
}
由于刚刚重新学习C语言不久,也和LZ一样困惑,就把帖子做个标记。待昨天再次去翻那篇帖子的时候,发现有人回复了,参考着别人的思路,自己写程序验证了一下,终于明白了答案的由来,先把验证的过程贴出来,也算是一个总结吧!
下面程序在VC6.0下得到的结果是266.
#include <stdio.h>

union ss
{
    short int i;
    char c[2];
};

int main(void)
{
    union ss x;
    x.c[0] = 10;
    x.c[1] = 1;

    printf ("%d\n", x.i);

    return 0;
}

由于定义的共用体ss中的变量i和c共同占有2个字节的地址,而编译器VC是小端模式,
故字符数组c和短整形i在内存中的分布情况是这样的:     地址1                   地址0
                            c[]:           0000 0001(c[1])           0000 1010(c[0])
                            i:            0000 0001                  0000 1010
因此变量i的值为:1*256^1 + 10*256^0 = 266!!
那我们是如和得出VC是小段模式的呢?什么有是小段模式呢?google之就可知道:所谓的大端模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中;所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为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处理器还可以由硬件来选择是大端模式还是小端模式。-----(摘自网络资料)
我们可用如下的程序验证自己的编译器是大端模式还是小段模式,验证代码如下:
#include <stdio.h>

int main(void)
{
    short int x;
    char x0, x1;
    
    x = 0x1122;
    x0 = ((char *)&x)[0];        //低地址单元
    x1 = ((char *)&x)[1];        //高地址单元

    printf ("%0x\n", x0);
    printf ("%0x\n", x1);
    return 0;
}
程序结果如下图:

由此我们可得出VC6.0为小段模式,这也验证了前面我们结果的正确性!
我们还可一对前面的程序做一个小小的改动也可以验证我们的猜想:改正后的代码如下:
#include <stdio.h>
union ss
{
    short int i;
    char c[2];
};

int main(void)
{
    union ss x;
    x.c[0] = 10;
    x.c[1] = 1;
    printf ("%0x\n", x.i);
    return 0;
}
程序输出的结果为:

由于我们定义i为短整形,在内存中为其分配了2个字节的空间所以结果10a前还省略了一个0,过完整的结果应该是:010a,高八位01对应着c[1],低八位0a对应着c[0]!
总结:通过上面这个小程序我们C初学者就可学到:共用体中的变量共同占有所定义的变量中最大的地址空间和如何验证自己的编译器是大端模式还是小段模式。
 
 
 
 
 
 

在不同的计算机体系结构中,对于数据(比特、字节、字等)的存储和传输机制有所不同;目前在各种体系结构的计算机中,主要采用的字节存储机制主要有两种:大端(Big-endian)和小端(Little-endian)。

字节序,又称端序,尾序,英文:Endianness。在计算机科学领域中,字节序是指存放多字节数据的字节(byte)的顺序,典型的情况是整数在内存中的存放方式和网络传输的传输顺序。Endianness有时候也可以用指位序(bit)。

“endian”一词来源于乔纳森·斯威夫特的小说格列佛游记。小说中,小人国为水煮蛋该从大的一端(Big-End)剥开还是小的一端(Little-End)剥开而争论,争论的双方分别被称为Big-endians和Little-endians。
1980年,Danny Cohen在其著名的论文”On Holy Wars and a Plea for Peace”中,为平息一场关于字节该以什么样的顺序传送的争论,而引用了该词。

首先有两个概念需要解释一下:
1.MSB(全大写)指the Most Significant Byte,指多字节序列中具有最大权重的字节。
2.LSB(全大写)指the Least Significant Byte,指多字节序列中具有最小权重的字节。
PS:小写的msb,lsb还有另外的意思,最高有效位(the Most Significant Bit,msb)和最低有效位(the least significant bit,lsb)。

大端(Big-endian):MSB放在低地址,LSB存放在高地址。(大端方式将高位存放在低地址)
小端(Little-endian):MSB存放在高地址,LSB存放在低地址。(小端方式将高位存放在高地址)

当然另外,还有一种混合序(mixed-endian or middle-endian),此处暂不解释了。

这个说的有点抽象,下面举个例子吧:
以一个数值为1029的整数(4个字节)为例子吧,其分别在大端和小端架构中的存储方式分别为下表所示:
Big-endian VS. Little-endian 
—————————————————————————-

Address  Big-endian  Little-endian  
0 byte  00000000  00000101  
1 byte  00000000  00000100  
2 byte  00000100  00000000  
3 byte  00000101  00000000  

—————————————————————————-

在主流的体系结构中,小端(Little-endian)的代表是我们最常用x86架构(包括x86_64),还有 6502 (including 65802, 65C816), Z80 (including Z180, eZ80 etc.), MCS-48, 8051, DEC Alpha, Altera Nios, Atmel AVR, SuperH, VAX, 和 PDP-11 等等;
大端(Big-endian)的架构有 Motorola 6800 and 68k, Xilinx Microblaze, IBM POWER, system/360, System/370 等等。
另外有一些架构的endian是可配置的,如下架构有配置endian为大端、小端中任一种的功能, ARM, PowerPC, Alpha, SPARC V9, MIPS, PA-RISC 和 IA-64 等等。

下面,这个简单的C程序,可以查询出当前的架构是大端还是小端。

View Code C
 
12345678910111213141516171819
 /* * To check CPU is Little endian or Big endian. *                 x86 is little-endian, many of other CPUs are big-endian.  *                 ARM is Bi-endian (configurable endian). * Author: Jay Ren * Date: 2012.01.04 */ #include <stdio.h> int main(int argc, char *argv[]){	int num = 1;	if(*(char*)&num == 1)		printf("Your CUP is Little Endian.\n");	else		printf("Your CUP is Big Endian.\n");}

参考:

http://en.wikipedia.org/wiki/Endianness

“Linux Kernel Development” book

 

 

 

 

 

 

 

 

 

 

一、大端模式和小端模式的起源

        关于大端小端名词的由来,有一个有趣的故事,来自于Jonathan Swift的《格利佛游记》:Lilliput和Blefuscu这两个强国在过去的36个月中一直在苦战。战争的原因:大家都知道,吃鸡蛋的时候,原始的方法是打破鸡蛋较大的一端,可以那时的皇帝的祖父由于小时侯吃鸡蛋,按这种方法把手指弄破了,因此他的父亲,就下令,命令所有的子民吃鸡蛋的时候,必须先打破鸡蛋较小的一端,违令者重罚。然后老百姓对此法令极为反感,期间发生了多次叛乱,其中一个皇帝因此送命,另一个丢了王位,产生叛乱的原因就是另一个国家Blefuscu的国王大臣煽动起来的,叛乱平息后,就逃到这个帝国避难。据估计,先后几次有11000余人情愿死也不肯去打破鸡蛋较小的端吃鸡蛋。这个其实讽刺当时英国和法国之间持续的冲突。Danny Cohen一位网络协议的开创者,第一次使用这两个术语指代字节顺序,后来就被大家广泛接受。
 

二、什么是大端和小端

        Big-Endian和Little-Endian的定义如下:
1) Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
2) Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
举一个例子,比如数字0x12 34 56 78在内存中的表示形式为:

1)大端模式:

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

2)小端模式:

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

可见,大端模式和字符串的存储模式类似。

3)下面是两个具体例子:

 

16bit宽的数0x1234在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:
 

 

内存地址小端模式存放内容大端模式存放内容
0x40000x340x12
0x40010x120x34

32bit宽的数0x12345678在Little-endian模式以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址小端模式存放内容大端模式存放内容
0x40000x780x12
0x40010x560x34
0x40020x340x56
0x40030x120x78
 

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

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

 

三、数组在大端小端情况下的存储:

  以unsigned int value = http://www.mamicode.com/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) -- 低位
        --------------
低地址

 

 

四、为什么会有大小端模式之分呢?

      这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为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处理器还可以由硬件来选择是大端模式还是小端模式。

 

五、如何判断机器的字节序

可以编写一个小的测试程序来判断机器的字节序:

[cpp]view plaincopyprint?
  1. BOOL IsBigEndian()  
  2. {  
  3.     int a = 0x1234;  
  4.     char b =  *(char *)&a;  //通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分   
  5.     if( b == 0x12)  
  6.     {  
  7.         return TRUE;  
  8.     }  
  9.     return FALSE;  
  10. }<span style="font-family: Arial, Verdana, sans-serif; white-space: normal; rgb(255, 255, 255); "> </span>  

BOOL IsBigEndian() { int a = 0x1234; char b = *(char *)&a; //通过将int强制类型转换成char单字节,通过判断起始存储位置。即等于 取b等于a的低地址部分 if( b == 0x12) { return TRUE; } return FALSE; }<span style="font-family: Arial, Verdana, sans-serif; white-space: normal; "> </span>联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性可以轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写:

[cpp]view plaincopyprint?
  1. BOOL IsBigEndian()  
  2. {  
  3.     union NUM  
  4.     {  
  5.         int a;  
  6.         char b;  
  7.     }num;  
  8.     num.a = 0x1234;  
  9.     if( num.b == 0x12 )  
  10.     {  
  11.         return TRUE;  
  12.     }  
  13.     return FALSE;  
  14. }<span style="font-family: Arial, Verdana, sans-serif; white-space: normal; rgb(255, 255, 255); "> </span>  

BOOL IsBigEndian() { union NUM { int a; char b; }num; num.a = 0x1234; if( num.b == 0x12 ) { return TRUE; } return FALSE; }<span style="font-family: Arial, Verdana, sans-serif; white-space: normal; "> </span>

六、常见的字节序

一般操作系统都是小端,而通讯协议是大端的。

4.1 常见CPU的字节序

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

4.2 常见文件的字节序

Adobe PS – Big Endian
BMP – Little Endian
DXF(AutoCAD) – Variable
GIF – Little Endian
JPEG – Big Endian
MacPaint – Big Endian
RTF – Little Endian
 
另外,Java和所有的网络通讯协议都是使用Big-Endian的编码。
 

七、如何进行转换

对于字数据(16位):

[cpp]view plaincopyprint?
  1. #define BigtoLittle16(A)   (( ((uint16)(A) & 0xff00) >> 8)    | \   
  2.                                        (( (uint16)(A) & 0x00ff) << 8))  

#define BigtoLittle16(A) (( ((uint16)(A) & 0xff00) >> 8) | \ (( (uint16)(A) & 0x00ff) << 8))

对于双字数据(32位):

 

[cpp]view plaincopyprint?
  1. #define BigtoLittle32(A)   ((( (uint32)(A) & 0xff000000) >> 24) | \   
  2.                                        (( (uint32)(A) & 0x00ff0000) >> 8)   | \  
  3.                                        (( (uint32)(A) & 0x0000ff00) << 8)   | \  
  4.                                        (( (uint32)(A) & 0x000000ff) << 24))  

#define BigtoLittle32(A) ((( (uint32)(A) & 0xff000000) >> 24) | \ (( (uint32)(A) & 0x00ff0000) >> 8) | \ (( (uint32)(A) & 0x0000ff00) << 8) | \ (( (uint32)(A) & 0x000000ff) << 24))

 

八、从软件的角度理解端模式

        从软件的角度上,不同端模式的处理器进行数据传递时必须要考虑端模式的不同。如进行网络数据传递时,必须要考虑端模式的转换。在Socket接口编程中,以下几个函数用于大小端字节序的转换。

[cpp]view plaincopyprint?
  1. #define ntohs(n)     //16位数据类型网络字节顺序到主机字节顺序的转换   
  2. #define htons(n)     //16位数据类型主机字节顺序到网络字节顺序的转换   
  3. #define ntohl(n)      //32位数据类型网络字节顺序到主机字节顺序的转换   
  4. #define htonl(n)      //32位数据类型主机字节顺序到网络字节顺序的转换  

#define ntohs(n) //16位数据类型网络字节顺序到主机字节顺序的转换 #define htons(n) //16位数据类型主机字节顺序到网络字节顺序的转换 #define ntohl(n) //32位数据类型网络字节顺序到主机字节顺序的转换 #define htonl(n) //32位数据类型主机字节顺序到网络字节顺序的转换
其中互联网使用的网络字节顺序采用大端模式进行编址,而主机字节顺序根据处理器的不同而不同,如PowerPC处理器使用大端模式,而Pentuim处理器使用小端模式。
       大端模式处理器的字节序到网络字节序不需要转换,此时ntohs(n)=n,ntohl = n;而小端模式处理器的字节序到网络字节必须要进行转换,此时ntohs(n) = __swab16(n),ntohl = __swab32(n)。__swab16与__swab32函数定义如下所示。

[cpp]view plaincopyprint?
  1. #define ___swab16(x)   
  2. {  
  3.             __u16 __x = (x);  
  4.             ((__u16)(  
  5.                         (((__u16)(__x) & (__u16)0x00ffU) << 8) |  
  6.                         (((__u16)(__x) & (__u16)0xff00U) >> 8) ));  
  7. }  
  8.   
  9.   
  10. #define ___swab32(x)   
  11. {  
  12.             __u32 __x = (x);  
  13.             ((__u32)(  
  14.                         (((__u32)(__x) & (__u32)0x000000ffUL) << 24) |  
  15.                         (((__u32)(__x) & (__u32)0x0000ff00UL) << 8) |  
  16.                         (((__u32)(__x) & (__u32)0x00ff0000UL) >> 8) |  
  17.                         (((__u32)(__x) & (__u32)0xff000000UL) >> 24) ));  
  18. }  

#define ___swab16(x){ __u16 __x = (x); ((__u16)( (((__u16)(__x) & (__u16)0x00ffU) << 8) | (((__u16)(__x) & (__u16)0xff00U) >> 8) ));} #define ___swab32(x){ __u32 __x = (x); ((__u32)( (((__u32)(__x) & (__u32)0x000000ffUL) << 24) | (((__u32)(__x) & (__u32)0x0000ff00UL) << 8) | (((__u32)(__x) & (__u32)0x00ff0000UL) >> 8) | (((__u32)(__x) & (__u32)0xff000000UL) >> 24) )); }
        PowerPC处理器提供了lwbrx,lhbrx,stwbrx,sthbrx四条指令用于处理字节序的转换以优化__swab16和__swap32这类函数。此外PowerPC处理器中的rlwimi指令也可以用来实现__swab16和__swap32这类函数。

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

 

九、从系统的角度理解端模式

先补充两个关键词,MSB和LSB:
  MSB:MoST Significant Bit ------- 最高有效位
        LSB:Least Significant Bit ------- 最低有效位
 

        处理器在硬件上由于端模式问题在设计中有所不同。从系统的角度上看,端模式问题对软件和硬件的设计带来了不同的影响,当一个处理器系统中大小端模式同时存在时,必须要对这些不同端模式的访问进行特殊的处理。
       PowerPC处理器主导网络市场,可以说绝大多数的通信设备都使用PowerPC处理器进行协议处理和其他控制信息的处理,这也可能也是在网络上的绝大多数协议都采用大端编址方式的原因。因此在有关网络协议的软件设计中,使用小端方式的处理器需要在软件中处理端模式的转变。而Pentium主导个人机市场,因此多数用于个人机的外设都采用小端模式,包括一些在网络设备中使用的PCI总线,Flash等设备,这也要求在硬件设计中注意端模式的转换。
       本文提到的小端外设是指这种外设中的寄存器以小端方式进行存储,如PCI设备的配置空间,NOR FLASH中的寄存器等等。对于有些设备,如DDR颗粒,没有以小端方式存储的寄存器,因此从逻辑上讲并不需要对端模式进行转换。在设计中,只需要将双方数据总线进行一一对应的互连,而不需要进行数据总线的转换。
       如果从实际应用的角度说,采用小端模式的处理器需要在软件中处理端模式的转换,因为采用小端模式的处理器在与小端外设互连时,不需要任何转换。而采用大端模式的处理器需要在硬件设计时处理端模式的转换。大端模式处理器需要在寄存器,指令集,数据总线及数据总线与小端外设的连接等等多个方面进行处理,以解决与小端外设连接时的端模式转换问题。在寄存器和数据总线的位序定义上,基于大小端模式的处理器有所不同。
       一个采用大端模式的32位处理器,如基于E500内核的MPC8541,将其寄存器的最高位msb(most significant bit)定义为0,最低位lsb(lease significant bit)定义为31;而小端模式的32位处理器,将其寄存器的最高位定义为31,低位地址定义为0。与此向对应,采用大端模式的32位处理器数据总线的最高位为0,最高位为31;采用小端模式的32位处理器的数据总线的最高位为31,最低位为0。         
       大小端模式处理器外部总线的位序也遵循着同样的规律,根据所采用的数据总线是32位,16位和8位,大小端处理器外部总线的位序有所不同。大端模式下32位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第31位,LSB是第24~31字段。小端模式下32位总线的msb是第31位,MSB是数据总线的第31~24位,lsb是第0位,LSB是7~0字段。大端模式下16位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第15位,LSB是第8~15字段。小端模式下16位总线的msb是第15位,MSB是数据总线的第15~7位,lsb是第0位,LSB是7~0字段。大端模式下8位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第7位,LSB是第0~7字段。小端模式下8位总线的msb是第7位,MSB是数据总线的第7~0位,lsb是第0位,LSB是7~0字段。
         由上分析,我们可以得知对于8位,16位和32位宽度的数据总线,采用大端模式时数据总线的msb和MSB的位置都不会发生变化,而采用小端模式时数据总线的lsb和LSB位置也不会发生变化。
         为此,大端模式的处理器对8位,16位和32位的内存访问(包括外设的访问)一般都包含第0~7字段,即MSB。小端模式的处理器对8位,16位和32位的内存访问都包含第7~0位,小端方式的第7~0字段,即LSB。由于大小端处理器的数据总线其8位,16位和32位宽度的数据总线的定义不同,因此需要分别进行讨论在系统级别上如何处理端模式转换。在一个大端处理器系统中,需要处理大端处理器对小端外设的访问。

 

十、实际中的例子

       虽然很多时候,字节序的工作已由编译器完成了,但是在一些小的细节上,仍然需要去仔细揣摩考虑,尤其是在以太网通讯、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]),之后再进行发送才可以得到正确的数据。

我如何确定一个机器的字节顺序是大端还是小端?