字节序与位域

目录

前言

1. 字节序

1.1 什么是字节序?

1.2 内存中的存储方式

2. 位域

2.1 什么是位域

2.2 内存中的存储方式

2.3 实际结果与分析结果对比

2.4 数据在大小端机器下的转换


前言

本文首先介绍了字节序的种类以及各类字节序在内存中的存储方式,然后介绍了结构体中的位域申明方法和数据存储方式,然后位域数据的实际存储结果和分析结果进行内存数据比对,最后给出了大端机器和小端机器间的数据转换方法。


1. 字节序

1.1 什么是字节序?

字节序是指多字节数据在内存中的存放顺序。

字节序(Byte Order)分为大端字节序(Big-Endian)小端字节序(Little-Endian)
在处理多字节数据时,以字节为单位,大端序指的是数据高位字节存储在低位地址,数据低位字节存储在高位地址,即数据表示和数据存储按字节反序排列。小端序指的是数据高位字节存储在高位地址,低位字节存储在低位地址,即数据表示和数据存储按字节正序排列。

1.2 内存中的存储方式

对于整型数据0x12345678,大端序和小端序在内存中的存储方式如下表如示(假设内存起始地址为0x10000000)。

字节序与地址映射关系
内存地址0x10000000  0x10000001  0x10000002  0x10000003  
大端序0x120x340x560x78
小端序0x780x560x340x12

2. 位域

2.1 什么是位域

位域是一种数据结构的定义方式,可以将多个字段(成员)组合在一起,每个字段使用特定的比特位数来存储数据。位域可以用来节省内存空间,特别适用于存储一些开关类型的数据。

位域的定义可以通过使用特定的语法来指定每个字段所占用的位数。例如,下面的代码定义了一个具有三个位域字段的结构体:

struct Flags {
    unsigned int flag1: 1;
    unsigned int flag2: 2;
    unsigned int flag3: 3;
};

在这个例子中,flag1占用了1位,flag2占用了2位,flag3占用了3位。每个字段都可以存储的值的范围取决于它所占用的位数。

使用位域可以方便地读取和修改特定的位字段,如下所示:

struct Flags flags;
flags.flag1 = 1;
flags.flag2 = 2;
flags.flag3 = 4;

这样我们就可以通过访问flags.flag1flags.flag2flags.flag3来读取和修改位域的值。

需要注意的是,位域的使用也有一些限制。因为位域的存储顺序和大小取决于编译器的实现,所以在不同的编译器和不同的机器上可能会有不同的结果。此外,位域的总位数不能超过字段的数据类型的大小。

2.2 内存中的存储方式

定义了位域的数据结构不是简单的按照大端序和小端序的存储方式进行有效存储。接下来通过实例来解析位域这种数据结构在内存中的存储方式,测试平台如下所示。

操作系统Window 11
编译器MS Visual Studio 2022

首先,我们定义如下的结构体ST_BIT_FIELD,其中包含一个unsigned int型数据的位域成员和一个unsigned short型数据的位域成员,共6字节数据。

typedef struct _ST_BIT_FIELD
{
	// int
	unsigned int       ulPartA : 3;
	unsigned int       ulPartB : 3;
	unsigned int       ulPartC : 8;
	unsigned int       ulPartD : 8;
	unsigned int       ulPartE : 10;

	// short
	unsigned short     usPartA : 4;
	unsigned short     usPartB : 6;
	unsigned short     usPartC : 6;
}ST_BIT_FIELD;

对该结构体进行如下的赋值操作。

ST_BIT_FIELD* stField = new ST_BIT_FIELD;
memset(stField, 0, sizeof(ST_BIT_FIELD));

stField->ulPartA = 1;
stField->ulPartB = 2;
stField->ulPartC = 3;
stField->ulPartD = 4;
stField->ulPartE = 5;

stField->usPartA = 1;
stField->usPartB = 2;
stField->usPartC = 3;

位域只有在设置的基本数据类型长度内才有意义,因此上述内存中的内容可以分为两个部分,第一部分为4字节的unsigned int型变量,第二部分为2字节的unsigned short型变量。

针对于代码中的赋值结果,可以得出每个基本数据类型下的每个位域数值的二进制表现形式,如下表所示。

unsigned int数据组成
ulPartAulPartBulPartCulPartDulPartE
0b0010b0100b000000110b000001000b0000000101
unsigned short数据组成
usPartAusPartBusPartC
0b00010b0000100b000011

位域数据又是如何组成完整数据的呢?又是如何在内存中存储的呢?

