位域 内存 字节序_详解数据在内存中的真实存放次序:位序、字节序、大/小端模式与结构体、位域...

本文详细解释了计算机内存中数据存放的层次,包括位序、字节序和大/小端模式。通过实例展示了小端法和大端法在位和字节层面如何存储数据,以及结构体和位域的内存布局。同时,还探讨了函数参数入栈的顺序和规则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

计算机学科中的很多问题,都是因为概念的抽象模糊,导致理解上的不确定性,增加学习领悟的难度。对于计算机中数据存放次序的问题,很多教材或文章要么含糊其辞,要么凭空飞来结论,让人看的一头雾水。几经周折,结合多篇文章,对内存中数据存放次序终于有了较深入的理解,现记录如下:

一、几个概念:

1.内存中关于数据的存放顺序有三个层次(即三种不同的视角):

1.位序(以一位即bit为单位)-------字节内的bit位与bit位之间的先后顺序

2.字节序(以一字节为单位)-------某类型的字节与字节之间的先后顺序

3.类型序(以若干字节构成的某种类型为单位)-------类型之间的先后顺序

2.内存的扩展方向:

1.递增地址:存放数据时, 自低地址向高地址依次存放, 例如栈

2.递减地址:存放数据时, 自高地址向低地址依次存放, 例如堆

题外话:Linux系统中,用虚拟内存映射到物理内存的方式来管理内存空间。操作系统占用的内存空间称为系统空间, 用户程序占用的空间称为用户空间,在用户空间中,有栈和堆,两块内存区是连在一起 ,但是栈是地址递增,堆是地址。

3.大端模式与小端模式

12345678

左 右

看上面的数字顺序,我们可以从右向左读87654321,也可以从左向右读12345678,那么计算机也一样,在读入这串数据时(即将这串数据装入内存),它也可以按从右向左或从左向右的顺序读入

小端法;即计算机读入这串数据时,是按照从右向左的顺序,依此将87654321存放到内存的

大端法:即计算机读入这串数据时,是按照从左向右的顺序,依此将12345678存放到内存的

弄明白了大小端模式,下面我们来结合前面地址增长方向和位序、字节序来看看,内存中的数据到底是怎么来存放的

二、内存中数据的真实存放次序

1.地址递增情况

(1).位序

以二进制10110101(B)为例:

左------>10110101

小端模式:按位,自右向左依次将1 0 1 0 1 1 0 1存放在0地址单元中的第0位到第7位, 如图1-1:

bc77f9afb1272534cf3952e297ebdb62.png

图1-1

大端模式:按位,自左向右依次将1 0 1 1 0 1 0 1存放在0地址单元中的第0位到第7位, 如图1-2:

49546abd8e46da5b1d4d334ff4017dab.png

图1-2

(2).字节序

以十六进制数0x12345678(12、34、56、78分别各自构成一个字节,由于以字节为单位,故将忽略字节内的位序))为例:

左------->0x12 34 56 78

小端模式:按字节, 自右向左依次将78 56 34 12这四个字节存放在自0地址开始的4个内存单元(每个单元存放一个字节)中, 如图2-1:

5ca839216bb276647caeb42167da4354.png

图2-1

大端模式:按字节, 自左向右依次将12 34 56 78这四个字节存放在自0地址开始的4个内存单元(每个单元存放一个字节)中, 如图2-2:

ff40d5fc04eed34dea5821d5e6a51b55.png

图2-2

(3).综合字节序与位序

【总结】可见无论是位序还是字节序, 小端模式总是自右向左的读取数据,而大端模式总是自左向右的读取数据, 区别仅在于是按位还是按字节,因此,对于任意一个数据而言可以总结如下:

以数据0x12345678(00010010 00110100 01010110 01111000B)为例

小端模式:如图3-1

25f4dcf51c83cf276d0addf69a5eb06b.png

图3-1

大端模式:如图3-2

