最近工作中有用到一个知识点,就是大小端,当然这是一个小的知识点,为什么写一个博文呢,我其实是想测试一下chatGPT,所以我开始将自己的想法告诉这个chatbot,让他给我一些写博文的建议,并且给我解答了一些疑惑,今天将自己的学习笔记整理出来展示给大家(by the way,一个有用的搜索引擎和chatbot对我们的工作有着事半功倍的作用,并且它的思路很明显比我更好,如果只是单纯的用在学习上,我觉得这个简直是百科全书)。
废话不多说了,正文开始。
在计算机中,字节序指的是在存储器中,多字节数据的字节存放顺序。大小端是计算机体系结构中的一个概念,用于表示在多字节数据类型中,字节的顺序。在不同的计算机体系结构中,字节顺序可能不同。一些处理器将最高位字节存储在地址最低的位置,这被称为“大端字节序”(高位字节排放在内存的低地址端,低位字节排放在内存的高地址端),而另一些处理器将最低位字节存储在地址最低的位置,这被称为“小端字节序”(低位字节排放在内存的低地址端,高位字节排放在内存的高地址端)。
例如,假设我们要存储十六进制数0x12345678(十进制数为305419896),在大端字节序下,它的存储顺序为:
12 34 56 78
而在小端字节下,则为:
78 56 34 12
以上从左到右=》 低地址(高位)到高地址(低位)。偷个图:
大端和小端的概念最初由IBM的工程师Danny Cohen提出。这两种字节序最初是由不同的计算机厂商开发出来的。早期的计算机在内部存储器和CPU之间使用通信线,用于发送和接收数据。这些通信线被设计为双向的,可以在读和写时使用。因此,数据的字节顺序是很重要的。在当时的计算机中,大端字节序被广泛采用。
这是由计算机体系结构的设计决定的。在早期的计算机设计中,内存是以字(word)为单位进行读写的,字的长度不同,如16位、32位或64位等。在读写数据时,需要确定数据的字节顺序,以便正确地处理数据。在计算机系统中,以字节为单位,所以每个地址单元都对应一个字节,一个字节为8bit。但是在C语言中除了8bit的char外,还有16bit的short,32bit的long。另外对于位数大于8位的处理器,例如16位或者32位的处理器,由于寄存器大于一个字节,那么必然存在一个如何将多个字节安排的问题。因此就导致了大端存储和小端存储模式。
在大端字节序中,最高有效位在最低的地址上,符合人类的习惯,容易理解和记忆。而在小端字节序中,最低有效位在最低的地址上,这种方式更容易实现,因为它可以让计算机在处理字节序列时不需要额外的转换操作,只需要按照顺序依次读写即可。
实际上,不同的处理器架构和操作系统都有不同的字节序规则,例如x86架构的处理器使用小端字节序,而PowerPC架构的处理器使用大端字节序。因此,在进行数据传输或数据交换时,需要考虑数据的字节序,以确保数据的正确传输和解析。
大小端(Endian)是指计算机中字节的排列顺序。它分为大端(Big-endian)和小端(Little-endian)。大端表示高位字节存储在低地址,而小端表示低位字节存储在低地址。大小端的发展历史可以追溯到计算机的早期发展阶段。
1960s:大小端的概念最早可以追溯到20世纪60年代,IBM的System/360系列计算机采用了大端字节序。这种字节序的设计与人类阅读数字的方式相似,即从高位到低位。
1970s:在20世纪70年代,DEC公司推出了PDP-11系列计算机,采用了小端字节序。小端字节序的优势在于,对于多字节整数的部分访问和计算更加方便,因为低位字节总是存储在最低的地址。
1980s:随着计算机技术的发展,许多不同的处理器架构开始出现。例如,Motorola 68000系列处理器采用大端字节序,而Intel x86系列处理器采用小端字节序。这导致了计算机领域内的大小端之争。
1990s:在20世纪90年代,随着网络技术的普及,计算机之间的数据交换变得越来越重要。因此,网络字节序(Network Byte Order)的概念应运而生。网络字节序采用大端字节序,以确保不同处理器架构之间的数据交换能够顺利进行。
2000s至今:在21世纪,计算机技术继续发展,处理器架构也趋于多样化。许多处理器,如ARM和PowerPC,支持大小端可配置,使得系统设计者可以根据实际需求选择合适的字节序。此外,一些编程语言和库也提供了跨平台的字节序转换功能,以便在不同字节序的系统之间进行数据交换。
在早期的计算机体系结构中,大端字节序是主流的存储方式。但是,后来随着英特尔处理器的流行,小端字节序逐渐成为主流。英特尔处理器采用小端字节序的主要原因是,其实现简单且效率高。同时,小端字节序的处理方式也更符合人的直观感觉,因为我们通常是从低位到高位依次读取数据。总之,大小端的发展历史反映了计算机技术的演进和处理器架构的多样化。如今,大小端问题已经不再是一个严重的障碍,因为现代处理器和软件都提供了灵活的解决方案来应对字节序差异。
在计算机中,大端(Big-Endian)和小端(Little-Endian)是两种不同的数据存储方式。下面我们详细学习一下大小端的实现原理和存储方式,具体定义如下:
大端字节序(Big-Endian):在大端字节序中,数据的高位字节存储在低位地址上,而数据的低位字节存储在高位地址上。这种方式类似于我们人类习惯的顺序,从左到右,高位到低位。
存储方式:假设我们有一个32位整数0x12345678,其内存地址为0x1000。在大端字节序的系统中,该整数的存储方式如下:
地址 数据 0x1000 -> 0x12 (高位字节) 0x1001 -> 0x34 0x1002 -> 0x56 0x1003 -> 0x78 (低位字节)
小端字节序(Little-Endian):在小端字节序中,数据的低位字节存储在低位地址上,而数据的高位字节存储在高位地址上。这种方式与大端字节序相反,但是在实际应用中也有很多优点,例如可以方便地进行低位对齐。
存储方式:同样以32位整数0x12345678为例,其内存地址为0x1000。在小端字节序的系统中,该整数的存储方式如下:
地址 数据 0x1000 -> 0x78 (低位字节) 0x1001 -> 0x56 0x1002 -> 0x34 0x1003 -> 0x12 (高位字节)
大小端的实现原理和存储方式主要体现在多字节数据在内存中的排列顺序。大端字节序将高位字节存储在低地址,而小端字节序将低位字节存储在低地址。不同的处理器架构可能采用不同的字节序,因此在进行跨平台数据交换时需要注意字节序的转换。两种字节序的区别在于字节的存储顺序,而不是位的存储顺序。在多字节数据类型(例如整数、浮点数等)存储时,其字节序决定了字节在内存中的排列顺序。
下面是一个示意图,展示了一个32位整型数在大端和小端字节序下的存储方式:
大端字节序: 0x12345678 +--------+ | 12 | 34| +--------+ | 56 | 78| +--------+ 小端字节序: 0x12345678 +--------+ | 78 | 56| +--------+ | 34 | 12| +--------+
不同的处理器架构可能采用不同的字节序规则。以下是一些常见处理器架构及其字节序规则:
Intel x86/x64(IA-32/IA-64): 字节序规则:小端(Little-endian) 说明:Intel x86和x64架构处理器广泛应用于个人计算机和服务器领域。这些处理器采用小端字节序,将低位字节存储在低地址。
ARM: 字节序规则:大小端可配置(Configurable Endianness) 说明:ARM处理器广泛应用于嵌入式系统和移动设备。ARM架构支持大小端可配置,系统设计者可以根据实际需求选择合适的字节序。大多数情况下,ARM处理器默认采用小端字节序。
MIPS: 字节序规则:大小端可配置(Configurable Endianness) 说明:MIPS处理器应用于嵌入式系统、网络设备和游戏机等领域。MIPS架构支持大小端可配置,允许系统设计者根据需求选择字节序。通常情况下,MIPS处理器默认采用大端字节序。
PowerPC: 字节序规则:大小端可配置(Configurable Endianness) 说明:PowerPC处理器应用于嵌入式系统、服务器和游戏机等领域。PowerPC架构支持大小端可配置,允许系统设计者根据需求选择字节序。通常情况下,PowerPC处理器默认采用大端字节序。
Motorola 68000: 字节序规则:大端(Big-endian) 说明:Motorola 68000系列处理器曾广泛应用于个人计算机、游戏机和嵌入式系统等领域。这些处理器采用大端字节序,将高位字节存储在低地址。
SPARC: 字节序规则:大端(Big-endian) 说明:SPARC处理器主要应用于高性能计算和服务器领域。SPARC架构采用大端字节序,将高位字节存储在低地址。
需要注意的是,不同处理器架构的字节序规则可能会影响跨平台数据交换。在进行数据交换时,需要注意字节序的转换,以确保数据的正确性。许多编程语言和库提供了跨平台的字节序转换功能,以便在不同字节序的系统之间进行数据交换。
简单将上面内容整理一下(方便直接看结论的童靴):
大小端 | CPU |
Big Endian | PowerPC, MIPS, Mac OS, IBM , Sun, Mototola 68000, SPARC |
Little Endian | x86, x64, DEC, Windows |
遇到大小端的问题,我们如何解决呢,首先就是判断机器的字节序(是大端字节序还是小端字节序),然后进行转换。
给一个C++判断机器字节序的代码:
// Judge the endian of the local system inline bool isLittleEndian() { union { uint32_t i; char c[4]; } test = {0x01020304}; return test.c[0] == 0x04; }
或者下面例子:
#include <iostream> // 判断机器字节序的函数 bool isLittleEndian() { int num = 1; char *ptr = reinterpret_cast<char*>(&num); return *ptr == 1; } int main() { if (isLittleEndian()) { std::cout << "This machine is Little-endian." << std::endl; } else { std::cout << "This machine is Big-endian." << std::endl; } return 0; }
下面是一个转换大小端字节序的例子,比如你的机器需要大端,所以处理不同操作系统的时候,你需要考虑先判断系统字节序的,然后不同的需要转换,毕竟我们上面也提到了不同系统字节序不同。
#include <iostream> // 转换16位整数的字节序 uint16_t swapEndian16(uint16_t value) { return (value << 8) | (value >> 8); } // 转换32位整数的字节序 uint32_t swapEndian32(uint32_t value) { return ((value << 24) & 0xFF000000) | ((value << 8) & 0x00FF0000) | ((value >> 8) & 0x0000FF00) | ((value >> 24) & 0x000000FF); } // 转换64位整数的字节序 uint64_t swapEndian64(uint64_t value) { return ((value << 56) & 0xFF00000000000000) | ((value << 40) & 0x00FF000000000000) | ((value << 24) & 0x0000FF0000000000) | ((value << 8) & 0x000000FF00000000) | ((value >> 8) & 0x00000000FF000000) | ((value >> 24) & 0x0000000000FF0000) | ((value >> 40) & 0x000000000000FF00) | ((value >> 56) & 0x00000000000000FF); } int main() { uint16_t num16 = 0x1234; uint32_t num32 = 0x12345678; uint64_t num64 = 0x123456789ABCDEF0; std::cout << "Original 16-bit value: " << std::hex << num16 << std::endl; std::cout << "Swapped 16-bit value: " << std::hex << swapEndian16(num16) << std::endl; std::cout << "Original 32-bit value: " << std::hex << num32 << std::endl; std::cout << "Swapped 32-bit value: " << std::hex << swapEndian32(num32) << std::endl; std::cout << "Original 64-bit value: " << std::hex << num64 << std::endl; std::cout << "Swapped 64-bit value: " << std::hex << swapEndian64(num64) << std::endl; return 0; }
上面的示例展示了如何在C++中判断机器的字节序以及如何转换大小端字节序。在实际应用中,可能大家需要根据自己的需求对代码进行调整和优化,我自己也是。(当然如果使用python,可能更方便)