当前位置: 首页 > 图灵资讯 > 技术篇> [嵌入式er笔记]大端小端详解(含代码及详细注释)

[嵌入式er笔记]大端小端详解(含代码及详细注释)

来源:图灵教育
时间:2023-05-23 09:28:32

link

之前文章《 浅谈ARM ABI,Android ABI 》有提到计划的专题文章讲大小端,今天就兑现一下。

1>"大端" "小端"的来源

网上有一个关于大端小端名词起源的有趣故事,可以追溯到1726年的Jonathan Swift的《格列佛游记》中的一篇文章讲述了两个国家争论不休,甚至爆发了战争,因为他们吃鸡蛋是先打破大端还是小端。

《格利佛游记》:在过去的36个月里,Lilliput和Blefuscu一直在苦战。战争的原因是:我们都知道,吃鸡蛋,原来的方法是打破鸡蛋的大端,但皇帝的祖父因为小时候吃鸡蛋,根据这种方法打破手指,所以他的父亲,命令所有的孩子吃鸡蛋,必须打破鸡蛋的小端,违规者严厉惩罚。然后人们对这项法律非常反感,导致了许多叛乱。叛乱的原因是另一个国家Blefuscu的国王大臣煽动的。叛乱平息后,流亡的人逃到帝国避难。据估计,已有11000多人愿意si也不肯打破鸡蛋较小的端吃鸡蛋。”

Swift的《格列佛游记》实际上是在讽刺英国(Lilliput)和法国(Blefuscu)之间持续的冲突。

Big现在以大端和小端命名-Endian、Little-Endian看,确实符合鸡蛋的特点,一切都来自生活。

2> 计算机中的“大端”和“小端”是什么意思?

真正引入计算机领域的大端小端,来自于网络协议的早期创始人Danny Cohen,他第一次用这两个术语来指代字节顺序,后来慢慢被大家广泛接受。

字节顺序是什么?首先复习一个基本知识:

位(bit):计算机中最小的数据单元,计算机存储两个鬼:二进制0和1。

字节(Byte):字节是存储空间的基本测量单位,内存的基本单位和编址单位。例如,计算机内存为4GB,即计算机内存为4GB×1024×1024×1024字节意味着它有4G内存搜索空间。

换算关系:

1 GB = 1024 MB

1 MB = 1024 KB

1 KB = 1024 Bytes

1 Byte = 8 bits

【Q】:思考一个问题,通常描述32位二进制数据,为什么使用8个十六进制数?例如0x1A2B3C4D

【A】:十六进制(hex)是一种逢16进1的进位制。16进制的数字有1、2、3、4、5、6、7、8、9A(10),B(11),C(12),D(13),E(14),F(15)。

第一种理解:

四个二进制bit 数值范围为000~1111,即0~15, 刚好等同于 一位 16进制数值范围0~F(15)。

因此可以类推:

1Byte = 8bit;也就是说,一个字节包含8个二进制bit,8个二进制bit需要2位16进制数来表示(最大值是 0xFF);

4Byte = 32bit; 也就是说,4个字节包含32个二进制bit,32个二进制bit需要8位16个进制数来表示;(最大值为 0xFFFFFFFF);

第二种理解:

由于16=2^4(24次方),1位16进制数可转化为4位2进制数,即16进制的每个字符需要用4位2进制位表示,如0x0为000,0xf为111,即1位16进制数为4位2进制bit。

因此,将32位二进制数转换为16进制数后的位数转换为32位÷4=8位,即32位二进制地址信息需要8位十六进制数表示。

总结下:

4个二进制位(bit)(不足以表示字节) = 1个十六进制(hex)。

8个二进制位(bit) = 一个字节(Byte) = 2个十六进制(hex)。

32个二进制位(bit) = 四个字节(Byte) = 8个十六进制(hex)。

