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

文章目录

  • 概述
  • 大小端分歧的类比
  • 为什么要关注字节序
    • 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.mzph.cn/bicheng/52341.shtml

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

相关文章

【威锋网-注册安全分析报告-无验证方式导致安全隐患】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

深度解析:常见本地大模型知识库工具部署、微调与对比,个人高效选型指南!

常见本地大模型知识库工具 LLM knowledge base 这里先盘点一下最近比较火爆的几个工具&#xff0c;将从知识库侧和大模型侧分别介绍。 01 知识库侧 知识库侧主要是指更加偏向于能够直接读取文档并处理大量信息资源&#xff0c;包括文档上传、自动抓取在线文档&#xff0c;…

Linux下进程间的通信--信号

信号的概念&#xff1a; 在Linux操作系统中&#xff0c;信号是一种软件中断机制&#xff0c;用于通知进程某个事件已经发生。信号是Linux进程间通信&#xff08;IPC&#xff09;的一种简单且快速的方式&#xff0c;它可以用来处理各种异步事件&#xff0c;如用户输入、硬件事件…

Redis (day 3)

一、通过jedis连接数据库 1.首先导入依赖 <!-- https://mvnrepository.com/artifact/redis.clients/jedis --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>5.1.0</version></de…

(第三十三天)

1. 设置主从从 mysql57 服务器 &#xff08; 1 &#xff09;配置主数据库 [rootmsater_5 ~] # systemctl stop filewalld [rootmsater_5 ~] # setenforce 0 [rootmsater_5 ~] # systemctl disable filewalld [rootmsater_5 ~] # ls anaconda-ks.cfg mysql-5.7.44-linux-g…

【Unity】通用GM QA工具 运行时数值修改 命令行 测试工具

GM工具使用: GM工具通常用于游戏运行时修改数值(加钱/血量)、解锁关卡等&#xff0c;用于快速无死角测试游戏。一个通用型GM工具对于游戏项目是非常实用且必要的&#xff0c;但通用不能向易用妥协&#xff0c;纯命令行GM门槛太高&#xff0c;对QA不友好。 这类运行时命令行工具…

进程的创建、终止

目录 前言1. 进程创建2. 进程终止3. exit && _exit 的异同3.1 相同点3.2 不同点 前言 紧接着进程地址空间之后&#xff0c;我们这篇文章开始谈论进程控制相关的内容&#xff0c;其中包括进程是如何创建的&#xff0c;进程终止的几种情况&#xff0c;以及进程异常终止的…

数学建模学习(115):主成分分析(PCA)与Python实践

文章目录 一.主成分分析简介1.1 数学背景与维度诅咒1.2 PCA的定义与应用二.协方差矩阵——特征值和特征向量三.如何为数据集选择主成分数量四.特征提取方法五.LDA——与PCA的区别六.PCA的应用七.PCA在异常检测中的应用八.总结一.主成分分析简介 1.1 数学背景与维度诅咒 主成成…

TOP10漏洞原理

## 本人为学习网安不久的新人&#xff0c;记一次学习笔记&#xff0c;有缺陷或者表述不对的地方欢迎大家指出&#xff0c;感谢&#xff01; ## 1、sql注入&#xff1a;web应用程序对用户输入的数据没有进行过滤&#xff0c;或者过滤不严&#xff0c;就把sql语句拼接进数据库…

Mac电脑遇到DNS解析失败,ip可以访问,域名无法访问

当Mac电脑遇到DNS解析失败的问题时&#xff0c;可以尝试以下几个解决方法‌&#xff1a; 1.检查网络连接‌&#xff1a;确保Mac已连接到可用的网络&#xff0c;并且网络连接正常。可以尝试重新连接Wi-Fi或使用有线连接来排除网络问题。 2.清除DNS缓存‌&#xff1a;打开终端应…

docker容器基本命令、docker进入容器的指令、容器的备份、镜像底层原理、使用commit命令制造镜像、将镜像推送到阿里云镜像仓库与私服仓库

除了exit 还有 ctrlpq exit退出停止 ctrlpq 退出不停止 将本地镜像推到阿里云 登入阿里云 容器镜像服务 实力列表 镜像仓库 创建镜像仓库 安装里面步骤来 这里192.168.10.145这部分用自己ifconfig地址

【Android 远程数据库操作】

按正常情况下&#xff0c;前端不应该直接进行远程数据库操作&#xff0c;这不是一个明智的方式&#xff0c;应该是后端提供对应接口来处理&#xff0c;奈何公司各方面原因需要前端这样做。 对此&#xff0c;我对远程数据库操作做了总结&#xff0c;便于自己复盘&#xff0c;同…

python绘制爱心代码

效果展示 完整代码 Python中绘制爱心的代码可以通过多种方式实现&#xff0c;高级的爱心代码通常指的是使用较复杂的算法或者图形库来生成更加精致的爱心图形。下面是一个使用Python的Turtle模块来绘制爱心的示例代码&#xff1a; import turtledef draw_love():turtle.speed…

[Other]-安装ruby、ascli、ascp

最近新接到这样一个需求&#xff0c;将生物原始数据上传到某中心&#xff0c;其中用到ascp命令&#xff0c;阴差阳错的装了ruby、ascli&#xff0c;这里就都一并介绍下安装方式&#xff0c;由于服务器老旧默认安装时ruby2.0&#xff0c;又 升级到2.7等引发的一系列问题&#xf…

XSS-DOM

文章目录 源码SVG标签Dom-Clobbringtostring 源码 <script>const data decodeURIComponent(location.hash.substr(1));;const root document.createElement(div);root.innerHTML data;// 这里模拟了XSS过滤的过程&#xff0c;方法是移除所有属性&#xff0c;sanitize…

AI工具革新:国内外设计艺术的融合

在人工智能的浪潮中&#xff0c;全球的创新者和开发者们推出了一系列令人惊叹的工具&#xff0c;它们正以前所未有的速度改变着我们的工作、学习和生活方式。从图像生成到语言处理&#xff0c;从数据分析到自动化设计&#xff0c;AI 作图工具展示了其强大的能力&#xff0c;帮助…

DRF——Filter条件搜索模块

文章目录 条件搜索自定义Filter第三方Filter内置Filter 条件搜索 如果某个API需要传递一些条件进行搜索&#xff0c;其实就在是URL后面通过GET传参即可&#xff0c;例如&#xff1a; /api/users?age19&category12在drf中也有相应组件可以支持条件搜索。 自定义Filter …

面试题详解

前言&#xff1a;这一期我们专门来巩固所学知识&#xff0c;同时见识一些面试题。对知识做出一个总结。 1 不创建临时变量交换两个整数 . 第一种方法 #include<stdio.h> int main() {int a 0;int b 0;scanf("%d %d", &a, &b);printf("交换前…

神经网络算法 - 一文搞懂BERT(基于Transformer的双向编码器)

本文将从BERT的本质、BERT的原理、BERT的应用三个方面&#xff0c;带您一文搞懂Bidirectional Encoder Representations from Transformers | BERT。 Google BERT BERT架构&#xff1a; 一种基于多层Transformer编码器的预训练语言模型&#xff0c;通过结合Tokenization、多种E…

Java基于数据库、乐观锁、悲观锁、Redis、Zookeeper分布式锁的简单案例实现(保姆级教程)

1. 分布式锁的定义 分布式锁是一种在分布式系统中用来协调多个进程或线程对共享资源进行访问的机制。它确保在分布式环境下&#xff0c;多个节点&#xff08;如不同的服务器或进程&#xff09;不会同时访问同一个共享资源&#xff0c;从而避免数据不一致、资源竞争等问题。 2…