所谓的大端模式,就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
所谓的小端模式,就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
简单来说:大端——高尾端,小端——低尾端
举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:
1)大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
可见,大端模式和字符串的存储模式类似。
3)下面是两个具体例子:
16bit宽的数0x1234在Little-endian模式(以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 | 小端模式存放内容 | 大端模式存放内容 |
---|---|---|
0x4000 | 0x34 | 0x12 |
0x4001 | 0x12 | 0x34 |
32bit宽的数0x12345678在Little-endian模式以及Big-endian模式)CPU内存中的存放方式(假设从地址0x4000开始存放)为:
内存地址 | 小端模式存放内容 | 大端模式存放内容 |
---|---|---|
0x4000 | 0x78 | 0x12 |
0x4001 | 0x56 | 0x34 |
0x4002 | 0x34 | 0x56 |
0x4003 | 0x12 | 0x78 |
4)大端小端没有谁优谁劣,各自优势便是对方劣势:
小端模式 :强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样。 大端模式 :符号位的判定固定为第一个字节,容易判断正负。
以unsigned int value = 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) -- 低位 -------------- 低地址
这是因为在计算机中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8 bit。但是在C 语言中除了 8 bit 的char之外,还有 16 bit 的 short型,32bit的long型(要看具体的编译器),另外,对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小端存储模式。例如一个16bit的short型 x ,在内存中的地址为 0x0010,x 的值为0x1122,那么0x11位高字节,0x22位低字节。对于大端模式,就将0x11放在低地址中,即0x0010中,0x22放在高地址中,即0x0011中。小端模式,刚好相反。我们常用的X86结构是小端模式,而KEIL C51则为大端模式。很多的ARM,DSP都为小端模式。有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
一般都是通过 union 来测试的,下面这段代码可以用来测试一下你的编译器是大端模式还是小端模式:
#include <stdio.h>int main (void){ union { short i; char a[2]; }u; u.a[0] = 0x11; u.a[1] = 0x22; printf ("0x%x\n", u.i); //0x2211 为小端 0x1122 为大端 return 0;}输出结果:0x2211
union 型数据所占的空间等于其最大的成员所占的空间。对 union 型的成员的存取都是相对于该联合体基地址的偏移量为 0 处开始,也就是联合体的访问不论对哪个变量的存取都是从 union 的首地址位置开始。
联合是一个在同一个存储空间里存储不同类型数据的数据类型。这些存储区的地址都是一样的,联合里不同存储区的内存是重叠的,修改了任何一个其他的会受影响。
当然你也可以这样
#include <stdio.h>int main (void){ short i = 0x1122; char *a = (char*)(&i); printf ("0x%x\n", *(a + 0)); //大端为 0x11 小端为 0x22 printf ("0x%x\n", *(a + 1)); return 0;}输出结果:0x220x11
说明:上面两个例子,可以通过 if 语句来判断大小端,这里只是介绍方法。
一般操作系统都是小端,而通讯协议是大端的。
1)常见CPU的字节序
Big Endian : PowerPC、IBM、Sun Little Endian : x86、DEC ARM既可以工作在大端模式,也可以工作在小端模式。
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的编码。
第一种方法:位操作
#include<stdio.h>
typedef unsigned int uint_32 ;typedef unsigned short uint_16 ;
//16位#define BSWAP_16(x) \(uint_16)((((uint_16)(x) & 0x00ff) << 8) | \(((uint_16)(x) & 0xff00) >> 8) \)
//32位#define BSWAP_32(x) \(uint_32)((((uint_32)(x) & 0xff000000) >> 24) | \(((uint_32)(x) & 0x00ff0000) >> 8) | \(((uint_32)(x) & 0x0000ff00) << 8) | \(((uint_32)(x) & 0x000000ff) << 24) \)
//无符号整型16位uint_16 bswap_16(uint_16 x){return (((uint_16)(x) & 0x00ff) << 8) | \(((uint_16)(x) & 0xff00) >> 8) ;}
//无符号整型32位uint_32 bswap_32(uint_32 x){return (((uint_32)(x) & 0xff000000) >> 24) | \(((uint_32)(x) & 0x00ff0000) >> 8) | \(((uint_32)(x) & 0x0000ff00) << 8) | \(((uint_32)(x) & 0x000000ff) << 24) ;}
int main(int argc,char *argv[]){printf("------------带参宏-------------\n");printf("%#x\n",BSWAP_16(0x1234)) ;printf("%#x\n",BSWAP_32(0x12345678));printf("------------函数调用-----------\n");printf("%#x\n",bswap_16(0x1234)) ;printf("%#x\n",bswap_32(0x12345678));
return 0 ;}输出结果:------------带参宏-------------0x34120x78563412------------函数调用-----------0x34120x78563412
这里有个思考?上面的哪个是转换为大端,哪个是转为小端了呢?
举个例子,比如数字 0x12 34 56 78在内存中的表示形式为:
1)大端模式:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
2)小端模式:
低地址 ------------------> 高地址
0x78 | 0x56 | 0x34 | 0x12
则:
转换为大端:
pPack[2] = (u8)((len >> 8) & 0xFF); pPack[3] = (u8)(len & 0xFF);
转为为小端:
pPack[2] = (u8)(len & 0xFF);
pPack[3] = (u8)((len >> 8) & 0xFF);
第二种方法:
从软件的角度理解端模式
使用 htonl, htons, ntohl, ntohs 等函数 这个可以参考我的网络编程部分的知识第一节 深入浅出TCPIP之理解TCP报文格式和交互流程
htonl() //32位无符号整型的主机字节顺序到网络字节顺序的转换(小端->>大端) htons() //16位无符号短整型的主机字节顺序到网络字节顺序的转换 (小端->>大端) ntohl() //32位无符号整型的网络字节顺序到主机字节顺序的转换 (大端->>小端) ntohs() //16位无符号短整型的网络字节顺序到主机字节顺序的转换 (大端->>小端)
注,主机字节顺序,X86一般多为小端(little-endian),网络字节顺序,即大端(big-endian);
举两个小例子:
//示例一#include <stdio.h>#icnlude <arpa/inet.h>int main (void){ union { short i; char a[2]; }u; u.a[0] = 0x11; u.a[1] = 0x22; printf ("0x%x\n", u.i); //0x2211 为小端 0x1122 为大端 printf ("0x%.x\n", htons (u.i)); //大小端转换 return 0;}输出结果:0x22110x1122
//示例二#include <stdio.h>#include <arpa/inet.h>struct ST{short val1;short val2;};union U{int val;struct ST st;};
int main(void){int a = 0;union U u1, u2;
a = 0x12345678;u1.val = a;printf("u1.val is 0x%x\n", u1.val);printf("val1 is 0x%x\n", u1.st.val1);printf("val2 is 0x%x\n", u1.st.val2);printf("after first convert is: 0x%x\n", htonl(u1.val));u2.st.val2 = htons(u1.st.val1);u2.st.val1 = htons(u1.st.val2);printf("after second convert is: 0x%x\n", u2.val);return 0;}输出结果:u1.val is 0x12345678val1 is 0x5678val2 is 0x1234after first convert is: 0x78563412after second convert is: 0x78563412
在对普通文件进行处理也需要考虑端模式问题。在大端模式的处理器下对文件的32,16位读写操作所得到的结果与小端模式的处理器不同。单纯从软件的角度理解上远远不能真正理解大小端模式的区别。事实上,真正的理解大小端模式的区别,必须要从系统的角度,从指令集,寄存器和数据总线上深入理解,大小端模式的区别。