f71ae46abeb6a57b594299e83f182e67.png

图3-2

2.地址递减情况

小端模式:将数据自右向左依次逐位或逐字节地填入到自高地址开始逐渐递减的内存中

大端模式:将数据自左向右依次逐位或逐字节地填入到自高地址开始逐渐递减的内存中

【ps】:很多教材或文章中,总用低地址,高地址和低字节、高字节做区分和记忆,实际这样很不方便记忆,而且也不易理解,同时,只适用栈这样的地址递增的情况,而不适用于堆这种内存递减的情况,因此本文章弃用这种方法,只按数据书写格式的自左向右与自右向左来区分大端模式与小端模式,方便记忆和理解

三、结构体中各数据类型内存存放次序与位域

1.结构体中各类型元素,按其声明的先后顺序,依次存放

例如:下面的结构体,对于内存递增情况,自低地址向高地址依此存放a b c

struct data

{

int a;

short b;

char c;

};

2.位域:指定变量占用几位bit位

例如:下面结构体中,int型的a只占用3位bit, short型的b占用9位bit, 而char型的c占用4位bit,整个结构体共占用16位,即共占用两个字节

struct data

{

int a : 3; 假设: a为110

short b : 9; b为101011001

char c : 4; c为1101

};

小端模式:如图4-1

3c1f13462057d2b7bbbf845f5c24e062.png

图4-1

大端模式:如图4-2

79902606f73f8fce888e284628a166c9.png

图4-2

四、函数参数入栈

1.一般可以通过宏命令指定参数入栈方式:参数自右向左逐个依次入栈,还是自左向右依次入栈。一般默认参数入栈方式为自右向左

例如:函数add(int a, int b);其参数入栈次序按默认为自右向左依次入栈,即b先入栈,然后a再入栈

2.入栈的位序与字节序

(1).入栈的位序

以long long型十进制数1427195721448768429为例

无论大/小端模式,其入栈的次序都是相同:对于一块内存上的数据:

入栈顺序:最高地址所对应的字节中的最高位最先入栈, 然后是最好字节中的次高位入栈,…, 最后是最低地址所对应的字节中的最低位入栈。

出栈顺序:栈顶字节中的最高位弹出到最低地址所对应的字节中的最低位, …栈底字节中最低位弹出到最高地址所对应的最高位

这样设计入栈顺序,是为了出栈后的顺序与入栈前的顺序保持一致

小端模式与大端模式的区别在于各自不同的字节序与字节内的位序

小端模式(如图5-1):

703a093aad3813305788992890da3361.png

图5-1

大端模式(如图5-2):

c9ab1147e0c3e3da4cff05b6b4990274.png

图5-2

(2).入栈的字节序

由入栈的位序可知,入栈的字节序为:对于一块内存上的数据

入栈顺序:最高地址对应的字节先入栈,然后第二高地址对应的字节入栈,...,最后最低地址对应的字节最后入栈

出栈顺序:栈顶字节弹出到最低地址对应的字节, ..., 栈底字节弹出到最高地址对应的字节

3.入栈字节数限制

一般入栈的字节数为一个机器字长(32位机为4字节), 对于大于4字节的类型,如long long其入栈情况前面已经讨论过,无论是大端模式,还是小端模式,即其最高地址对应的字节先入栈, ..., 最低地址对应的字节最后入栈, 而字节内部最高位最先入栈, 最低位最后入栈

对于小于4字节的类型, 如char和short在入栈时, 将其按低地址对齐,

如果是无符号数, 则在其后补的高地址对应的字节内补0, 例如char型的0x12只占用1个字节, 为将其补全为4个字节,可在其后递增的地址上补上3个字节的0

如果是有符号位, 则在其后补的高地址对应的字节内补符号位, 例如short型的-25只占用2个字节, 为将其补全为4个字节,可在其后递增的地址上补上2个字节的1(-25的符号位是负的,因此补1)

