存储与传输/大小端字节序的概念、决定因素、给编程带来的困扰

文章目录

  • 概述
  • 大小端分歧的类比
  • 为什么要关注字节序
    • NET网络字节序
    • 什么时候必须转换字节序
    • 大小端字节序哪个优秀
    • 判断系统字节序类型
    • 字节序类型转换
    • 大小端内存监视和调试
  • 谁决定了大小端模式
    • CPU架构决定大小端
    • 操作系统影响大小端?
    • 编译器也影响大小端?
    • 可配置芯片的大小端模式?
    • ARM 支持大端字节序?
    • 寻一个大端字节序环境
  • 权威文献对字节序的讨论
  • 大小端字节序困扰了编程
    • 网络字节序和转换
    • 结构体的字段顺序
    • 结构体字节对齐和填充规则
    • 数组的大小端问题
    • 位段结构的大小端问题
  • 晚安

概述

本文回答了什么是字节序,什么是大端字节序、小端字节序、大和小怎么理解,大端和小端的存储特性是由什么来决定的,编程中的大小端问题是在怎样的情景下产生的,结合在小端和大端真实环境下的分别实践,分析大小端字节序概念在与网络字节序、结构数据字节排列、位域、字节对齐操作等概念交叉后造成的疑惑。

@History /2022
草稿压了5年,最近有时间缝缝补补,有此文章。本文主要围绕如下几个问题展开:
1、 何为大端字节序和小端字节序? 何为大,何为小 ?
2、大端小端与CPU架构/OS有关? 常见CPU/OS/ARM,它们是大端还是小端 ?
3、谈及大小端问题时,其对结构体数据、位域数据、字节对齐等情况下的存储规律有什么影响?
4、什么是网络字节序和主机字节序,什么场景下必须要进行字节序转换操作 ?
5、如何验证本机字节序类型 ?如何找到或搭建不太常见的大端字节序环境!
6、CAN通信协议中约定的是传输位序,此时还要考虑字节序吗?

大小端分歧的类比

在好多年里,我都记不牢靠大端序和小端序的概念定义。本节从大端和小端字节序名字的由来谈起,帮助理解记忆。

大小端名字的由来
我们已经大抵知道,大端(Big-Endian)和小端(Little-Endian)这两个术语的由来与计算机中多字节数据类型的存储方式有关。其实,这两个术语最早可以追溯到英国作家乔纳森·斯威夫特所写的一本小说《格列佛游记》(Gulliver’s Travels)中的一个故事。该书于1726年在英国首次出版便受到读者追捧,一周之内售空。出版几个世纪以来,被翻译成几十种语言,在世界各国广为流传。在中国也是最具影响力的外国文学作品之一,被列为语文新课程标准必读书目。在该书第一卷 ‘利立浦特(小人国)’ 中,讲到,小人国的居民被分为两派:喜欢从圆头(较宽的一端)敲鸡蛋的人被归为“bigendian”,喜欢从尖头(较尖的一端)敲鸡蛋的人被归为“littleendian”,计算机术语 “大端模式”(bigendian)和 “小端模式”(littleendian)就源于此。斯威夫特本意是借以讽刺英国的政党之争,在计算机体系中被引申为数据储存顺序的分歧。
在这里插入图片描述
哈哈,为了加强记忆,我还想继续废话,
小人国大端派系,认为应该从蛋的大端砸开,因为这样可以保证蛋壳上的裂纹最小,蛋液不易溅出。小人国小端派系,则主张从蛋的小端砸开,他们认为这样更加方便,蛋壳碎片不易混入蛋液。
类比如下,
在计算机中,多字节数据类型(如int、short、long整型、浮点数等)的存储需要考虑字节的存储顺序。
小端:
在小端字节序系统中,最低有效字节(LSB)存储在内存的低地址,而最高有效字节(MSB)存储在内存的高地址。这种存储方式类似于将数据从小端打开(鸡蛋),先看到的是最低有效字节。
大端:
在大端字节序系统中,最高有效字节(Most Significant Byte,MSB)存储在内存的低地址,而最低有效字节(Least Significant Byte,LSB)存储在内存的高地址。这种存储方式类似于将数据从大端打开(鸡蛋),先看到的是最高有效字节。
因此,
将一个多字节数据和鸡蛋来比拟,高有效字节(值高位)对应鸡蛋大端,低字节(值低位)对应鸡蛋小端。而与从大头还是小头打开鸡蛋类比的瑟是,先看到高字节还是先看到低字节。按照人类的阅读喜好(从内存低地址到高地址 + 从值的高位到低位),在低地址首先读到的是高有效字节(值高位),则为大端,在内存低地址首先读到的是低字节(值低位),则为小端模式。