同一数据类型下定义的所有位域成员,遵循按顺序从低地址向高地址依次相邻存储的规则。

位域是按照比特位存放数据的,那么比特序又遵循什么原则呢?

比特序与字节序保持相同的存储规则,即大端序为数据高比特位对应内存低地址,小端序为数据高比特位对应内存高地址。

根据上述两个规则,小端序下位域的存储方式总结如下:

  1. 提取定义了位域的基本数据类型长度大小,以unsigned int为例。
  2. 按照大小端规则,将内存数据按照数据表现形式排列,如下表1所示。
  3. 按照位域成员按照定义顺序,从低地址向高地址依次存储的规则进行存储。
  4. 按照地址排列顺序将数据存储到内存中,内存中结果为0xD1004101,结果如下表所示。
表1 处理步骤(小端序)
内存地址0x100000000x100000010x100000020x10000003
按照数据表示形式排列后的地址顺序0x100000030x100000020x100000010x10000000
按比特位排列的顺序BIT31-BIT0
小端序

ulPartA(BIT31-BIT22): 0b0000000101

ulPartB(BIT21-BIT14): 0b00000100

ulPartC(BIT13-BIT6): 0b00000011

ulPartD(BIT5-BIT3): 0b010

ulPartE(BIT2-BIT0): 0b001

组合后:0b00000001 01000001 00000000 11010001

十六进制表示:0x01 41 00 D1

表2 小端序内存存储结果
内存0x100000000x100000010x100000020x10000003
小端序0xD10x000x410x01

大端序同上,具体步骤见表3和表4。

表3 处理步骤(大端序)
内存地址0x100000000x100000010x100000020x10000003
按照数据表示形式排列后的地址0x100000000x100000010x100000020x10000003
比特位按地址顺序排列BIT0-BIT31
小端序

ulPartA(BIT22-BIT31): 0b0000000101

ulPartB(BIT14-BIT21): 0b00000100

ulPartC(BIT6-BIT13): 0b00000011

ulPartD(BIT3-BIT5): 0b010

ulPartE(BIT0-BIT2): 0b001

组合后:0b00101000 00001100 00010000 00000101

十六进制表示:0x28 0C 10 05

表4 大端序内存存储结果
内存0x100000000x100000010x100000020x10000003
小端序0x280x0C0x100x05

2.3 实际结果与分析结果对比

在visual studio 2022下,我们得到如下图所示的内存存储结果,存储结果为0xd1004101210c,总共6字节。(此处,由于字节对齐,结构体ST_BIT_FIELD正常情况应该占8字节,作者在测试时设置了1字节对齐,因此只占用6字节,感兴趣的朋友可以搜索字节对齐相关知识)

ef70fbc438b8499aa4dfccd4f1e0a054.png

第一部分(unsigned int)第二部分(unsigned short)
0xd10041010x210c

从图中可以看出,第一部分对于unsigned int数据类型的内存结果与分析结果一致,说明2.2节中分析结果的正确性。

2.4 数据在大小端机器下的转换

对于上述提到的结构体ST_BIT_FIELD,为了确保大端序和小端序下对于所有的位域成员和基本成员得到相同的取值结果,需要对结构体定义做些许处理。

处理方法:

步骤1: 将隶属于同一数据类型的所有位域成员按照小端序下的声明顺序进行反序申明即可,反之亦可,如下图所示。

步骤2:在数据发送或者接收时,还需要按照基本数据类型的大小端转换方式进行字节的倒序操作,该操作在发送端或者接收端处理都可以,一般建议在发送端处理。

// 小端序下的定义
typedef struct _ST_BIT_FIELD
{
	// int
	unsigned int       ulPartA : 3;
	unsigned int       ulPartB : 3;
	unsigned int       ulPartC : 8;
	unsigned int       ulPartD : 8;
	unsigned int       ulPartE : 10;

	// short
	unsigned short     usPartA : 4;
	unsigned short     usPartB : 6;
	unsigned short     usPartC : 6;
}ST_BIT_FIELD;

// 大端序下的定义
typedef struct _ST_BIT_FIELD_BIGEND
{
	// int
	unsigned int       ulPartE : 10;
	unsigned int       ulPartD : 8;
    unsigned int       ulPartC : 8;
	unsigned int       ulPartB : 3;
    unsigned int       ulPartA : 3;
	

	// short
	unsigned short     usPartC : 6;
	unsigned short     usPartB : 6;
	unsigned short     usPartA : 4;
}ST_BIT_FIELD_BIGEND;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值