所谓的小端模式,是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的也是以字节为单位来寻址的,每个地址单元都对应着一个字节,一个字节为 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处理器还可以由硬件来选择是大端模式还是小端模式。
大小端的问题,主要是由于字长大于8bit的处理器,在处理一个字的时候,将其拆分成多个字节的表示方法。对于大端处理器,高位在低地址,低位在高地址。如0x12345678,在内存中这样表示:
地址78797a 7b
数据 12 34 56 78
对于这个32位的整数在内存中的存放位置,在X86中由于是32位的处理器所以在内存中寻址的地址也为32位,每个32位的地址寻址一个字节,假设存放这32位整数的起始地址为0x0012ff78,则在x86这样的小端模式中存放顺序依次为78,56,34,12,为什么不是直接反序(87,65,43,21),我们知道在内存中都是以字节为单位来寻址的,所以存放数据的最小单位也就是字节。一般,ARM/MIPS/PPC都是大端处理器。
而对于小端处理器,低位在低地址,高位在高地址。0x12345678这样排列:
地址 00 01 02 03
数据 78 56 34 12
x86属于小端。
这两种排列方法没有好坏之分,但是,对于不同体系结构的处理器之间通讯的时候,有可能产生问题。如MIPS机器发送0x12345678,到了x86接收的时候,就成了0x78563412。
试想,如果通讯中发生这种错误,多可怕啊——
“呼叫021,呼叫021”
那边,120收到了:“收到呼叫,收到呼叫”
“炮轰3309地区!”
“明白,炮火覆盖9033地区”~
于是,无数我方战士被自己人的炮火……
因此,人们制定了网络字节序——和大端字节序一致,高字节先发,低字节后发。这样,对于x86和MIPS/ARM/PPC处理器,对数据的处理流程就不一样了。x86需要将收到的数据按字节序颠倒以后处理,而MIPS/ARM/PPC可以直接处理。编写程序的工程师们为了让代码可移植,定义了以下的宏:
htonl /× 主机序转网络序,long int类型 ×/
ntohl /× 网络序转主机序,long int类型 ×/
htons /× 主机序转网络序,short int类型 ×/
ntohs /× 网络序转主机序,short int类型 ×/
对于小端处理器,这个宏返回颠倒字节序后的值,而对于大端处理器什么都不做。
通过一个联合体的例子来在各个不同平台下来做做实验:
有这样一个联合体数据结构:
typedef unsigned long uint32;
typedef unsigned short uint16;
typedef unsigned char uint8;
typedef union foo_u_ {
uint32 member1;
uint16 member2[2];
uint8 member3[4];
} foo_u;
那么,当这个联合体中,member1的值为0x12345678的时候,member2[0]和member3[0]各为多少呢?
因为不同体系结构的处理器,不同的编译器对各种类型所占的字节数也有不同,现假设上面的unsigned long占4个字节也就是32位,unsigned short占2个字节也就是16位,unsigned char占1个字节也就是8位。
我们知道联合体变量的存储方式,它会按照联合体中占字节数最大的那个类型来开辟内存,不是像结构体中为每个成员变量都分配一定的内存空间。在联合体中其他的成员变量都会重复的存放在大类型成员变量的内存上。所以一个成员变量值的改变会影响到其他成员变量。
上面的联合体变量中的成员变量,所占内存最大的就是占32位的member1(其实另外两个成员变量也是32位)所以这里就会给这个联合体变量分配4个字节也就是32位的存储空间,假如首地址为0x0012ff78,则按照小端模式,在内存中排放的顺序为:
地址:78 79 7a 7b
数据:785634 12
而对于第二个成员变量数组,含有两个数组元素,每个数组元素占16位,而且地址也是从第一个成员变量起始地址开始,所以member2[0]就是0x5678,而member2[1]也就是0x1234,而对于第三个成员变量数组含有4个数组元素,member3[0]就是0x78,代表的字符为x
member3[1]也就是0x56,代表字符为V,member3[2]也就是0x34,代表字符为4,member3[3]是0x12代表着一个小黑块。
而对于大端模式,联合体中第二,第三个成员变量的值就会变化。