进一步梳理下所谓的人类阅读顺序,
以Windows下数据0x1234为例子,谈谈人类阅读顺序的两层意思,
1、从左到右,从高字节到低字节阅读一个多字节数值。
2、从左到右,阅读内存地址,是由低到高的(也是期望的存储顺序)。(以IDE内存监视器为例)
我们通常说,大端模式是符合人类阅读顺序的,也可以说是,此时实际的存储顺序和阅读顺序一致。要注意,如上1和2条描述中,并不是高对高低对低,人类对内存地址的阅读和对数值的阅读习惯,本身就高低交错对应的。无论大端还是小端模式下,人类总是喜欢从左到右的读内存地址和数据字节。对于地址,无论在何种情况下,我们都会先读低地址再读高地址。对于数值,在大端模式下,从左到右,先读到高字节,符合前边说的人类阅读数值的习惯。对于小端模式下,从左到右,先读到低字节,不符合前边说的人类阅读数值的习惯。

在QtCreator内存监视器中显示为例,数值 u16Value=0x1234 的内存地址和字节存储如下,
在这里插入图片描述
如上,低字节数据(0x34)存在低地址(0x65fe8c),高字节数据(0x12)存在高地址(0x65fe8d)。从低地址到高地址,我们首先读到的是低字节,不符合人类阅读顺序,也就是鸡蛋的小端。你记住了木有?

为什么要关注字节序

关注大小端字节序的问题在计算机编程中是非常重要的,可以确保数据在不同平台上的正确解释和处理,保证数据存储和通信的准确性和可靠性。数据存储和数据传输,有时候可以统称为数据交换或数据共享。在进行数据交换时,需要确保数据按照统一的字节顺序进行传输和解释。可以使用协议或标准来规定字节顺序,或者进行适当的字节序转换操作,以确保数据的正确传输和解释,我们常听到的网络字节序就是这么个约定。(大河qu@CSDN)

NET网络字节序

为了确保在不同计算机体系结构之间进行网络通信时,数据的正确传输和解释,TCP/IP协议定义了网络字节序。网络字节序采用大端字节序,也称为网络序,用于规定数据在网络传输过程中的字节顺序。在TCP/IP协议中,定义了一些函数库,如htonl、htons、ntohl、ntohs等,用于进行主机字节序和网络字节序之间的转换。这些函数库提供了一种标准的方法,确保数据在网络中的传输和解释是正确的。

大小端字节序并没有绝对的优劣之分,否则,就不会出现争端了。那么网络字节序为啥要选大端呢?
既然常见的的CPU架构和常见的操作系统都是小端的,那么如此常用的以太网通信协议中为啥要选用大端字节序来做规定呢?难道是搞大端的那些家伙,它们后台硬 ?还是因为我太年轻了,错过了什么历史。
如前文分析,大端字节序是符合人类的阅读顺序的,这可能是一个重要的原因。大端字节序在早期计算机系统中更为常见,当时的一些主要计算机架构,如IBM的System/360和Motorola的68k系列,都采用了大端字节序,因此,在设计以太网协议时,可能是受到了这些早期计算机系统的影响。以太网通信的主要基石,TCP/IP协议中,一些关键字段(如IP地址、端口号)采用了大端字节序。关于该问题,在《网络通信/协议栈内网络字节序与主机字节序的转换实现》中会进一步探讨。

什么时候必须转换字节序

这也是开始对字节序研究的最初的问题,已经在《网络编程/在哪些场景中不必要进行网络字节序装换? Windows Sockets: Byte Ordering》中进行了回答,可以简略总结为两点,
1、要传的信息需要被(中间通用)网络设备正确解释,而不是仅仅传递数据给另一台机器。例如,你可能需要传递端口号和地址,这些信息必须被网络设备正确理解。人家这些通用网络设备都遵循了网络字节序规则,因此你也要遵循。
2、你将要与之通信的程序或服务,你不持有它的源码,也不确定其与你本身环境采用相同的大小端内存字节序。

大小端字节序哪个优秀

大小端字节序的分歧是由不同的设计哲学(哈哈,也可能是设计者的个人喜好有关)、硬件实现和历史原因所导致。大小端本来并不复杂,概念不好记忆、跨平台的数据存储和传输、网络字节序规定等诸多问题混杂一起,困扰就产生了。
似乎如果不考虑异构系统之间的数据交互(通信交互、文件交互等)需求,字节序问题似乎也不会被摆到你的台面上。如果你对字节序和大小端的概念不清楚,在受到网络字节序定义的干扰,那么就难受了。从某些方面来说,你是大端是小端都行,爱咋咋地,只要你不和别人交互,就都不是问题。在没有与其他系统或设备进行数据交互的情况下,大小端的问题不会影响程序的执行,那么大小端的问题就不会成为一个实际的问题。(大河qu@CSDN)

