最近看了一些关于大小端的帖子,顺便搜集和归纳了一下这方面的资料。
一、关于edian的由来:
端模式(Endian)的这个词出自《格列佛游记》。这本书根据将鸡蛋敲开的方法不同将所有的人分为两类,从大头头开始将鸡蛋敲
开的人被归为Big Endian,从小头开始将鸡蛋敲开的人被归为Littile Endian。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。
在计算机中,Endian表示多字节数中各个字节在存储器中存放的顺序。也就是我们的字节存储的两种机制。
二、关于大小端模式
在了解大小端之前先了解一下数据的高低位:
如果我们有一个32位无符号整型0x12345678,那么高位是什么,低位又是什么呢?。在十进制中我们都说靠左边的是高位,靠右边的是低位,在其他进制也是如此。就拿0x12345678来说,从高位到低位的字节依次是0x12、0x34、0x56和0x78。有些书或
者文章中称低位字节为最低有效位LSB,高位字节为最高有效位MSB。
MSB:Most Significant Bit ------- 最高有效位
LSB:Least Significant Bit ------- 最低有效位
所谓的大端模式(Big-endian),是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中,这样的存储模式
有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;
例如,对于一个32为的int整数变量据OX12345678 。该变量在内存中存储的地址从OX2000开始,其在内存中的存储如下:
地址 数据
0X2000 OX12
OX2001 OX34
0X2002 0X56
0X2003 0X78
所谓的小端模式(Little-endian),是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致。
例如,对于一个32为的int整数变量据OX12345678 。该变量在内存中存储的地址从OX2000开始,其在内存中的存储如下:
地址 数据
0X2000 OX78
OX2001 OX56
0X2002 0X34
0X2003 0X12
为什么会有大小端模式之分呢?这是因为在计算机系统中,我们是以字节为单位的,每个地址单元都对应着一个字节,一个字节为 8bit。但是在C语言中除了8bit的char之外,还有16bit的short型,32bit的long型(要看具体的编译器),另外,对于位数大于 8位的处理器,例如16位或者32位的处理器,由于寄存器宽度大于一个字节,那么必然存在着一个如果将多个字节安排的问题。因此就导致了大端存储模式和小 端存储模式。
特别是在跨系统或者跨平台中实现字节的操作就需要弄明白两个系统或者平台所用的字节存贮的机制了。
小端模式和大端模式的优缺点
小端模式 :强制转换数据不需要调整字节内容。
大端模式 :符号位的判定固定为第一个字节。
那么在跨平台或网络程序中如何实现字节序的转换呢?这个通过C语言的移位操作很容易实现,例如下面的宏:
这个通过C语言的移位操作很容易实现
Big-Endian转换成Little-Endian如下:
#define BigtoLittle16(A) ((((uint16)(A) & 0xff00) >> 8) | /
(((uint16)(A) & 0x00ff) << 8))
#define BigtoLittle32(A) ((((uint32)(A) & 0xff000000) >> 24) | /
(((uint32)(A) & 0x00ff0000) >> 8) | /
(((uint32)(A) & 0x0000ff00) << 8) | /
(((uint32)(A) & 0x000000ff) << 24))
三、用代码进行检测
1:
#include<iostream.h>
int check_edian(void)
{
int i = 0x1234;
char * p = (char*)&i;
if( p[0] == 0x34 && p[1] == 0x12 )
return 1;//系统是Little-Endian
return 0;//系统是Big-Endian
}
void main()
{
cout<<check_edian()<<endl;
}
如果小端方式(i占至少两个字节的长度)则i所分配的内存最小地址那个字节中就存着1,其他字节是0.大端的话则1在i的最高地址字节处存放,char是一个字节,所以强制将char型量p指向i则p指向的一定是i的最低地址,那么就可以判断p中的值是不是1来确定是
不是小端
2:
由于联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性就可以轻松地获得了CPU对内存采用Little-endian还是
Big-endian模式读写。
利用了union的空间分配原则。union空间必须足够大,以保存里面类型中的最大的一种,这些类型中的任何一种都可赋给union,但必
须保证是一致的,即读取的类型必须是最近一次存入的类型。 如果保存的类型与读取的类型不一致,其结果取决于具体的实现。
例如:
#include<iostream.h>
int check_edian(void)
{
union {
unsigned int a;
unsigned char b;
}c;
c.a = 0x0001;
return (c.b == 1);
/*return 1 : little-endian, return 0:big-endian*/
}
void mian()
{
cout<<check_edian()<<endl;
}
这个解法涉及到Union的内存分配模式。
Union的大小为其内部所有变量的最大值,并且按照类型最大值的整数倍进行内存对齐.
举例中union分配的内存按照int分配4个字节,如果是小端模式则存放的方式为
地址A
------------------------------------
|A |A+1 |A+2 |A+3 | 地址
|0x01 |0x00 |0x00 |0x00 | int a;
-------------------------------------
|A | 地址
| c.b | char b;
---------
如果是大端如何存储c.a的呢?
地址A
------------------------------------------
|A |A+1 |A+2 |A+3 | 地址
|0x00 |0x00 |0x00 |0x01 | int a;
------------------------------------------
|A | 地址
| c.b | char b;
---------
因此就可以通过查看char b==1来判断大小端了。