高位字节优先 低位字节优先
自第一台计算机诞生,其最小存储单元就被永久的定格了——一个由8个比特(bit)组成的称为字节(byte)的单位。计算机的所有内存以字节数组的方式进行编址。
当一个逻辑上长于一个字节的整形数据放置在内存中时(比如16位,32位,和64位的整数),计算机设计者需要考虑这些字节的存储顺序。一些体系结构的设计者选择了将字节的 逻辑顺序与 物理顺序一致,即将逻辑上较低的字节放置在物理上较低的字节上;另外一些设计者则选择了将字节的逻辑顺序与物理顺序相反,即将逻辑上较低的字节放置在物理上较高的字节上。前者被称为“little endian”,比如Intel x86系列;后者则被称为“big endian”,比如Motorola的PowerPC以及Sun Sparc。还有一些平台同时支持两种方案,由开发者决定使用哪一种。
两种选择为底层开发者带来了一定的困扰。比如, 两个字节顺序不一致的平台之间进行通信,或者在两个字节顺序不一致的平台之间 移植系统。这都是 跨平台的例子,对于这些情况,字节顺序的问题是不能回避的。对于仅仅在一种平台上进行开发的程序员而言,如果它能够避免强制类型转换(比如将字节数组强制转换为一个长整数),一贯的以逻辑顺序来操作大于一个字节的整数,应该可以回避这个问题。但由于C语言是一种非常灵活的语言,有时候通过强制类型转换可以让代码非常精简,甚至达到非常巧妙的效果,所以,要求C程序员完全回避这个问题,几乎是不现实的。
由于Little Endian提供了逻辑顺序与物理顺序的一致性,让编程者摆脱了不一致性所带来的困扰,C语言开发者可以无所顾忌的按照自己的意愿进行强制类型转换,所以现代体系结构几乎都支持Little Endian。但Big Endian也有其优点,尤其对于汇编程序员:他们对于任意长度的整数,总是可以通过判断Byte 0的bit-7来查看一个整数的正负;对于Little Endian则不得不首先知道当前整数的长度,然后查看最高byte的bit-7来判断其正负。对于这种情况,big endian的开发者可以写出非常高效的代码。
两派的支持者争论不休,正像他们所支持名词(big endian和little endian)的典故所讲述的那样:Little Endian和Big Endian这两个名词来源于Jonathan Swift的《格利佛游记》其中交战的两个派别无法就应该从哪一端--小端还是大端--打开一个半熟的鸡蛋达成一致。:)在那个时代,Swift是在讽刺英国和法国之间的持续冲突,Danny Cohen,一位网络协议的早期开创者,第一次使用这两个术语来指代字节顺序,后来这个术语被广泛接纳了(摘自《深入理解计算机系统》)。
需要特别指出的是,通常所提到的Little Endian和Big Endian仅仅指字节顺序。在硬件设计者的术语中,对于一个字节内部的bit顺序也分Little Endian和Big Endian,但对于程序员而言,这些bit顺序的不同是透明的,也就是说,程序员只需要按照逻辑顺序来看待和操作字节内部的bit即可。
Endian的不同不仅仅带来字节顺序的不同,还有更多的问题。如果C程序员在定义一个结构体时,使用了bitwise的域定义,比如:
struct foo {
int a:3;
int b:7;
int c:13;
int d:9;
};
这个结构体的一个对象会占用4个字节。由于a,b,c,d的类型都是int,所以他们都在以int 32为单位的整数上分配bit,另外,由于他们的bit数量正好等于int32的bit数,所以,它们都分配于一个int所占用的空间。关键问题在于这些字节在这4个字节内是分配顺序是怎么样的?
对于little endian,其分配顺序与逻辑顺序是一致的,即在byte[0]的bit[0~2]上分配a,在byte[0]的bit[3,7]以及byte[1]的bit[0,1]上分配b,依次类推。
对于big endian,其方案会带来很大的问题。其分配顺序为:
字节物理顺序:从低到高;
字节内bit顺序:从高到底;
也就是说,big endian在bitwise的分配方案上,从字节顺序到bit顺序都反过来了(因为其正向存储顺序为:字节从高到底,bit从低到高(从程序员的观点看))。换句话说:big endian的bit分配顺序为,按照bit的逻辑顺序,从高到底进行分配。
|--------|--------|--------|--------|
Logical Byte Order | byte 3 | byte 2 | byte 1 | byte 0 |
|--------|--------|--------|--------|
Bitwise allocation |-a-|---b---|------c------|----d----|
请注意,并不是硬件平台使用的这种方案,而是C语言编译器。这是一种荒谬的方案,我想可能是C语言编译器的早期开发者希望通过编译器屏蔽掉big endian和little endian在bitwise allocation上的差异,而都与物理存储顺序一致。但由于其采用了bit order的反向分配,反而加剧了这种差异,随后的编译器为了保持兼容,也只好将错误延续了下来。
基于这种原因,在C语言中直接使用bitwise的方式定义结构体是一种危险的方式,因为这些代码是平台依赖的。当进行跨平台移植的时候必须重新定义这些结构体。
有两种方式可以消除这种风险:
1、使用逻辑移位的方式来操作bit;以上面的例子为例,我们可以这么做:
struct foo {
int value;
};
#define SET_A(f,a) do { (f) |= ((a)&0x7); } while(0)
#define SET_B(f,b) do { (f) |= (((b)&0x7F)<<3); } while(0)
#define SET_C(f,c) do { (f) |= (((c)&0x1FFF)<<10); } while(0)
#define SET_D(f,d) do { (f) |= (((d)&0x1FF)<<23); } while(0)
#define GET_A(f) ((f)&0x7)
#define GET_B(f) (((f)>>3)&0x7F)
#define GET_C(f) (((f)>>10)&0x1FFF)
#define GET_D(f) (((f)>>23)&0x1FF)
2、对于big endian,我们可以使用相反的顺序来声明bitwise fields。仍然以上例为例:
#if LITTLE_ENDIAN
#define BITWISE(type,a,b,c,d) type a, b, c, d
#else
#define BITWISE(type,a,b,c,d) type d, c, b, a
#endif
struct foo {
BITWISE(int, a:3, b:7, c:13, d:9);
};
对于little endian,逻辑顺序与物理顺序一致,只需要按照原样定义;而对于big endian,由于其整体的bit顺序恰好与逻辑顺序是相反的,所以,我们将顺序反过来,使其bit的分配顺序与逻辑顺序一致即可。
当一个逻辑上长于一个字节的整形数据放置在内存中时(比如16位,32位,和64位的整数),计算机设计者需要考虑这些字节的存储顺序。一些体系结构的设计者选择了将字节的 逻辑顺序与 物理顺序一致,即将逻辑上较低的字节放置在物理上较低的字节上;另外一些设计者则选择了将字节的逻辑顺序与物理顺序相反,即将逻辑上较低的字节放置在物理上较高的字节上。前者被称为“little endian”,比如Intel x86系列;后者则被称为“big endian”,比如Motorola的PowerPC以及Sun Sparc。还有一些平台同时支持两种方案,由开发者决定使用哪一种。
两种选择为底层开发者带来了一定的困扰。比如, 两个字节顺序不一致的平台之间进行通信,或者在两个字节顺序不一致的平台之间 移植系统。这都是 跨平台的例子,对于这些情况,字节顺序的问题是不能回避的。对于仅仅在一种平台上进行开发的程序员而言,如果它能够避免强制类型转换(比如将字节数组强制转换为一个长整数),一贯的以逻辑顺序来操作大于一个字节的整数,应该可以回避这个问题。但由于C语言是一种非常灵活的语言,有时候通过强制类型转换可以让代码非常精简,甚至达到非常巧妙的效果,所以,要求C程序员完全回避这个问题,几乎是不现实的。
由于Little Endian提供了逻辑顺序与物理顺序的一致性,让编程者摆脱了不一致性所带来的困扰,C语言开发者可以无所顾忌的按照自己的意愿进行强制类型转换,所以现代体系结构几乎都支持Little Endian。但Big Endian也有其优点,尤其对于汇编程序员:他们对于任意长度的整数,总是可以通过判断Byte 0的bit-7来查看一个整数的正负;对于Little Endian则不得不首先知道当前整数的长度,然后查看最高byte的bit-7来判断其正负。对于这种情况,big endian的开发者可以写出非常高效的代码。
两派的支持者争论不休,正像他们所支持名词(big endian和little endian)的典故所讲述的那样:Little Endian和Big Endian这两个名词来源于Jonathan Swift的《格利佛游记》其中交战的两个派别无法就应该从哪一端--小端还是大端--打开一个半熟的鸡蛋达成一致。:)在那个时代,Swift是在讽刺英国和法国之间的持续冲突,Danny Cohen,一位网络协议的早期开创者,第一次使用这两个术语来指代字节顺序,后来这个术语被广泛接纳了(摘自《深入理解计算机系统》)。
需要特别指出的是,通常所提到的Little Endian和Big Endian仅仅指字节顺序。在硬件设计者的术语中,对于一个字节内部的bit顺序也分Little Endian和Big Endian,但对于程序员而言,这些bit顺序的不同是透明的,也就是说,程序员只需要按照逻辑顺序来看待和操作字节内部的bit即可。
Endian的不同不仅仅带来字节顺序的不同,还有更多的问题。如果C程序员在定义一个结构体时,使用了bitwise的域定义,比如:
struct foo {
int a:3;
int b:7;
int c:13;
int d:9;
};
这个结构体的一个对象会占用4个字节。由于a,b,c,d的类型都是int,所以他们都在以int 32为单位的整数上分配bit,另外,由于他们的bit数量正好等于int32的bit数,所以,它们都分配于一个int所占用的空间。关键问题在于这些字节在这4个字节内是分配顺序是怎么样的?
对于little endian,其分配顺序与逻辑顺序是一致的,即在byte[0]的bit[0~2]上分配a,在byte[0]的bit[3,7]以及byte[1]的bit[0,1]上分配b,依次类推。
对于big endian,其方案会带来很大的问题。其分配顺序为:
字节物理顺序:从低到高;
字节内bit顺序:从高到底;
也就是说,big endian在bitwise的分配方案上,从字节顺序到bit顺序都反过来了(因为其正向存储顺序为:字节从高到底,bit从低到高(从程序员的观点看))。换句话说:big endian的bit分配顺序为,按照bit的逻辑顺序,从高到底进行分配。
|--------|--------|--------|--------|
Logical Byte Order | byte 3 | byte 2 | byte 1 | byte 0 |
|--------|--------|--------|--------|
Bitwise allocation |-a-|---b---|------c------|----d----|
请注意,并不是硬件平台使用的这种方案,而是C语言编译器。这是一种荒谬的方案,我想可能是C语言编译器的早期开发者希望通过编译器屏蔽掉big endian和little endian在bitwise allocation上的差异,而都与物理存储顺序一致。但由于其采用了bit order的反向分配,反而加剧了这种差异,随后的编译器为了保持兼容,也只好将错误延续了下来。
基于这种原因,在C语言中直接使用bitwise的方式定义结构体是一种危险的方式,因为这些代码是平台依赖的。当进行跨平台移植的时候必须重新定义这些结构体。
有两种方式可以消除这种风险:
1、使用逻辑移位的方式来操作bit;以上面的例子为例,我们可以这么做:
struct foo {
int value;
};
#define SET_A(f,a) do { (f) |= ((a)&0x7); } while(0)
#define SET_B(f,b) do { (f) |= (((b)&0x7F)<<3); } while(0)
#define SET_C(f,c) do { (f) |= (((c)&0x1FFF)<<10); } while(0)
#define SET_D(f,d) do { (f) |= (((d)&0x1FF)<<23); } while(0)
#define GET_A(f) ((f)&0x7)
#define GET_B(f) (((f)>>3)&0x7F)
#define GET_C(f) (((f)>>10)&0x1FFF)
#define GET_D(f) (((f)>>23)&0x1FF)
2、对于big endian,我们可以使用相反的顺序来声明bitwise fields。仍然以上例为例:
#if LITTLE_ENDIAN
#define BITWISE(type,a,b,c,d) type a, b, c, d
#else
#define BITWISE(type,a,b,c,d) type d, c, b, a
#endif
struct foo {
BITWISE(int, a:3, b:7, c:13, d:9);
};
对于little endian,逻辑顺序与物理顺序一致,只需要按照原样定义;而对于big endian,由于其整体的bit顺序恰好与逻辑顺序是相反的,所以,我们将顺序反过来,使其bit的分配顺序与逻辑顺序一致即可。
不同的CPU有不同的字节序类型 这些字节序是指整数在内存中保存的顺序 这个叫做主机序
最常见的有两种
1. Little endian:将低序字节存储在起始地址 (即小的字节先存放)
2. Big endian:将高序字节存储在起始地址 (即大的字节先存放)
LE little-endian
最符合人的思维的字节序
地址低位存储值的低位
地址高位存储值的高位
怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说
低位值小,就应该放在内存地址小的地方,也即内存地址低位
反之,高位值就应该放在内存地址大的地方,也即内存地址高位
BE big-endian
最直观的字节序
地址低位存储值的高位
地址高位存储值的低位
为什么说直观,不要考虑对应关系
只需要把内存地址从左到右按照由低到高的顺序写出
把值按照通常的高位到低位的顺序写出
两者对照,一个字节一个字节的填充进去
例子:在内存中双字0x01020304(DWORD)的存储方式
内存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04
例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x23 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
x86系列CPU都是little-endian的字节序.
网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。
为了进行转换 bsd socket提供了转换的函数 有下面四个
htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序
在使用little endian的系统中 这些函数会把字节序进行转换
在使用big endian类型的系统中 这些函数会定义成空宏
同样 在网络程序开发时 或是跨平台开发时 也应该注意保证只用一种字节序 不然两方的解释不一样就会产生bug.
注:
1、网络与主机字节转换函数:htons ntohs htonl ntohl (s 就是short l是long h是host n是network)
2、不同的CPU上运行不同的操作系统,字节序也是不同的,参见下表。
处理器 操作系统 字节排序
Alpha 全部 Little endian
HP-PA NT Little endian
HP-PA UNIX Big endian
Intelx86 全部 Little endian <-----x86系统是小端字节序系统
Motorola680x() 全部 Big endian
MIPS NT Little endian
MIPS UNIX Big endian
PowerPC NT Little endian
PowerPC 非NT Big endian <-----PPC系统是大端字节序系统
RS/6000 UNIX Big endian
SPARC UNIX Big endian
IXP1200 ARM核心 全部 Little endian
最常见的有两种
1. Little endian:将低序字节存储在起始地址 (即小的字节先存放)
2. Big endian:将高序字节存储在起始地址 (即大的字节先存放)
LE little-endian
最符合人的思维的字节序
地址低位存储值的低位
地址高位存储值的高位
怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说
低位值小,就应该放在内存地址小的地方,也即内存地址低位
反之,高位值就应该放在内存地址大的地方,也即内存地址高位
BE big-endian
最直观的字节序
地址低位存储值的高位
地址高位存储值的低位
为什么说直观,不要考虑对应关系
只需要把内存地址从左到右按照由低到高的顺序写出
把值按照通常的高位到低位的顺序写出
两者对照,一个字节一个字节的填充进去
例子:在内存中双字0x01020304(DWORD)的存储方式
内存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04
例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x23 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
x86系列CPU都是little-endian的字节序.
网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释。网络字节顺序采用big endian排序方式。
为了进行转换 bsd socket提供了转换的函数 有下面四个
htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序
在使用little endian的系统中 这些函数会把字节序进行转换
在使用big endian类型的系统中 这些函数会定义成空宏
同样 在网络程序开发时 或是跨平台开发时 也应该注意保证只用一种字节序 不然两方的解释不一样就会产生bug.
注:
1、网络与主机字节转换函数:htons ntohs htonl ntohl (s 就是short l是long h是host n是network)
2、不同的CPU上运行不同的操作系统,字节序也是不同的,参见下表。
处理器 操作系统 字节排序
Alpha 全部 Little endian
HP-PA NT Little endian
HP-PA UNIX Big endian
Intelx86 全部 Little endian <-----x86系统是小端字节序系统
Motorola680x() 全部 Big endian
MIPS NT Little endian
MIPS UNIX Big endian
PowerPC NT Little endian
PowerPC 非NT Big endian <-----PPC系统是大端字节序系统
RS/6000 UNIX Big endian
SPARC UNIX Big endian
IXP1200 ARM核心 全部 Little endian