因此,对于0x1A2B3C4D等32位数值,共有四个字节,两个16进制数表示一个字节,高字节为0x1A,低字节为0x4D;中间两个字节分别是0x2B和0x3C;

如果要在计算机中正确使用数值0x1A2B3C4D,必须考虑在内存中合理存储相应的四个字节。假设内存的地址从低到高分布,有两种存储方法可以存储一个值和多个字节顺序:

方法一:内存低地址端存储数值高字节,内存高地址端存储低字节:

内存低地址 --------------------> 内存高地址

0x1A | 0x2B | 0x3C | 0x4D

高位字节 <-------------------- 低位字节

方法二:内存低地址端存储数值低字节,内存高地址端存储高字节:

内存低地址 --------------------> 内存高地址

0x4D | 0x3C | 0x2B | 0x1A

低位字节 --------------------> 高位字节

方式一 ,我们称之为 大端模式;即高字节放在内存的低地址端,低字节放在内存的高地址端。

方式二 ,我们称之为 小端模式;即低字节放在内存的低地址端,高字节放在内存的高地址端。

更直观地理解画图:

[嵌入式er笔记]大端小端详解(含代码及详细注释)_小端模式

[嵌入式er笔记]大端小端详解(含代码及详细注释)_c++_02

总结下:

  • 大端小端是不同的字节顺序存储方式,统称为字节序;
  • 大端模式是指数据的高字节位 保存在 内存的低地址,数据的低字节位 保存在 内存的高地址。这种存储模式有点类似于将数据作为字符串顺序处理:地址从小到大,数据从高到低。这与我们“从左到右”的阅读习惯是一致的。
  • 小端模式是指数据的高字节位 保存在 内存的高地址,数据的低字节位 保存在 内存的低地址。这种存储模式有效地将地址的高度与数据位权相结合。高地址的高权值和低地址的低权值与我们的逻辑方法相一致。

分享一个私人口语记忆技巧:

  • 大端模式:“[低位]字节在[高位]地址中坚持存在”。-“低对高(或高对低),门不对,真的很大”,记为大端模式。
  • 小端模式:“[低位]字节正好存在于[低位]地址中。--- “低对低,门当户对,你侬我侬”,记为小端模式。

3> 为什么要学会理解“大端”和“小端”?

大端模式:

根据其存储特性,符号位置在数据内存的第一个字节中,便于快速判断数据的正负和大小(CPU从低地址到高地址,大端将首先获得数据(高字节)符号位)。

小端模式:

根据其存储特性,低字节存储在内存的低地址,因此在强制转换数据时不需要调整字节的内容(例如,将int-4字节强制转换为short-2字节,您可以直接将int数据存储的前两个字节交给short,因为前两个字节只是最低的两个字节,符合转换逻辑;此外,CPU在进行数值操作时,从低位地址到高位地址依次从内存中依次取数据,开始只取值,最后刷新最高位地址的符号位。这种操作方法会更有效率)。

由于这两种模式各有优势,具有“你有我没有,你没有我”的特点,因此不同的硬件制造商基于不同的效率(角度),有不同的硬件设计支持,最终形成了计算机相关领域不采用统一的字节序,没有统一的标准。

其实也不难理解,就像文章开头描述的“吃鸡蛋的方式一样” 谁能站出来限制或证明世界上最大的鸡蛋必须从“小端”开始,而不是从“大端”开始 好?或者 吃鸡蛋必须从“大端”开始,而不是从“小端”开始 好?呢。

“大端“ ”小端” 每个人都有自己的优点,就像吃鸡蛋一样,世界有自己的选择权。

CPU目前是我们常见的CPU PowerPC、IBM是大端模式,X86是小端模式。ARM可以在大端模式或小端模式中工作。一般来说,ARM默认为小端模式。一般通信协议采用大端模式。

此外,常见文件的字节序如下:

BMP – Little Endian

GIF – Little Endian

JPEG – Big Endian

RTF – Little Endian

Adobe PS – Big Endian