### 小端概念 小端主要描述数据存储在计算机内存中的字节顺。对于多字节数值,例如整数 `0x12345678` 存储在一个 4 字节的空间中: #### 小端模式 - **定义**: 数据的低存放在较低地址置,高存放在较高地址置。 - **示例**: 假设数值 `0x12345678` 被分配到起始地址为 `0x100` 的内存,在小端模式下的排列如下: - 地址 `0x100`: `0x78` - 地址 `0x101`: `0x56` - 地址 `0x102`: `0x34` - 地址 `0x103`: `0x12` 这种存储方式常见于 Intel x86 架构处理器[^5]。 #### 模式 - **定义**: 数据的高存放在较低地址置,低存放在较高地址置。 - **示例**: 同样假设数值 `0x12345678` 被分配到起始地址为 `0x100` 的内存,在模式下的排列如下: - 地址 `0x100`: `0x12` - 地址 `0x101`: `0x34` - 地址 `0x102`: `0x56` - 地址 `0x103`: `0x78` 这种存储方式常见于 Motorola PowerPC 或 SPARC 架构处理器[^5]。 --- ### 解决小端相关的问题 当程运行在不同架构的平台上时,可能会遇到因小端差异而导致的数据不一致问题。以下是几种常见的解决方案及其实现方法: #### 方法一:条件编译检测平台小端并调整布局 可以利用预处理指令来判断当前平台是端还是小端,并据此重新安排结构体成员的顺。例如: ```c #include <stdio.h> #include <asm/byteorder.h> struct bit_order { #if defined(__LITTLE_ENDIAN_BITFIELD) unsigned char a: 2, b: 3, c: 3; #elif defined(__BIG_ENDIAN_BITFIELD) unsigned char c: 3, b: 3, a: 2; #else #error "Please fix <asm/byteorder.h>" #endif }; int main() { unsigned char ch = 0x79; struct bit_order* ptr = (struct bit_order*)&ch; printf("bit_order->a : %u\n", ptr->a); printf("bit_order->b : %u\n", ptr->b); printf("bit_order->c : %u\n", ptr->c); return 0; } ``` 此代码片段展示了如何根据不同平台设置字段的顺以适应各自的字节序特性[^1]。 #### 方法二:使用标准化函数转换字节序 在网络通信场景下,通常会涉及跨设备间传输数据的情况。为了确保接收方能够正确解析发送过来的信息包内容,往往需要统一采用某种固定的字节序形式——即所谓的“网络字节序”,它实际上是一种特定的端表示法。可以通过调用诸如 `htonl()`(将无符号长整形从主机字节序转化为网络字节序)、`ntohl()` (反向操作)之类的库函数完成必要的变换过程[^3]。 --- ### 示例代码展示 下面给出一段简单的 C++ 程用来演示如何判定系统的字节序以及执行相应的转换动作: ```cpp #include <iostream> using namespace std; // 判断系统是否为Little Endian bool is_little_endian(){ short int number = 0x1; return *(char*)(&number) == 1; } void convert_to_network_order(uint32_t& value){ if(is_little_endian()){ uint32_t temp = ((value << 24) & 0xFF000000 ) | ((value << 8) & 0x00FF0000 ) | ((value >> 8) & 0x0000FF00 ) | ((value >> 24) & 0x000000FF ); value = temp; } } int main(){ cout<<"System Byte Order:"<<endl; if(is_little_endian())cout<<"\tLittleEndian"<<endl; else cout<<"\tBigEndian"<<endl; uint32_t num=0x12345678; cout<<hex<<"Original Number:\t"<<num<<dec<<endl; convert_to_network_order(num); cout<<hex<<"After Conversion:\t"<<num<<dec<<endl; return 0; } ``` 该例子首先定义了一个辅助功能用于识别目标环境属于哪种类别的字节序列;接着提供了一种手动实施由本地格式转变为网络兼容版本的方法。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值