其实从技术上来说,大小端的并无谁有明显的优势,更多的是计算机发展历史的影响。最初设计时,对字节序的选择往往是任意的,但后续技术的发展,需要背上兼容性的包裹。比如ARM从架构层甚至是核心层,几乎都支持大小端字节序双模式,但在芯片级别上相关配置都是只读,通常默认小端模式,这可能主要是为了移植x86程序更方便;还有RISC-V手册描述他们选择了小端序的原因:因为小端字节序,目前在商业上占主导地位(所有x86系统、iOS、Android和Windows for ARM)。当然也有商业竞争的原因,Intel的x86选择小端(可能是为了躲避专利纠纷),最终击败了IBM,导致如今主机领域小端是主流。
后来也有了解到,其实具体的大小端模式,在硬件设计和实现上,甚至在部分指令的执行效率上是有区别的,不过,都不是压倒性的,只是小范围的各有千秋吧,在一些架构手册中可以读到相关的解释,太深奥,我现在研究不了。

判断系统字节序类型

下文给出了3种判断一个系统是大端还是小端的方案,其中的主要思路几乎都是联合结构。

typedef union {unsigned char  u8a;unsigned int   u32b;
} UEndianness;
//用联合结构对象判断
int main() {UEndianness objEndian;memset(&objEndian, 0, sizeof(UEndianness));objEndian.u32b = 1;if (objEndian.u8a == 1)printf( "小端字节序\r\n");else printf("大端字节序\r\n");void *pDebugAcddr = &objEndian;...
}

在这里插入图片描述
如上,在大小端(红色)和大端(绿色)系统上的测试结果。对于4字节的u32b联合字段,其在大小端系统中,其值都是1喽当然,但是其在内存中的字节存储是不一样的(对于这种不一样的分析,后文会详述)。对于1字节的u8a联合字段,无论如何其都是存在最低地址上的(试想,如果不是这样,那么取地址操作岂不是会凌乱,这个后文也是详述),即,小端系统上a值等于1,大端系统上a值等于0。类似的测试代码和内存监视在《存储和传输/探究结构数据(C/C++结构体)在内存中的对齐和填充规则》 等相关文章写过不少,道理也很简单,这里不再赘述。

//用联合结构对象判断
int main() {memset(&objEndian, 0, sizeof(UEndianness));UEndianness objEndian;objEndian.u8a = 1;          //换成了给u8a赋值哈if (objEndian.u32b == 1)    //换成了判断u32b的值哈printf( "小端字节序\r\n");else printf("大端字节序\r\n");...
}

在这里插入图片描述
上述测试与第一种方案几乎一致,也很好理解,但是不如第一种方案的视觉效果好,不够直观。我们重点分析下此时objEndian分别在大小端系统中内存存储。无论在大端还是小端中,联合中的u8a都是存在低地址上的,故此时内存字节一致,这毋庸置疑,实在不明白的可以去文末看看关联文章。从低地址到高地址 01 00 00 00 被解释为32bit整形数据时,在小端系统上,它是0x00000001,在大端系统上它是0x01000000,

