嵌入式裸机&RTOS开发杂谈系列
第四章 详解处理器的大小端存储,及数据传输中的大小端
文章目录
前言
为什么会有大小端?分析:在32位的处理器中一个int数据类型的变量有4个字节,比如 int32_t = 0xaabbccdd,那么问题来了,在内存中它是这么存储的了?是aa在低地址还是dd的在低地址了?其实都可以,不同的架构采用的存储方法不一样,如此便引入了大小端的概念。本质:大小端是对多字节数据类型的概念;单字节数据类型我就一个字节就存在低地址就好了,无所谓大小端。
一、大小端存储机制
1.大端模式(Big-Endian)
大端模式,也被称为大字节序。在这种模式下,数据的高位字节存储在低地址,低位字节存储在高地址。这就好比我们人类书写数字的习惯,从左到右,高位在前,低位在后。例如,对于一个十六进制数 0x12345678,它由4个字节组成,分别是 0x12、0x34、0x56 和 0x78。在大端模式下,存储顺序为 0x12 0x34 0x56 0x78。从内存地址的角度来看,低地址处存储的是高位字节 0x12,随着地址的升高,依次存储 0x34、0x56 和 0x78。这种存储方式符合人类的直观认知,在一些需要人类直接读取和处理数据的场景中具有一定的优势。
为了更好地理解大端模式,我们可以想象一个书架,每一层代表一个内存地址,而书本则代表字节数据。当我们按照大端模式摆放书本时,会将重要的信息(高位字节)放在书架的底层(低地址),随着层数的增加,依次放置次要的信息(低位字节)。这样,当我们从书架底部开始读取书本时,就能按照我们习惯的顺序获取数据。
2.小端模式(Little-Endian)
与大端模式相反,小端模式(Little-Endian)下,数据的低位字节存储在低地址,高位字节存储在高地址。同样以 0x12345678 为例,在小端模式下,存储顺序为 0x78 0x56 0x34 0x12。这意味着在低地址处存储的是低位字节 0x78,而高位字节 0x12 则存储在高地址处。小端模式在x86/ARM等常见的处理器架构中被广泛使用。
我们依然以书架为例来理解小端模式。在小端模式下,我们会将不太重要的信息(低位字节)放在书架的底层(低地址),而重要的信息(高位字节)则放在书架的上层(高地址)。这种存储方式虽然与人类的书写习惯不同,但在计算机的处理过程中却有着独特的优势。例如,在进行数据的加法、减法等运算时,小端模式可以更方便地处理低位字节,提高运算效率。
二、数据传输中的大小端问题
当一台小端机器需要向网络发送数据时,它必须先将数据从本机的小端模式转换为大端模式。这是因为网络协议规定了数据在网络中传输时必须采用大端模式,只有这样,接收方才能正确地解析数据。例如,一台采用x86架构的计算机(小端模式)要向另一台计算机发送一个32位的整数 0x12345678,在发送之前,它需要将这个数据转换为大端模式 0x12 0x34 0x56 0x78 再进行发送。
在接收数据时,小端机器又需要将接收到的大端模式数据转换回小端模式,以便在本机上进行正确的处理。例如,当这台x86计算机接收到一个来自网络的32位整数数据时,它会先将数据从大端模式转换为小端模式,然后再进行后续的处理。这个转换过程就像是一场翻译工作,确保数据在不同的“语言环境”(端模式)之间能够正确地交流。
网络协议强制使用大端字节序的原因主要是为了保证数据的一致性和兼容性。不同的计算机可能采用不同的端模式,如果没有统一的标准,数据在传输过程中就会出现混乱。例如,一个小端机器发送的数据在另一个大端机器上可能会被错误地解析,导致数据的错误处理。通过统一采用大端字节序,网络协议为不同端模式的计算机之间搭建了一座沟通的桥梁,使得数据能够在网络中准确地传输和共享。
三、示例代码(C语言)
1.判断当前系统端模式
代码如下(示例):在C语言中,我们可以通过一段简单的代码来判断当前系统的端模式是大端还是小端。这段代码利用了联合体(union)的特性,联合体是一种特殊的数据类型,它的所有成员共享同一块内存空间
#include <stdio.h>
int check_endian() {
union {
int i;
char c[sizeof(int)];
} u;
u.i = 1;
return (u.c[0] == 1); // 返回1为小端,0为大端
}
int main() {
printf("当前系统为:%s\n", check_endian() ? "小端" : "大端");
return 0;
}
2.大小端转换函数
代码如下(示例):在实际的开发中,我们经常需要进行大小端的转换,特别是在处理网络传输数据时。下面是一段C语言代码,实现了32位数据的大小端转换以及模拟网络传输的场景。
#include <stdint.h>
// 32位数据转换
uint32_t swap_endian32(uint32_t value) {
return ((value >> 24) & 0xFF) | ((value >> 8) & 0xFF00) |
((value << 8) & 0xFF0000) | ((value << 24) & 0xFF000000);
}
// 模拟网络传输场景
void send_network_data(uint32_t data) {
uint32_t net_data = swap_endian32(data); // 转大端
// 发送net_data到网络...
}
// 接收数据示例
void receive_network_data(uint32_t net_data) {
uint32_t host_data = swap_endian32(net_data); // 转回主机端模式
// 使用host_data...
}