基本概念
在这里简单的记录和介绍下大小端模式,如果在以后使用的过程中遇到更为深层次的问题再做讨论。
名词 | 注释 |
---|---|
大端 | 较低 的有效字节存放在 较高 的存储器地址 |
较高 的有效字节存放在 较低 的存储器地址 | |
小端 | 较低 的有效字节存放在 较低 的存储器地址 |
较高 的有效字节存放在 较高 的存储器地址 |
存储方式
如何去理解这个概念,我们要回到代码当中,通过变量的存储方式去判断,不妨先定义一个无符号整型数据 :
unsigned int data = 0x12345678;
虽然在这里我们并未验证32位数据在Little-endian/Big-endian模式下内存中存放的方式究竟如何,但通过概念可以大致得出以下图示,稍后会通过代码去验证其表示方式是否正确。
大端 | 内存地址 | 小端 |
---|---|---|
0x78 | 0x4003 | 0x12 |
0x56 | 0x4002 | 0x34 |
0x34 | 0x4001 | 0x56 |
0x12 | 0x4000 | 0x78 |
三种方式判断编译器的大小端
1、通过移位操作判断(待验证)
此验证方法由于需要大端和小端两种不同编译器环境去验证才可确保其真实性,但我恰好没有(太懒了,下次一定做),这是一种通过移位操作判断大小端的方法,但这种方法可能会无效,想了解的可以看一下,否则直接略过即可。
理由如下:因为大小端是数据在内存中的字节存放顺序,而运算指令是由CPU执行的,运算等方法是改变CPU寄存器中所存的值,而寄存器中的值通过大小端模式已经得到了正确的数据存放顺序,也就是说移位操作是针对数据而执行的,与数据存储方式无关,故不能通过移位操作判断数据存储方式。
地址: 高地址 ------------------> 低地址
小端模式存放:0000 0001 >> 1 == 0000 0000
大端模式存放:1000 0000 >> 1 == 0100 0000
在这里,还是贴出相关的判断代码,因为其仅需一句宏即可完成,n为int型变量,数值为1。
ifndef TRUE
#define TRUE 1
#endif
ifndef FALSE
#define FALSE 0
#endif
#define check_big_endian(n) n>>1?TRUE:FALSE
2、通过联合体判断
在使用联合体前,首先介绍下联合体的特点,简单的说就是一段空间,大家一起使用不分你我,主要作用是节省内存,跟结构体最大的区别是结构体内变量的空间是独立的,而联合体是共用同一段内存,利用这个特性,除了节省空间外,还能用于机器大小端的判断。
联合体特点 |
---|
union中可以定义多个成员,union的大小由最大的成员的大小决定 |
union成员共享同一块大小的内存,一次只能使用其中的一个成员 |
对某一个成员赋值,会覆盖其他成员的值 |
联合体union的存放顺序是所有成员都从低地址开始存放 |
利用联合体共用同一段内存的特性,申请一段大小为4个字节的空间,然后根据一个16进制占4位(bit),两个16进制占8位(bit)共1字节的原理,用0x12345678共8个16进制将联合体内的4个字节全部填充完整,最后直接取第一个字节的值,即取较低地址当中的值,根据我们上述说的小端模式中:较低的有效字节存放在较低的存储器地址,可知较低的有效字节为:0x78,如果该地址存放的值恰好等于0x78,那么即为小端模式,如果等于0x12,则为大端模式。
BOOL check_little_endian()
{
union
{
char a;
int b;
}u;
u.b = 0x12345678;
return (BOOL)(u.a == 0x78);
}
3、通过强制类型转换判断
当我们明白联合体的工作原理后,理解类型强制转换的方法应该是水到渠成了,同样是定义一个int型数据,取该数据的地址,然后用char*指针指向该地址的第一个字节,随后读取char *指针指向地址中所存储的值,如果为0x78,则是小端模式,如果为0x12,则是大端模式。
uint32 check_little_endian()
{
int i = 0x12345678;
char *p = (char *)&i;
return (*p == 0x78);
}