//用字符指针判断
int main() {unsigned int x = 1;if (*(unsigned char*)&x == 1)printf("小端字节序\r\n");elsepirntf("大端字节序\r\n"");...
}

上述方案3,虽然没有显式的定义联合结构,但是本质是一样的。

字节序类型转换

在LWIP中我们可以看到关于大小端转换函数的源码如下,在《网络通信/协议栈内网络字节序与主机字节序的转换实现》中有更细致的探究。下文

#if BYTE_ORDER == BIG_ENDIAN          //如果是大端
#define lwip_htons(x) ((u16_t)(x))
#define lwip_ntohs(x) ((u16_t)(x))
...
#else /* BYTE_ORDER != BIG_ENDIAN */ //如果是小端
#ifndef lwip_htons
u16_t lwip_htons(u16_t x);
#endif
#define lwip_ntohs(x) lwip_htons(x)
/* These macros should be calculated by the preprocessor and are usedwith compile-time constants only (so that there is no little-endianoverhead at runtime). */
#define PP_HTONS(x) ((u16_t)((((x) & (u16_t)0x00ffU) << 8) | (((x) & (u16_t)0xff00U) >> 8)))
#define PP_NTOHS(x) PP_HTONS(x)
...
#endif /* BYTE_ORDER == BIG_ENDIAN *///in def.c 
u16_t lwip_htons(u16_t n) {return PP_HTONS(n);
}

如上,以小端系统本地字节序转网络字节序为例,是通过位移运算来实现的,并不难理解。如果是小端4字节数据,转换源码如下,

#define PP_HTONL(x) ((((x) & (u32_t)0x000000ffUL) << 24) | \(((x) & (u32_t)0x0000ff00UL) <<  8) | \(((x) & (u32_t)0x00ff0000UL) >>  8) | \(((x) & (u32_t)0xff000000UL) >> 24))

如在实际编程中遇到大小端转换需求,可以参考上述思路,不再赘述。

大小端内存监视和调试

在IDE中通过内存监视器来查看字节的排序,无疑是最简单实用的方法。当不具备这种条件时,我们只能通过逐个字节打印的手段来进行调试,从低地址开始,逐个地址按字节输出,这与直接通过内存监视器效果一致。

内存字节值的打印输出比较简单,不再附代码,这里重点看看一个整形数bit位的输出,在《语言基础/分析和实践 C&C++ 位域结构数据类型》会有应用和细述。重点强调下,在小端系统下,要输出bit位时,千万不要像输出字节值那样,从低地址到高地址按字节遍历。

//从高bit位到低bit位输出一个无符号整型数的内存
void printBits(unsigned int num) {unsigned int mask = 1 << (sizeof(num) * 8 - 1); // Create a mask with 1 in the most significant bit positionwhile (mask > 0) {if (num & mask) {printf("1");} else {printf("0");}mask >>= 1; // Shift the mask to the right by one bit}printf("\n");
}

谁决定了大小端模式

我们已经知道一些一般性的常识,微软的CPU(X86架构)一般都是小端模式;SUN-Solaris 操作系统在多个版本上都采用了大端字节序;后来基于《存储和传输/寻找大端字节序/有哪款MCU或MPU是真支持大端》的整理,得出了几乎所有ARM在架构和内核层次上是支持大小端可配置,但通常到了芯片级别上,相关配置寄存器位都是只读的,且默认小端模式。那么决定大小端的因素到底有哪些?本来是想细分析决定大小端的各路因素,但奈何能力和时间都有限,只整理了个大概。

CPU架构决定大小端

怎么说呢?这算是最肯定的一个决定因素吧。处理器的设计决定了数据在内存中的存储方式。一些处理器架构,如PowerPC和SPARC,采用大端字节序;而其他处理器架构,如x86采用小端字节序。由于处理器直接控制数据在内存中的存储方式,因此处理器架构决定了系统的字节序。其他因素如操作系统和应用程序可以提供字节序转换的支持,以适应不同的处理器架构和协议要求。

有的地方说,在大部分处理器架构中,寄存器的字节顺序与处理器架构保持一致。但我觉得,寄存器似乎很少有讨论字节序的问题,在寄存器这个范畴下,更多被讨论的是bit位,会讨论位序而不是字节序,如CAN的仲裁段寄存器。另外,ARM的多个系列的架构和内核层都是支持大小端可配置,但最终基于ARM的芯片通常只对用户开放小端,这可能与芯片设计、工艺、成本、兼容实际上的主流小端系统等诸多因素有关。但无论怎样,处理器硬件架构都是决定系统是大小端中哪个的主要和根本因素。

操作系统影响大小端?

在过去,Windows曾经支持运行在一些大端序的CPU架构上,例如PowerPC架构。但随着时间的推移,随着市场需求的变化,Windows对于非x86架构的支持逐渐减少,目前主要集中在x86和ARM架构上。虽然Windows可以在某些大端序的CPU架构上运行,但需要进行适配和修改,以确保正确处理字节序和数据访问。这包括对操作系统内核、驱动程序和应用程序的修改,以确保其在大端架构上正常运行。说白了,就是在OS层上进行了转换和适配。

SUN-Solaris操作系统最初是由SUN Microsystems开发的,后来由Oracle继续维护和支持。它是基于UNIX操作系统的一个分支,主要用于服务器和工作站等高性能计算环境。SUN-Solaris操作系统在多个版本上都采用了大端字节序,这意味着数据的高位字节存储在内存的低地址处,低位字节存储在内存的高地址处。这与网络字节序(大端字节序)一致,因此在处理网络通信时,SUN-Solaris操作系统可以直接使用网络字节序进行数据的传输和解释。虽然SUN-Solaris操作系统本身是大端系统,但它也可以运行在其他体系结构的计算机上,如x86体系结构。在这种情况下,SUN-Solaris操作系统会进行字节序的转换,以确保与所在计算机体系结构的字节序兼容。

我猜哈,作为OS的开发者,肯定是希望自己的系统在大端和小端系统上都可以使用,所以在系统头文件中定义了与处理器大小端相关的宏,不再展开。另外,要注意的一点是,小端系统和小端操作系统的概念侧重点不同。小端系统是指硬件层面的特性,而小端操作系统是指软件层面对硬件特性的支持和应用。

编译器也影响大小端?

一些情况下,据说编译器,也能影响字节序模式,起初我似信非信,后来我信了。如果细想的话,这应该与OS的情况类似,本质上都是一种软层次上的字节序的转换。刚开始工作那会,客户现场的 SUN Solaris 应该就是大端的,可那时候我还没有写下 hello word,O(∩_∩)O 错失了更早称为程序员和探究大小端字节序问题的机会,一拖就是10年,时光如梭啊。

在 ARM论坛 讨论中,看到过如下一句话:For GCC, you can just supply a switch on the command-line and you’re building big-endian binaries. 也就是说会有编译参数,用来直接指定使用大端还是小端字节序,即使你在小端环境中也能编译大端程序。但是我没有去实践过。这也是我临时放弃使用 模拟PowerPC的主要原因,即使我成功构建了PowerPC,如何搭建相关开发环境、如何编译、如何部署可执行程序等,对于现在的我来说,都有可能是挑战。

我所能理解和实践的,只是STC89C51这个8为单片机(单字节哦,本来是没有大小端问题的),在Keil C51编译器的加持下,就成了大端系统。在Keil C51中没有看到大小端字节序的IDE设置,但是如下图 Keil 5 中工程配置的截图,其中包含了字节序配置,
在这里插入图片描述
如上截图,我使用的是STM32F429,其ARM-M4核理论上支持大小端切换,其默认为小端模式,倒是暴露了 AICRC 寄存器的Endian位,但仅是只读的,并不能切换大小端字节序模式,因此编译器上直接灰色啦。

可配置芯片的大小端模式?

记不清从哪里得到的信息,说是i.MX6支持大小端切换配置,不过这倒是开启了我对ARM大小端支持情况的探究。

以i.MX6为例,该系列芯片内部使用的是ARM Cortex-A9处理器核心,而字节序方面,i.MX6芯片默认采用的是小端序(Little-Endian)。虽然i.MX6芯片默认使用的是小端序,但NXP提供了一些特定配置选项,例如通过修改CPSR寄存器中的字节序位来切换为大端序(Big-Endian)模式。我买不起这种芯片,但是我应该可以下载到相关的芯片手册,我们可以搜索CPSR寄存器。
在这里插入图片描述
我选择其中的,i.MX 6SoloLite Processors 型号产品,并下载其参考手册 i.MX 6SoloLite Applications Processor Reference Manual(要注册并登录才能下载),
手册中对芯片的特性描述中,提到,
Support for Big Endian and Little Endian operation modes per access
在这里插入图片描述
在这里插入图片描述
这里 Cortex-A9 Core Platform 是指整体的基于Cortex-A9内核的NXP的平台,而 Cortex-A9 processor 则是指Cortex-A9内核,其是基于ARMv7-A架构的。可能i.MX系列的其他芯片,有支持大小端切换的,我没在细细研究,也没试图去联系销售或客服。

ARM 支持大端字节序?

对与ARM是否支持大小端字节序切换这个问题,我花了不少时间整理了《存储和传输/寻找大端字节序/有哪款MCU或MPU是真支持大端》这篇文章。这里不再细数,总体结论如下,

常用的 STM32F103 系列芯片是基于 Cotex-M3 ARM 内核, ARMv7-M 架构的。常用的 STM32F407系列芯片 系列是基于 Cotex-M4 ARM 内核, ARMv7E-M 架构的。我查询了好多ST的芯片手册,它们在芯片层上即使提及了字节序模式,也都是只读的,默认小端。我咨询了ST官方技术支持,它们也找不到字节的产品中有支持大端字节序的ARM。
通过阅读ARM内核手册和架构手册,可以看到,它们大多都是支持大小端切换的,只是细化到产品级别时,大都只开放出来了小端模式。比较特殊的,在架构层级上,Armv7-R 通过其xPSR和System Control寄存器中的EE位支持动态字节序控制,而 Armv7-M 在复位时是静态配置的。Cortex-A系列处理器支持两种端序配置的系统,由CPSR E位控制,该位允许软件动态切换数据的大小端字节序模式,但内存中的指令始终被视为小端序。

更具体的描述,请参考上文链接。

寻一个大端字节序环境

尝试过如下几种方案,
1、ZYNQ板子,其中包含Cortex-A9处理器,但最终发现其不可配置字节序。
2、采用Qemu虚拟化PowerPC,最终失败。而且即使我能成功安装了Qemu和PowerPC,我可能在IDE集成开发环境搭建、代码编写、编译、调试等环节上栽跟头。
3、继续在ARM中搜索,始终没有找到。
4、在MicroChip 中找到 AT32UC3xxxx系列产品,支持大端,最小开发板倒是不贵,但AVR的调试仿真器逆天的贵,买不起。
5、老实地回到 STC89C51+ Keil C51集成开发环境,稳定,便宜,方便。好多年前也是学习过AT89C51的,当时却不知道它一个8位单片机还可以在编译器的加持下,称为大端字节序开发环境。多时间内没有更好的,就好好珍惜眼前的吧,不对,以后也要好好珍惜。

权威文献对字节序的讨论

在整理 《存储和传输/寻找大端字节序/有哪款MCU或MPU是真支持大端》文章时,我发现在《ARM Cortex-A Series Programmer’s Guide for ARMv7-A Ver4.0 》 中存有不少关于大小端字节序的干货,如在其14.1章中,有比我立意更深的大小端的讨论。文中提到,
大端和小端字节序这两个术语的使用是由丹尼·科恩(Danny Cohen)在他1980年的论文《关于圣战和和平的请求》中引入的,是对《格列佛游记》的一个引用。在内存中查看字节有两种基本方式 - 小端和大端。在大端机器上,内存中对象的最重要字节存储在最低有效(最接近零)的地址处。而在小端机器上,最高有效字节存储在最高地址处。除了端序(endian)外,也可以使用字节顺序(byte-ordering)这个术语。还存在其他类型的端序,特别是中端序和位端序,但我们不会讨论这些。
在这里插入图片描述
要特别指出的一点是,很多人觉得端序(endianness)是令人困惑的,即使绘制示意图来说明也可能暴露出个人的偏见。下图显示了寄存器中的一个32位数值,其通过STR指令被写入0x1000内存地址的过程。然后CPU核心使用LDRB指令执行字节读取。根据您使用的是小端还是大端内存系统,此指令序列将返回不同的值。
在这里插入图片描述
STR指令用于将数据从寄存器写入内存中的指定地址。在ARM汇编语言中,STR指令的语法通常类似于“STR{type} Rn, [Rm, #offset]”,其中Rn是要存储的寄存器,Rm是指示内存地址的寄存器,offset是地址的偏移量。
LDRB指令用于从内存中读取一个字节的数据并加载到寄存器中。在ARM汇编语言中,LDRB指令的语法通常类似于“LDRB{type} Rt, [Rn, #offset]”,其中Rt是目标寄存器,Rn是指示内存地址的寄存器,offset是地址的偏移量。
在这里插入图片描述
LDRB指令和LDR指令在ARM架构处理器上都用于从内存中读取数据。LDRB指令用于从内存中读取一个字节(8位)的数据,并加载到目标寄存器中。LDR指令用于从内存中读取一个字(32bit)或半字(16位)的数据,并加载到目标寄存器中。

大小端字节序困扰了编程

本章中个小节的内容,几乎都是以独立开篇的形式进行了详细整理,故不过多赘述。

网络字节序和转换

在前文章节中也有部分讲述。详细描述可具体可参考 《网络编程/在哪些场景中不必要进行网络字节序转换?》、《网络通信/协议栈内网络字节序与主机字节序的转换实现》等文章。

结构体的字段顺序

可参考《存储和传输/探究结构数据(C/C++结构体)在内存中的对齐和填充规则》和《语言基础/分析和实践 C&C++ 位域结构数据类型》 中的讲述。
无论在小端还是大端字节序的系统中,字段的定义和字段整体的存储区位置之间的相对顺序是一致的。可以认为,字节序(大小端字节序问题)是单个字段内部要讨论的问题,讨论的是单个字段(单/多字节单一数据类型)的字节排序问题(如果结构体包含结构字段,则平铺展开后再分析)。

结构体字节对齐和填充规则

《语言基础/分析和实践 C&C++ 位域结构数据类型》中关于结构(含普通结果和位域结构)成员的存储规律的总结:
0、大小端问题是(多字节)基础数据类型在内存中字节排序的问题,也即所谓大小端的问题其实与结构体本身关系不大,本质上大小端问题是发生在结构内的成员层级上的,只是sizeof结构体字节大小,从顶层体现了这种本质或者说体现了规则的结果。
1、首先要明白的是,结构体的普通成员和位段成员存储规律的探究层次是不同的。对位段成员研究,针对的是单一数据类型(或者说单一普通结构成员)内部位的阅读、bit位与位段的顺序关系。而对普通成员的研究,其研究对象是多个基础数据类型(或者说多个普通成员)之间的存储顺序、Byte字节与普通字段的顺序关系。
方便起见,在后文中,将普通结果成员称为字段,位域结构成员称为位段。在此基础上,上一小段描述可以简化为,位段是在字段的基础上,将一个字段划分为1-n个位段。
2、相同字段定义顺序的结构类型,其对象在小端和大端系统中,字段的存储顺序是一致的,只是多字节字段内部字节的存储顺序不一致。为了字段对齐而填充的字节,也是无论大小端,均填充在真实字段存储地址之后的更高的地址上。
3、位段没有内存地址的概念,它是对一个字段的拆分定义。无论小端和大端系统,结构中先出现的位段,都占据字段的低位,后出现的位段占据字段的高位。为了符合位段语法(不够容纳新位段时,占用新的字段空间)而浪费的bit位,始终出现在字段的低有效位上(下一节有实践代码)。

数组的大小端问题

关于数组的大小端问题,我并没有独立开篇讲述,因为本质上数组也是结构形式的数据,这在<C和指针>书中有描述,可以把从小下标到大下标的数组元素对应看做是结构体中先出现和后出现的字段,不同的是所有字段的基础数据类型是相同的。

从理论上我得出一个结论,使用U8的数组访问一个U32的内存,在大小系统中输出的0-3号下标对应的字节值,是顺序相反的,看一下下边的联合定义,这很容易理解,

union
{unsigned char u8Num[4];unsigned int u32Num;
} UData;

也是通过联合结构,做理论分析。参考具体文章去做详细研究。

位段结构的大小端问题

参考《语言基础/分析和实践 C&C++ 位域结构数据类型》和《CAN应用层协议设计,理解并实践仲裁段位域定义》,不多说了。

晚安

接下来要忙一阵子,没空整理文章了。这应该是近阶段相关系列文章的总结篇,本来是该细细写的,奈何时间不允许,匆匆截稿,后期有时间我会来修缮的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/407486.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何完美备份自己的微博,即使是封号之后

感谢 https://github.com/Chilfish/Weibo-archiver 工具和环境 可以用chrome插件的浏览器&#xff0c; 比如opera安装篡改猴插件安装nodejsvscode 第一步&#xff0c;安装浏览器插件 安装Tampermonkey 然后打开https://raw.githubusercontent.com/Chilfish/Weibo-archiver/m…

MS2232/MS2232T——±20kV ESD 保护、3V-5.5V 供电、真 RS-232 收发器

MS2232/MS2232T 芯片是集成电荷泵、具有 20kV ESD 保护的 RS-232 收发器&#xff0c;包括两路接收器、两路发送器。芯 片满足 TIA/EIA-232 标准&#xff0c;为异步通信控制器和串口连接器 提供通信接口。 芯片采用 3V-5.5V 供电&#xff0c;电荷泵仅用 4 个 0.1-0.47μF 小…

c++ 谷歌的招聘 题解

题目描述 2004 年 7 月,谷歌在硅谷的 101 号公路边竖立了一块巨大的广告牌(如下图)用于招聘 内容超级简单,就是一个以 .com 结尾的网址,而前面的网址是一个 10 位素数,这个素数是自然常数 e 中最早出现的 10 位连续数字 能找出这个素数的人,就可以通过访问谷歌的这个网站进入…

Final Cut Pro Mac(fcpx专业视频剪辑软件) 10.8 中文版安装

Final Cut Pro 是苹果公司为专业视频编辑人士量身打造的非线性编辑软件&#xff0c;以其卓越的性能和深度定制的工具集&#xff0c;在影视制作、电视广播、广告创意等多个领域占据了重要地位。凭借其对高分辨率视频的无缝支持和实时剪辑的流畅体验&#xff0c;Final Cut Pro 成…

【Linux】简易日志工具项目

有些鸟儿是不应该被关在笼子里的&#xff0c; 因为他们的羽毛太丰润了。 当他们飞走&#xff0c;你会由衷地庆贺他获得自由。 --- 肖申克的救赎》--- 从零开始构建简易日志系统 1 日志1.1 什么是日志1.2 日志的意义1.3 为什么要构建自己的日志工具 2 构建自己的日志工具2.1…

gin快速入门

gin 项目地址晓智科技晓智科技晓智文档晓智文档文档源码文档源码 快速体验 func HandlerPong(c *gin.Context) {c.JSON(http.StatusOK, gin.H{"message": "pong",}) }func main() {r : gin.Default()r.GET("/ping", HandlerPong)_ r.Run(&qu…

Windows电脑自建我的世界MC服务器并与好友远程联机游戏教程

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

美股收涨,半导体板块领涨;苹果iPhone出货预测上调

市场概况 在昨夜的交易中&#xff0c;美股三大股指全线收涨。道琼斯工业平均指数上涨1.39%&#xff0c;纳斯达克综合指数上涨2.34%&#xff0c;标准普尔500指数上涨1.61%。值得注意的是&#xff0c;英伟达股票涨幅近4%&#xff0c;推动了科技股的整体表现。美国十年期国债收益…

vue3使用i18n实现国际化

安装vue-i18n npm install vue-i18n创建一个ts文件用于存储各种翻译 globalLang.ts的内容如下&#xff1a; export default {"cn": {},"en": {},"de": {},"es": {},"fr": {},"id": {},"it": {},&quo…

【Linux操作系统】进程间通信(1)

目录 一、认识进程间通信二、匿名管道三、命名管道 一、认识进程间通信 进程间不能直接传递数据&#xff0c;因为进程具有独立性&#xff0c;直接传递会破坏进程的独立性。 进程间通信是什么&#xff1f; 一个进程把自己的数据交给另一个进程。 为什么要有进程间通信&#xf…

DAG计算框架:实现业务编排

文章目录 DAG如何实现DAG计算框架Node的实现Engine的实现Graph的实现具体某个节点如何使用 在工作几年之后&#xff0c;大部分人如果还在继续做着 CRUD的简单重复工作&#xff0c;即使领导不提出对你更高的期望&#xff0c;自身也会感到焦虑吧。学如逆水行舟不进则退&#xff…

怎么整合spring security和JWT

什么是spring security spring security是一个安全框架,它里面有过滤器链,可以多次过滤,其实他可以给前端的cookie传入一个jsessionid,都可以不使用jwt也能完成校验 第一步:导入依赖 <!-- springboot security --> <dependency><groupId>org.springframew…

git错误fatal: Unpack error, check server log

git错误fatal: Unpack error, check server log fatal: Unpack error, check server log error: remote unpack failed: error Missing tree xxxxxxxxxxxxxxxxxx 先执行 git fetch 命令&#xff0c;再push。 git拉取远程所有分支/添加远程仓库_git pull所有分支代码-CSDN博客…

OpenGL-ES 学习(8) ---- FBO

目录 FBO OverViewFBO 优点使用FBO的步骤 FBO OverView FBO(FrameBuffer Object) 指的是帧缓冲对象&#xff0c;实际上是一个可以添加缓冲区容器&#xff0c;可以为其添加纹理或者渲染缓冲区对象(RBO) FBO(FrameBuffer Object) 本身不能用于渲染&#xff0c;只有添加了纹理或者…

【C++ Primer Plus习题】3.6

问题: 解答: #include <iostream> using namespace std;int main() {float miles 0;float gallons 0;float gallon 0;cout << "请输入驱车里程(单位为英里):";cin >> miles;cout << "请输入使用的汽油量(单位为加仑):";cin &g…

【Three.js基础学习】19.Custom models with Blender

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 前言 blender模型资源:【blender】一个汉堡包-CSDN博客 一、代码 import ./style.css import * as THREE from three import { OrbitControls } from three/examples/jsm/co…

CentOS服务器三级等保加固

1.密码周期: vim /etc/login.defs max_days:90 mindays:2 minlen:8 warnage:72.密码复杂度: vim /etc/pam.d/system-auth &#xff1a; password requisite pam_cracklib.so retry3 difok3 minlen8 lcredit-1 dcredit-1 ucredit-1 ocredit-1 【Ubuntu系统->vim /etc/pam.d/c…

【STM32】C语言基础补充

学习过程中发现自己好些需要用到的C语言语法、特征都不太熟练了&#xff0c;特意记录一下&#xff0c;免得忘记了&#xff0c;以后遇到了新的也会继续更新 目录 1 全局变量 2 结构体 3 静态变量 4 memset()函数 5 使用8位的存储器存16位的数 1 全局变量…

C++学习笔记----4、用C++进行程序设计(四)---- 复合关系与继承关系之间的细线

在现实世界只是很容易区分对象之间是复合关系还是继承关系。没有人会说桔子有一个水果--而只能是桔子是一种水果。但是&#xff0c;在代码中&#xff0c;有时候就不是那么清晰了。 设想有一个代表关联数组的假想类&#xff0c;将一个键影射到一个值的数据结构。例如&#xff0c…

【Canvas与艺术】环形橄榄枝纹饰

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>环形橄榄枝(draft2)</title><style type"text/css&quo…