推荐下我之前写的C/C++ OpenGL入门专栏,如果你想学习OpenGL,欢迎订阅,也可以通过底部的阅读原文进入
最近一次的项目组会议中涉及到了大小端的讨论,而后我又在读者交流群内发了条消息,想引发大家对于这个基本概念——大小端的思考。后来读者朋友想要让我写一篇文章来讨论下大小端的问题,遂作本文。
我写文章分析问题的通常有一个固定的思路,是什么(What)、为什么(Why)、怎么做/用(How)。本文也将按照这个思路进行。
1. 什么是大小端?我们学习任何一门语言时,都需要了解支持的数据类型及其占用的字节数,在不考虑特殊平台/编译器(下同,本文均不考虑特殊平台、特殊编译器)的情况下:
char:占用1个字节;int:占用4个字节;long:占用4个字节;float:占用4个字节;double:占用8个字节;我们可以看到除char外占用超过一个字节。我们知道内存以字节为单位存储数据,那内存中是如何存储大于一个字节的数据呢。 以int类型的数据为例,已知int类型数据占用四个字节,如存在数据int a = 0x12345678,a在内存中如何存储的呢?是从高位开始排,还是从低位开始排?
从低位排或者从高位排,这是两种字节序的讨论:
大端(Big Endian):高位字节排在低地址处,低位字节排在高地址处。小端(Little Endian):低位字节排在低地址处,高位字节排在高地址处。你可以把它类比为读书的顺序:
大端更像是我们平常从左往右写字(高位在前)。小端则像是从右往左写字(低位在前)。针对于上文提到的int型数据a,其大小端的存储方式如下:
通过该表格,可以看到:大端更符合我们的阅读习惯。
2.为什么会有大小端?为什么会出现两种存储类型呢,为什么当初没能够统一呢?出现两种存储类型,不仅增加了理解负担,而且增加了使用成本。
究其原因,是因为计算机体系结构的不同,导致不同的处理器架构做出了不同的选择:
摩托罗拉(PowerPC 等) 使用的是大端序。Intel x86 系列 一直使用小端序。ARM 架构 比较灵活,很多芯片可以配置成大端或小端。既然大端是符合我们的阅读习惯的,为什么会出现小端呢?主要是为了在某些运算(如整数低位截取)时更方便——因为地址最低的那一位刚好就是最低有效字节。 所以说:大端方便人类阅读,小端方便机器运算。
两种字节序具有其各自的优势,但是两种字节序的存在,也导致了新的问题——当字节序不一致时,解析数据将会出错。
3. 怎么做为了避免数据解析出错,我们应该做好两步:
判断当前平台的大小端和待解析数据的大小端是否一致;如果不一致,则进行转换;所以,首要任务是判断当前平台是大端还是小端?
3.1 大小端判断结合大小端的定义——大端的高位字节在低地址处,小端的低位字节在低地址处,我们可以通过一个简单的 C 语言代码来判断当前平台的大小端:
代码语言:javascript复制int IsLittleEndian() {
int a = 1;
return *(char*)&a == 1; // 如果最低地址存的是 1,就是小端
}
3.2 数据转换如果当前平台的大小端和数据的大小端不一致,则需要进行转换。转换的方式有很多:
自己写转换函数自己书写转换方法也很简单,需要配合移位操作和按位与操作。如下是一个简单的转换函数:
代码语言:javascript复制uint32_t SwapEndian(uint32_t val) {
return ((val >> 24) & 0x000000FF) |
((val >> 8) & 0x0000FF00) |
((val << 8) & 0x00FF0000) |
((val << 24) & 0xFF000000);
}
借助成熟的序列化库当前存在很多成熟的序列化库,如protobuf、flatbuffers等,它们都提供了跨平台、跨语言的数据序列化和反序列化功能,并且自动处理了大小端的问题。可以自行查找对应的库,本文不再赘述。
4. 总结其实大小端是一个老生常谈但又非常基础的问题,本文从是什么、为什么、怎么做三个方面进行了分析,希望对你有所帮助。
