今天谈谈一个重要的计算机概念大家可能都听说过它但是很少深究那就是字节序Endianness。一、概念字节序指的是多字节数据的内存排列顺序。这样说比较抽象使用图形解释就很好懂。内存好比一排房间每个字节是一间房。每间房都有门牌号内存地址从0号开始然后是1号、2号…0号字节的地址小称为低位内存3号字节的地址大称为高位内存。现在有一个数值abcd要放进这些房间每个房间放一个数字那么有两种放法。第一种放法是第一位a放在低位地址0号最后一位d放在高位地址3号。这种排列称为大端序big-endian简称 BE即大头在前因为a是abcd的大头最重要的数字。第二种放法是第一位a放在高位地址3号地址最后一位d放在低位地址0号地址。这种排列称为小端序little-endian简称 LE即小头d在前。大端序和小端序合称字节序这两个名字来自18世纪的英国小说《格列佛游记》。某国分成两派一派认为鸡蛋应该从大头吃起称为大端派另一派认为鸡蛋应该从小头吃起称为小端派。两派相执不下谁也无法说服谁最后甚至为此交战。二、可读性对于人类来说不同字节序的可读性是不一样的。大部分国家的阅读习惯是从左到右阅读。大端序的最高位在左边最低位在右边符合阅读习惯。所以对于这些国家的人来说从左到右的大端序的可读性更好。但是现实中从右到左的小端序虽然可读性差但应用更广泛x86 和 ARM 这两种 CPU 架构都采用小端序这是为什么或者换一种问法两种不同的字节序为什么会并存统一规定只使用一种难道不是更方便吗原因是它们有各自的适用场景某些场景大端序有优势另一些场景小端序有优势下面就逐一分析。三、检查奇偶性小端序优势最明显的大概就是检查奇偶性即通过查看个位数确定某个数字是奇数还是偶数。以123456为例大端序从左到右排列计算机必须一直读到最后一位的个位数6才能确定这是偶数。小端序是从右到左排列个位数在第一位。所以只要读取第一位就能确定它是偶数。四、检查正负号一个类似的场景是检查正负号确定一个数是正数还是负数。大端序的符号位在左边第一位小端序的符号位在右边最后一位。所以大端序有优势只看第一位就能知道是不是负数。五、比较大小下一个操作是比较大小。现在有三个数字需要比较大小436625765942。上图是大端序排列因为是从左到右排列所以三个数字在右边个位数对齐。比较大小时计算机就不得不读取每一个数的所有位直到个位数再进行比较。如果改成小端序就是下面的排列方式。小端序是从右到左所以三个数字在第一位对齐。计算机就不需要读取所有位哪个数字先读不到下一位就是最小的。比如2这个数字就没有第二位所以读到第二位时就知道它是最小的。所以比较大小时小端序有优势。六、乘法接下来再看乘法操作。乘法是逐位相乘每一轮乘法都要向前进位。上图是大端序的24165乘以3841。大端序的乘法是向左进位也就是向左边扩展必须等到每一轮的结果都出来上例是四轮再相加统一写入内存。如果改成小端序的乘法就不需要等待下一轮的结果每一轮都可以直接写入内存。上图是小端序的24165乘以3841。小端序的乘法是向右进位也就是向右边扩展左边的边界不变。每一轮结果写入内存后就不需要移动后面有变化只需要改动对应的位就行了。因此小端序的乘法有明显优势。七、任意精度整数上一个例子的从低位开始计算的特性对于任意精度整数特别有用。任意精度整数又称大整数可以存放任意大小的整数。它的内部实现是把整数分成一个个较小的单位通常是 uint32无符号32位整数或 uint64无符号64位整数按顺序组合在一起。如果是大端序第一个 u64 就是这个整数最大的部分。运算时一旦这个数发生变化需要进位后面的所有位都必须移动和改写。小端序发生进位时往往就不需要所有位移动。小端序的另一个好处是如果逐字节的运算从个位数开始比如乘法和加法可以从左到右依次运算一个个 u64算完上一个再读取下一个。大端序就不行必须读取整个数以后再进行运算。八、更改类型最后一个例子是C 语言有一种 cast 操作可以强制改变变量的数据类型比如把32位整数强行改变为16位整数。上图中32位整数0x00000001更改为16位整数0x0001大端序是截去前面两个字节这时指向这个地址的指针必须向后移动两个字节。小端序就没有这个问题截去的是后面两个字节第一位的地址是不变的所以指针不需要移动。九、总结综上所述大端序和小端序各自的优势如下。如果需要逐位运算或者需要到从个位数开始运算都是小端序占优势。反之如果运算只涉及到高位或者数据的可读性比较重要则是大端序占优势。大端序Big-endian和小端序Little-endian是指在多字节数据类型如整数或浮点数的存储和表示方式上的不同。大端序Big-endian是指将高位字节存储在低地址低位字节存储在高地址的方式。这意味着在内存中数据的高位字节位于低地址而低位字节位于高地址。小端序Little-endian则是将低位字节存储在低地址高位字节存储在高地址的方式。也就是说数据的低位字节位于低地址高位字节位于高地址。网络字节序Network Order是一种规定好的字节顺序用于在不同计算机之间进行数据交换。它采用的是大端序的方式即将高位字节存储在低地址。因此网络字节序也被称为大端序。在网络通信中为了确保不同计算机平台之间的数据交换正确需要进行字节序的转换。当数据从网络传输到计算机内存或从计算机内存传输到网络时需要进行字节序的转换以保持数据的正确顺序。为了进行字节序转换通常可以使用一些特定的函数或者库如htonl、htons、ntohl、ntohs等这些函数可以在不同字节序之间进行转换确保数据在网络通信中的正确传输。实例 在大端序Big-endian和小端序Little-endian中表示整数值 100 在内存中的区别在于字节的顺序。在大端序表示中最高有效字节MSB存储在最低的内存地址而最低有效字节LSB存储在最高的内存地址。因此对于整数值 100十六进制为 0x64在大端序表示中它将按如下方式存储内存地址 值地址 0 0x00地址 1 0x00地址 2 0x00地址 3 0x64而在小端序表示中最低有效字节LSB存储在最低的内存地址而最高有效字节MSB存储在最高的内存地址。因此对于相同的整数值 100十六进制为 0x64在小端序表示中它将按如下方式存储内存地址 值地址 0 0x64地址 1 0x00地址 2 0x00地址 3 0x00大端序和小端序的区别在于多字节数据类型如整数或浮点数在内存中的字节顺序。这种字节顺序的差异会影响不同系统或处理器对这些值的解释和操作方式。需要注意的是字节序的选择由硬件架构和具体实现决定。不同的系统和处理器可能使用不同的字节序因此在系统之间交换数据或在二进制级别操作数据时考虑字节顺序是非常重要的。总结大端序Big-endian高位字节存储在低地址。小端序Little-endian低位字节存储在低地址。网络字节序Network Order采用大端序用于网络通信中的数据交换。 - 字节序转换在不同字节序的平台之间进行数据交换时需要进行字节序的转换以保持数据的正确顺序。一、概念深化从抽象到具象您用“房间”和“门牌号”来比喻内存地址和数据存储非常形象。我们在此基础上用一个具体的十六进制数来让这个概念彻底清晰。假设我们有一个32位4字节的十六进制整数0x12345678。0x12是最高有效字节MSB可以理解为“大头”。0x78是最低有效字节LSB可以理解为“小头”。现在我们要把这个数存放到从内存地址0x1000开始的4个连续字节中。大端序 (Big-Endian, BE)大端序的规则是高位字节存放在低地址。这完全符合我们人类从左到右的阅读和书写习惯。内存布局地址0x1000(低地址): 存放0x12(MSB)地址0x1001: 存放0x34地址0x1002: 存放0x56地址0x1003(高地址): 存放0x78(LSB)在内存中数据看起来就是12 34 56 78和我们写的值一模一样非常直观。小端序 (Little-Endian, LE)小端序的规则是低位字节存放在低地址。这看起来是“反着”存的。内存布局地址0x1000(低地址): 存放0x78(LSB)地址0x1001: 存放0x56地址0x1002: 存放0x34地址0x1003(高地址): 存放0x12(MSB)在内存中数据看起来是78 56 34 12与我们书写的顺序正好相反。二、为什么并存—— 性能与直觉的权衡您已经详细分析了小端序在算术运算乘法、加法、类型转换等方面的性能优势以及大端序在可读性和符号判断上的优势。这正是它们并存的根本原因设计哲学上的不同权衡。小端序 (Little-Endian)为硬件效率而生。它的设计优先考虑了CPU执行算术运算的便利性因为大多数运算如加法、乘法都是从最低位开始并逐级进位。小端序让CPU可以从低地址开始线性读取和处理数据无需额外的地址计算简化了硬件设计提高了运算效率。这也是为什么以性能为导向的x86架构Intel, AMD和广泛应用的ARM架构默认都选择小端序。大端序 (Big-Endian)为协议和人类直觉而生。它的优势在于数据在内存中的排列顺序与人类的阅读习惯一致这在调试和查看内存转储Memory Dump时非常方便。更重要的是它成为了网络协议的标准确保了不同架构设备间通信的一致性。三、案例分析字节序在真实世界中的“坑”与“桥”理论上的差异在实际开发中尤其是在跨平台、跨设备交互时会演变成一个个具体的“坑”。理解这些案例是掌握字节序的关键。案例一网络通信中的“巴别塔”问题这是字节序问题最经典、最重要的应用场景。背景互联网由各种各样的设备组成有的CPU是小端序如你的个人电脑有的是大端序如某些网络设备。如果一台小端序的机器直接把它内存中的0x12345678存储为78 56 34 12发送给一台大端序的机器接收方会按照自己的规则高位在前去解读结果就会读成0x78563412一个完全不同的数字这就像两个人说着不同的语言无法沟通。解决方案网络字节序 (Network Byte Order)为了解决这个问题TCP/IP协议族在诞生之初就做出了一个至关重要的规定所有在网络上传输的多字节数据必须统一采用大端序。这个标准被称为“网络字节序”。这样一来通信流程就变成了发送方无论何种架构在发送数据前必须将自己的“主机字节序”转换为“网络字节序”大端序。网络传输数据以统一的大端序格式在网线中流动。接收方无论何种架构收到数据后再将其从“网络字节序”转换回自己的“主机字节序”。代码实践C/C的Socket编程提供了一套标准函数来完成这个转换开发者无需关心底层细节htonl()(Host TO Network Long): 将32位主机序转为网络序。htons()(Host TO Network Short): 将16位主机序转为网络序。ntohl()(Network TO Host Long): 将32位网络序转回主机序。ntohs()(Network TO Host Short): 将16位网络序转回主机序。举例你要连接服务器的8080端口。在你的小端序电脑上端口号8080十六进制0x1F90在内存中是90 1F。在调用connect()函数前你必须使用htons(8080)将其转换为大端序1F 90再发送。这样无论服务器是什么架构它都能正确地识别出端口号是8080。案例二文件格式解析的“暗礁”不同的文件格式也有自己规定的字节序解析时如果不注意就会读取出错。BMP 图像格式采用小端序。当你用程序读取一个BMP文件头来获取图像宽度和高度时必须按照小端序的规则去解析字节。PNG 图像格式采用大端序。它的文件头中有一个固定的魔数0x89504E47如果你用大端序去读就能正确识别出这是一个PNG文件。如果用错字节序这个魔数就会变得面目全非。Java Class 文件Java虚拟机JVM内部采用大端序。这意味着所有Java编译后的.class文件其内部的常量池、方法表等结构都遵循大端序。举例假设一个BMP文件的宽度是0x0200(512像素)。在小端序文件中这两个字节会存储为00 02。如果你的解析程序错误地按照大端序去读就会得到0x0002(2像素)导致图像显示异常。案例三嵌入式与工业协议的“方言”在工业控制领域字节序问题同样普遍甚至更复杂。背景Modbus是工业领域广泛使用的通信协议。虽然它传输的是16位的寄存器但当需要传输32位的浮点数或长整数时就需要占用两个连续的16位寄存器。这时不仅存在字节序Byte Order问题还存在字序Word Order问题。常见的组合模式大端模式 (ABCD)高字节在前高字在前。符合网络字节序。小端模式 (BADC)低字节在前但高字在前。这是许多基于x86架构的工控软件如某些SCADA系统的常见模式。小端模式 (CDAB)高字节在前但低字在前。完全小端 (DCBA)低字节在前低字在前。举例一个传感器要发送一个32位浮点数255.0(十六进制0x437F0000)。如果设备配置为ABCD模式它会在寄存器中依次存放0x437F,0x0000。如果上位机软件期望的是BADC模式它会读取为0x7F430000解析出的数值将是一个完全不同的、非常小的数。这种问题在调试时非常隐蔽因为通信本身是通的只是数据“不对”。解决它需要仔细查阅设备手册并在软件中进行相应的字节和字的重组。四、总结与最佳实践字节序是计算机底层的一个基础概念它本身没有绝对的好坏只是在不同的场景下各有优劣。特性大端序 (Big-Endian)小端序 (Little-Endian)存储规则高位字节在低地址低位字节在低地址人类可读性优 (符合阅读习惯)差 (看起来是反的)硬件运算效率一般优 (便于从低位计算)类型转换需调整指针无需调整指针主要应用网络协议(TCP/IP), Java, PNGx86/ARM CPU, Windows, BMP给开发者的最佳实践永远不要假设在处理任何二进制数据网络包、文件、硬件寄存器时首先要明确其字节序约定。善用标准库在网络编程中务必使用htonl,ntohs等标准函数不要自己实现字节反转。编写可移植代码如果你的程序需要在不同架构间迁移避免直接进行内存拷贝memcpy来传输多字节数据应使用显式的序列化/反序列化逻辑。调试时保持警惕当遇到数据值“莫名其妙”的错误时字节序问题是一个需要优先排查的方向。使用十六进制编辑器或调试器查看原始内存布局往往能一针见血地发现问题。