DXF(AutoCAD) – Variable

因此,只有了解“大端”和“小端”,才能在跨平台、跨芯片、跨系统、跨网络通信中实时检查和转换内存字节序,确保内容传输的正确性。假设没有操作系统工程师,网络工程师默默地检查和转换后面的字节序,也许你用你的X86机器通过QQ给我PowerPC机QQ表白色,但数据传输混乱的yipi内存,消息,前言,我不知道你想说什么,也不知道你在忏悔,彼此完美错过将是结局。

4> 当前计算机环境采用大端模式或小端模式进行C代码检测。

举例:

  • 方式一: 借助联合体union的特点(联合体类型数据占用的内存空间等于其最大成员占用的空间,联合体内所有成员的访问相对于联合体基地址的偏移量为 0 从联合体所占内存的第一个地址开始。)

#include <stdio.h>int main(){    union{      int a;  //4 bytes      char b; //1 byte    } data;<span >data</span><span >.</span><span >a</span> <span >=</span> <span >1</span><span >;</span> <span >//占4 bytes,十六进制可以表示为 0x 00 00 00 01

//b因为char型只占1Byte,a因为int型占4Byte //因此,在联合体data所占内存中,b所占内存等于a所占内存的低地址部分 if(1 == data.b){ ///走case说明a的低字节,被取给b,即a的低字节有联合体所占内存的低地址,符合小端模式的特点 printf(“Little_Endian\n”);} else {printf(“Big_Endian\n”);}return 0;}

说明:

赋值 1 是数据的低字节位(0x0000001)。

如果 1 被存储在 data所占内存 在那data的低地址中.b 的值将会是 一、是小端模式。

如果 1 被存储在 data所占内存 在那data的高地址中.b 的值将会是 0,是大端模式。

  • 方式二: : 通过将int强制类型转换为char单字节,判断起始存储位置内容的实现。

#include <stdio.h>int main(){    union{      int a;  //4 bytes      char b; //1 byte    } data;      data.a = 1; //占4 bytes,十六进制可以表示为 0x 00 00 00 01    //b因为char型只占1Byte,a因为int型占4Byte    //因此,在联合体data所占内存中,b所占内存等于a所占内存的低地址部分     if(1 == data.b){ ///走case解释a的低字节,取给b,也就是说,a的低字节有联合体所占内存的低地址,符合小端模式的特点      printf("Little_Endian\n");     } else {      printf("Big_Endian\n");     }   return 0;}

说明:

赋值 1 是数据的低字节位(0x0000001)。

如果 1 被存储在 a所占内存 在低地址中,B的值将是 一、是小端模式。

如果 1 被存储在 a所占内存 在高地址中,B的值将是 0,是大端模式。

——————————

5>End.PS:网上有很多描述,用MSB和LSB来描述大端和小端。我们必须注意,大端和小端描述字节之间的关系,而MSB、LSB描述了Bit位置之间的关系。字节是存储空间的基本测量单元,因此通过高字节和低字节最直接地理解大端和小端存储。

MSB: Most Significant Bit ------- 最高有效位(指二进制中最高值的比特)

LSB:

当然,大端小端的判断和检测也可以通过MSB/LSB来实现。例如代码如下:

#include <stdio.h>int main(void){union {struct {char a:1; //定义位置为 1 bit[如果您不知道位置是什么,请自己查阅,后续文章也会特别讨论]      } s;char b;} data;<span >data</span><span >.</span><span >b</span> <span >=</span> <span >8</span><span >;</span><span >//8(Decimal) == 1000(Binary),MSB is 1,LSB is 0

///在联合体data所占的内存中,data.s.data所占内存bit等于data.bittb占内存低地址部分的bit if (1 == data.s.a) (///去case解释datatase.b的MSB存储在union所占内存的低地址中,符合大端序的特点 printf(“Big_Endian\n”);} else {printf(“Little_Endian\n”);}return 0;}