一、字节序
来自:CPU派系。那就是PowerPC系列Intel的CPU。big endian方式存储数据,而little endian方式存储数据。那么究竟什么是little endian呢?big endian是指低地址存放最高有效字节(little endian则是低地址存放最低有效字节(
用文字说明可能比较抽象,下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:
Big Endian
内存物理的低地址 内存物理的高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 12 | 34 | 56 | 78 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian
内存物理的低地址 内存物理的高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 78 | 56 | 34 | 12 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
从上面两图可以看出,采用big endian方式存储数据是符合我们人类的思维习惯的。而little endian,!@#$%^&*,见鬼去吧 -_-|||
为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但是,如果你的程序要跟别人的程序产生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而JAVA编写的程序则唯一采用big endian方式来存储数据。试想,如果你用C/C++语言在x86平台下编写的程序跟别人的JAVA程序互通时会产生什么结果?就拿上面的0x12345678来说,你的程序传递给别人的一个数据,将指向0x12345678的指针传给了JAVA程序,由于JAVA采取big endian方式存储数据,很自然的它会将你的数据翻译为0x78563412。什么?竟然变成另外一个数字了?是的,就是这种后果。因此,在你的C程序传给JAVA程序之前有必要进行字节序的转换工作。
无独有偶,所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。ANSI C中提供了下面四个转换字节序的宏。
big endian:最高字节在地址最低位,最低字节在地址最高位,依次排列。
little endian:最低字节在最低位,最高字节在最高位,反序排列。
endian指的是当物理上的最小单元比逻辑上的最小单元小时,逻辑到物理的单元排布关系。咱们接触到的物理单元最小都是byte,在通信领域中,这里往往是bit,不过原理也是类似的。
一个例子:
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x34 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
目前应该little endian是主流,因为在数据类型转换的时候(尤其是指针转换)不用考虑地址问题。
二、Big Endian和 Little Endian名词的由来
这两个术语来自于 Jonathan Swift 的《《格利佛游记》其中交战的两个派别无法就应该从哪一端--小端还是大端--打开一个半熟的鸡蛋达成一致。:)
“endian”这个词出自《格列佛游记》。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开,由此曾发生过六次叛乱,其中一个皇帝送了命,另一个丢了王位。
我们一般将endian翻译成“字节序”,将big endian和little endian称作“大尾”和“小尾”。
在那个时代,Swift是在讽刺英国和法国之间的持续冲突,Danny Cohen,一位网络协议的早期开创者,第一次使用这两个术语来指代字节顺序,后来这个术语被广泛接纳了
三、Big Endian和 Little Endian优劣
来自:Dr. William T. Verts, April 19, 1996
Big Endian
判别一个数的正负很容易,只要取offset0处的一个字节就能确认。
Little Endian
长度为1,2,4字节的数,排列方式都是一样的,数据类型转换非常方便。
大端优先模式以嵌入式CPU居多,如Motorola的PowerPC系列.而小端优先以PC的CPU居多,如Intel X86系统.而且ARM可以配置成任一种模式.
数字字节序应用
数字字节序的是关于数字如何存储的格式,而不关运算的事情.
比如数字左移操作,比如开发者都可以认为是从数字低位向高位移动,至于移动后,数字存储格式如何调整,CPU会处理其中细节.因此如果在一个CPU内部处理数字.开发者无需关心数字字节序问题
但如果牵涉到在多个CPU之间传输数字问题.如通过网络,文件和总线传输数字的话.必须要考虑到数字字节序问题.
数字字节序问题存在于2个Byte或以上的字节来表示的数字中,因此char类型不存在数字字节序问题.一般只考虑short和int,long型,在64bit CPU下还要考虑64bit长整型的数字字节序.更长类型已经超出过一般C语言的标准了.一般无论考虑了.
网络字节序,本地址字节序
IP协议是定义在可以在任何操作系统或CPU传输数据的协议,在ip传输一个数字,必须统一规定的字节序,否则在传输时会发混乱.这个统一数字字节序叫网络序.TCP/IP规定网络序采用大端字节序, 相对的, CPU本身数字表示顺序称为本机序.
IP包在发送数字之前,比如端口号之类,必须把这些数字本机序转换为网络序.在接收后,也需要把网络序数字转为本地序,这样才能让接收的CPU正常使用数字.
在各个操作系统都会要实现四个转换函数.
unit16_t htons(uint16_t host);
unit32_t htonl(uint32_t host);
unit16_t ntohs(uint16_t net);
unit32_t ntohl(uint32_t net);
ntohl(将32位网络字符顺序转换成主机字符顺序)
相关函数
htonl,htons,ntohs
表头文件
#include<netinet/in.h>
定义函数
unsigned long int ntohl(unsigned long int netlong);
函数说明
ntohl()用来将参数指定的32位netlong转换成主机字符顺序。
返回值
返回对应的主机字符顺序。
ntohs(将16位网络字符顺序转换成主机字符顺序)
相关函数
htonl,htons,ntohl
表头文件
#include<netinet/in.h>
定义函数
unsigned short int ntohs(unsigned short int netshort);
函数说明
ntohs()用来将参数指定的16位netshort转换成主机字符顺序。
返回值
返回对应的主机顺序。
htonl(将32位主机字符顺序转换成网络字符顺序)
相关函数
htons,ntohl,ntohs
表头文件
#include<netinet/in.h>
定义函数
unsigned long int htonl(unsigned long int hostlong);
函数说明
htonl()用来将参数指定的32位hostlong 转换成网络字符顺序。
返回值
返回对应的网络字符顺序。
htons(将16位主机字符顺序转换成网络字符顺序)
相关函数
htonl,ntohl,ntohs
表头文件
#include<netinet/in.h>
定义函数
unsigned short int htons(unsigned short int hostshort);
函数说明
htons()用来将参数指定的16位hostshort转换成网络字符顺序。
返回值
返回对应的网络字符顺序。
使用这四个函数要注意.它们处理的对象实际上是对无符号的数字作相庆的位置换.因此不会理会是否有符号的情况,
大端优先的CPU的无需作转换,而小端优先的CPU必须作转换,一般用位直接操作.
转换代码一般如下:
#define ntohs (x) {
__u16 __x = (x);
((__u16)( (((__u16)(__x) & (__u16)0x00ffU) << 8) |
(((__u16)(__x) & (__u16)0xff00U) >> 8) ));
}
#define ntohl(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) ));
}
网络字节序转换的使用:
struct sockaddr_in my_addr;
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);
my_addr.sin_addr.s_addr = INADDR_ANY;
数字表达与数字节序的区别联系
一般引入数字字节序后,很多人会形成一阵思维混乱,比如在小端字节序的CPU下,逻辑左移位操作。是按原始存储来移位,还是按数字本身的移位?这里要明确一点,所有数字运算和位操作(移位,或,与,异或等)都是按数字本身逻辑的排序来操作,如逻辑左移是数字低位向高位移动。与数字存储时的字节序没有关系。否则标准C程序在不同字节序CPU不是会有两种结果?但是数字位操作结果最终存储到内存中,还是由汇编语句移位操作(即CPU本身自动转换)成当前CPU字节序存放。
五、比特序
来自:<FONT face=""">http://ayazh.gjjblog.com/archives/1058846/
我在8月9号的《Big Endian和Little Endian》一文中谈了字节序的问题。可是有朋友仍然会问,CPU存储一个字节的数据时其字节内的8个比特之间的顺序是否也有big endian和little endian之分?或者说是否有比特序的不同?
实际上,这个比特序是同样存在的。下面以数字0xB4(10110100)用图加以说明。
Big Endian
msb lsb
---------------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Little Endian
lsb msb
---------------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
实际上,由于CPU存储数据操作的最小单位是一个字节,其内部的比特序是什么样对我们的程序来说是一个黑盒子。也就是说,你给我一个指向0xB4这个数的指针,对于big endian方式的CPU来说,它是从左往右依次读取这个数的8个比特;而对于little endian方式的CPU来说,则正好相反,是从右往左依次读取这个数的8个比特。而我们的程序通过这个指针访问后得到的数就是0xB4,字节内部的比特序对于程序来说是不可见的,其实这点对于单机上的字节序来说也是一样的。
那可能有人又会问,如果是网络传输呢?会不会出问题?是不是也要通过什么函数转换一下比特序?嗯,这个问题提得很好。假设little endian方式的CPU要传给big endian方式CPU一个字节的话,其本身在传输之前会在本地就读出这个8比特的数,然后再按照网络字节序的顺序来传输这8个比特,这样的话到了接收端不会出现任何问题。而假如要传输一个32比特的数的话,由于这个数在littel endian方存储时占了4个字节,而网络传输是以字节为单位进行的,little endian方的CPU读出第一个字节后发送,实际上这个字节是原数的LSB,到了接收方反倒成了MSB从而发生混乱。
总结:
BIG-ENDIAN、LITTLE-ENDIAN跟多字节类型的数据有关的比如int,short,long型,而对单字节数据byte却没有影响。
数字字节序只是数据在内存中存放的顺序,我们看到的数字是没有字节序的问题的,看到的是多少就是多少。
无论是哪种字节序,对于单个字节byte里的数据的bit序,我们是不需要关心的,CPU自己会处理;而对于多个字节的数据类型,内存中存储的变量值的每个字节之间则是根据字节序来决定如何存放。