大小端存储的故事源自于《格列佛游记》(Gulliver's Travels),这是爱尔兰作家乔纳森·斯威夫特(Jonathan Swift)于1726年所著的一部讽刺小说。在其中,主人公格列佛(Lemuel Gulliver)游历到一个名为 "利里普特"(Lilliput)的岛屿。在 "利里普特" 岛上,有两个派别:大端族和小端族,他们之间发生了长期的争斗。这两个派别之间的分歧源于如何打开鸡蛋的方式:大端族主张从大头砸开蛋壳,小端族则主张从小头砸开蛋壳。
在计算机中,数据的存储方式通常分为大端序(Big-Endian)和小端序(Little-Endian)两种方式。这两种方式涉及到多字节数据在内存中的存储顺序。
-
大端序(Big-Endian):在大端序中,数据的高位字节存储在低地址内存中,而低位字节存储在高地址内存中。因此,多字节数据的最高有效字节存储在最低内存地址处。这种方式类似于将数字从左到右排列。
-
小端序(Little-Endian):在小端序中,数据的低位字节存储在低地址内存中,而高位字节存储在高地址内存中。因此,多字节数据的最低有效字节存储在最低内存地址处。这种方式类似于将数字从右到左排列。
举例来说,对于十六进制数 0x12345678:
-
在大端序中,它会以字节为单位按照内存地址从低到高依次存储,即 12 34 56 78。
-
在小端序中,它会以字节为单位按照内存地址从低到高反向存储,即 78 56 34 12。
以下是在我Intel i7 x64 vs2019中显示内存中的结果,高位字节在内存地址高位,低位位字节在内存地址低位。
在实际应用中,不同的处理器架构和操作系统可能采用不同的字节序,因此在进行数据交换或跨系统通信时,需要注意字节序的转换以确保数据的正确传输和解释。
我们可以用以下程序测试你的系统数据存储采取的哪种方式。
#include <stdio.h>
int check_endianness() {
unsigned int num = 1;
char *ptr = (char *)#
// 如果系统是小端序,则第一个字节应该是1(最低有效字节)
if (*ptr == 1) {
return 0; // 小端序
} else {
return 1; // 大端序
}
}
int main() {
if (check_endianness() == 1) {
printf("系统是大端序存储数据。\n");
} else {
printf("系统是小端序存储数据。\n");
}
return 0;
}
在上面的程序中,将整数 1 强制转换为 char 指针的原因如下:
-
字节访问:char 类型是一个字节大小的数据类型,在 C 语言中通常用来表示字节。强制将整数指针转换为 char 指针后,可以按字节访问内存,以便检查系统存储的第一个字节。
-
绕过对齐要求:强制类型转换为 char 指针可以绕过内存对齐的要求。这一点对于需要直接访问内存的操作很有用,并且有助于确保我们可以准确地读取每个字节的值。
-
便于解释和比较:将整数强制转换为 char 指针后,可以更容易地解释和比较存储在不同字节中的数据。因为我们感兴趣的是内存中的单个字节,而不是整个整数。
程序中使用 char 指针的主要目的是为了以字节为单位访问内存,并且能够更直观地获取每个字节的值,从而帮助我们确定系统是以大端序还是小端序存储数据。
关于字节对齐
字节对齐(Byte Alignment)是指计算机系统中数据在内存中存储时按照特定的规则对齐到内存地址的过程。这个规则要求不同类型的数据在内存中被放置在特定地址上,以便系统能够高效地访问这些数据。在大多数计算机体系结构中,数据类型在内存中的起始位置是按照其自身大小的整数倍进行对齐的。通常情况下,基本数据类型如 char(1 字节)、short(2 字节)、int(4 字节)、long(4 或 8 字节)等会被对齐到它们自身所占用的字节数的倍数的地址上。例如,在一个要求以 4 字节对齐的系统中,int 类型变量将会被放置在内存地址为 0、4、8、12 等等的位置上。
字节对齐的原因主要有以下几点:
-
提高访问速度:对齐数据可以使处理器更有效地从内存中读取数据,因为处理器通常会更快地访问对齐地址的数据。
-
硬件要求:某些处理器甚至要求数据是对齐的,否则可能会导致错误或性能下降。
-
结构体对齐:结构体的对齐方式通常会受到其中包含的各个成员变量对齐方式的影响,以保证整个结构体中每个成员都被正确对齐。
虽然字节对齐是有利的,但也可能会导致内存空间的浪费。编译器通常会根据所使用的编译选项和平台对数据进行默认的对齐,同时还允许开发者通过指定对齐方式来优化数据布局。
快去测试一下你的机器是什么端!