之所以有大小端,是由于在计算机中,数据都是二进制存储的,一个字节八位,比如 char 类型就一个字节,这在所有类型的机器上存储都一样,但是 int 类型呢,4 个字节,也就是 32 位,这四个字节是低字节先存储呢还是高字节先储存呢,这个就是大小端(Big endian 和 Little endian)的由来。
比如 int num = 16777220;
这个数用十六进制表示就是 0x12345678 ,从左往右数, 0x12 是高位字节,0x78 是低位字节。在小端序机子上依次是 0x78、0x56、0x34、0x12 存储,数据高位(高字节)存储在内存高地址,低位(低字节)对应低地址;相应的大端序就是反过来 0x12、0x34、0x56、0x78,数据高位存储在内存低地址,低位对应高地址。
下面就用 GDB 来探索下在真实的机器上 0x12345678 在内存中是如何存储的。
#include
int main()
{
int i = 0x12345678;
printf("%pn", &i);
return 0;
}
编译并用 GDB 调试
# gcc test.c -g -o test
# gdb test
……
(gdb) p/x i
$1 = 0x12345678
(gdb) x/b &i
0x7fffffffe62c: 0x78
(gdb) x/4 &i
0x7fffffffe62c: 0x78 0x56 0x34 0x12
可以看到 0x12345678 在内存中是按照 0x78 0x56 0x34 0x12 顺序存储的,因此是小端序。
那如何计算计算机是大端还是小端呢?可以通过 C 中 union (联合体或叫共同体)来检测,union 中所有成员的存放顺序是从低字节到高字节的。
#include
int isLittleEndian()
{
union {
short i;
char a[2];
} u;
u.a[0] = 0x11;
u.a[1] = 0x22;
return u.i == 0x2211;
}
int main()
{
int flag = isLittleEndian();
printf("this server is %sn", flag == 1 ? "Little Endian" : "Big Endian");
return 0;
}
编译后运行
# gcc test.c -g -o test
# ./test
this server is Little Endian
若还是有些不明白,那咱们再用 GDB 去调试下,看下内存布局就一目了然了。
# gcc test.c -g -o test
# gdb test
……
(gdb) b isLittleEndian
Breakpoint 1 at 0x4004c8: file end.c, line 10.
(gdb) r
Starting program: /var/www/html/test/c/2105/./end
Breakpoint 1, isLittleEndian () at end.c:10
(gdb) p/x u
$1 = {i = 0x2211, a = {0x11, 0x22}}
(gdb) p/x u.i
$2 = 0x2211
(gdb) p/x u.a
$3 = {0x11, 0x22}
(gdb) x/b &u.a
0x7fffffffe600: 0x11
(gdb) x/4 &u.a
0x7fffffffe600: 0x11 0x22 0xa5 0xf7
仔细看最后一行,0x11 0x22 也就是低字节在低位,高字节在高位,妥妥的小端序嘛。
当然了,还有个更简单的方法
int testEndian() {
int x = 1;
return *((char *)&x);
}
如果返回 1,那么为小端,否则就是大端。原理和上面介绍的一致,x 类型为 int,占 4 个字节,char 占 1 个字节,当用 char 指向 int 时,只会指向首字节,如果这个字节存储的为 1,那么说明是小端。因为 x 二进制为 00000000 00000000 00000000 00000001
,高字节为 0,低字节为 1,按照上面说的低字节放在低地址,那就是小端了。