C语言中结构体(struct)的详细分解与使用(下)

简介: C语言中结构体(struct)的详细分解与使用(下)

C语言中结构体(struct)的详细分解与使用(中)https://developer.aliyun.com/article/1389335


第八:嵌入式开发中,C语言位结构体用途详解


嵌入式开发中,经常需要表示各种系统状态,位结构体的出现大大方便了我们,尤其是在进行一些硬件层操作和数据通信时。但是在使用位结构体的过程中,是否深入思考一下它的相关属性?是否真正用到它的便利性,来提高系统效率?

下面将进行一些相关实验(这里以项目开发中的实际代码为例):

1. 位结构体类型设计

//data structure except for number structure  
typedef struct symbol_struct  
{  
  uint_32 SYMBOL_TYPE   :5;  //data type,have the affect on "data display type"  
  uint_32 reserved_1    :4;  
  uint_32 SYMBOL_NUMBER  :7;  //effective data number in one element  
  uint_32 SYMBOL_ACTIVE  :1;//symbol active status  
  uint_32 SYMBOL_INDEX   :8;  //data index in norflash,result is related to "xxx_BASE_ADDR"  
  uint_32 reserved_2     :8;  
}SYMBOL_STRUCT, _PTR_ SYMBOL_STRUCT_PTR;


分析:这里定义了一个位结构体类型 SYMBOL_STRUCT,那么用该类型定义的变量都哪些属性呢?

看下面运行结果:

51d5a21a8b76c3686ddfaba2f1124ad2.jpg

WORDS是定义的另一个外层类型定义封装,可以把它当作变量来看待。WORDS变量里前5个数据域的地址都是0x1ffff082c,而reserved_2的地址0x1fff0830,紧接着的PressureState变量是0x1fff0834。

开始以为:reserved_1 和 SYMBOL_TYPE 不在一个地址上,因为他们 5+4 共9位,超过了1个字节地址,但实际他们共用首地址了;而且reserved_2只定义了8位,竟然实际占用了4个字节(0x1fff0834 - 0x1fff0830),我本来是想让他占用1个字节的。

WORDS整体占了8个字节(0x1fff0834 - 0x1fff082c),设计时分析占用5个字节:

SYMBOL_TYPE  1个;reserved_1  1个;

SYMBOL_NUMBER+SYMBOL_ACTIVE  1个;

SYMBOL_INDEX  1个;reserved_2  1个;

uint_32  reserved_2   : 8; 占用4个字节,估计是uint_32在起作用,而这里写的8位,只是我使用的有效位数,另外24位空闲,如果在下面再定义一个uint_32 reserved_3   : 8,地址也是一样的,都是以uint_32为单位取地址。

同理,上面的5个变量,共用一个地址就不足为奇了。而且有效位的分配不是连续进行的,例如 SYMBOL_TYPE+reserved_1 共9位,超过了一个字节,索性系统就分配两个字节给他们,每人一个;SYMBOL_NUMBER+SYMBOL_ACTIVE 共8位,一个字节就能搞定。


2、修改数据结构,验证上述猜想

//data structure except for number structure  
typedef struct symbol_struct  
{  
  uint_8 SYMBOL_TYPE    :5; //data type,have the affect on "data display type"
  uint_8 reserved_1     :4;  
  uint_8 SYMBOL_NUMBER   :7; //effective data number in one element  
  uint_8 SYMBOL_ACTIVE   :1; //symbol active status  
  uint_8 SYMBOL_INDEX    :8; //data index in norflash,result is related to "xxx_BASE_ADDR"
  uint_8 reserved_2      :8;  
}SYMBOL_STRUCT,_PTR_ SYMBOL_STRUCT_PTR;


地址数据如下:

b739f10edba625d4ec77fdfc2069dbbc.jpg

当换成uint_8后,可以看到地址空间占用大大减小,reserved_2 只占用1个字节(0x1fff069f - 0x1fff069e),其他变量也都符合上面的结论猜想。但是,注意看上面黄色和红色的语句,总感觉有些勉强,那么我又会想,前两个变量数据域是 9 位,那么他们实际上是不是真正的独立呢?虽然在 uint_8 上面他们是不同的地址,在uint_32 的时候是不是也是不同的地址空间呢?


3、分析结构体内部的数据域是否连续,看下图及结果 

9aea79ac5c67700b08a14f1693bcff4e.jpg

本来假设: 由前 2 次试验的结论,一共占用 8 个字节,节空间占用:(2+4)+(4+4)+(2+2+4)+(2+2)+(6)。可是,实际效果并不是想的那样。实际只占用了 4 个字节,系统并没有按照预想的方式,为 RESERVED 变量分配 4 个字节。


分析:

这些数据域,整体相加一共32位,占用4个字节(不考虑数据对齐问题)。而实际确实是占用了4个字节,唯一的原因就是:这些数据域以紧凑的方式链接,没有任何空闲位。实际是不是这样呢?


看下图和结果:

68a8e8efe762fc8ff4086dc1d92b1f88.jpg

这里为了验证是否紧凑链接,用到了一个union数据,后面会讲到用union不会对数据组织方式有任何影响,看实际与上次的一样,也能分析出来。


主要是分析第2和第3个数据域是否紧密链接的。OBJECT_ACTIVE_PRE赋值0b00001111,NUMBER_ACTIVE赋值0b00000101,其他变量都是0,看到WORD数值0b1011111000000。分析WORD数据,可以看到这款MCU还是小端格式(高位数据在高端,低位数据在低端,这里不对大小端进行讨论),断开数据变成(0)10111 11000000,正好是0101+1111,OBJECT_ACTIVE_PRE数据域,跨越了两个字节,并不是刚开始设想的那样。这就印证了上面的紧密链接的结论,也符合数据结果输出。  


4、再次实验,分析数据是否紧密链接,看下图和结果 

fe7422d2d8aa0f7a27a149b0ed4a816a.jpg

可以看到,RESERVED数据域已经不再属于4个地址空间内了(0x1fff0518 - 0x1fff051b),但是他们整体加起来还是32个位域。这说明数据中间肯定有“空隙”存在了,空隙在哪?看一下NUMBER_STATE,如果紧密的话它应该跟NUMBER_ACTIVE在同一个字节地址上,可是他们并不在一块,“空隙”就存在这里。

这两个结构体有什么不一样?数据类型不一致,一个是uint_32,一个是uint_8。

综上所述:数据类型影响的是编译器在分配物理空间时的大小单位,uint_32 是以 4个字节为单位,而后面的位域则是指在已经分配好的物理空间内部再紧凑的方式分配数据位,当物理空间不能满足位域时,那么系统就再次以一定大小单位进行物理空间分配,这个单位就是上面提到的 uint_8 或者 uint_32。

举例:上面 uint_32 时,这些位域不管是不是在一个字节地址上,如果能够紧凑的分配在一个4字节空间大小上,就直接紧凑分配。如果不能则继续分配(总空间超过4字节),则再次以4字节空间分配,并把新的位域建立在新的地址空间上(条目1上的就是)。当 uint_8 时,很明显如果位域不能紧凑的放在一个字节空间上,那么就从新分配新的1字节空间大小,道理是一样的。  


5、结构体组合、共用体组合是否影响上述结论

569750bb84d5a1cb1d12831593a55397.jpg

eac2c23377dd69cb3f8e10d9da0d4a90.jpg

可以看到,系统并没有因为位结构体上面有uint_4的4字节变量或者共用体类型,就改变分配策略把位域都挤到4字节之内,看来他们是没有什么实质性联系的。这里把uint_32改成uint_8,或者把位结构体也替换掉,经我试验证明,都是没有任何影响的。


第九:总结:


1、在操作位结构体时,要关注变量的位域是否在一个变量类型(uint_32或者uint_8)上,判断占用空间大小。

2、除了位域,还要关注变量定义类型,因为编译器空间分配始终是按类型分配的,位域只是指出了有效位(小于类型占用空间),而且如果位域大于类型空间,编译器直接报错(如 uint_8  test  :15,可自行实验)。

3、这两个因素都影响变量占用空间大小,具体可以结合调试窗口,通过地址分配分析判断。

4、最重要的一点:上面的所有结果,都是基于我自己的 CodeWarrior10.2 和MQX3.8 分析出来的,不同的编译环境和操作系统,都可能会有不同的结果;而且即便是环境相同,编译器的配置和优化选项都有可能影响系统处理结果。结论并不重要,主要想告诉大家这一块隐藏陷阱,在以后处理类似问题时,要注意分析避让并掌握方法。

目录
相关文章
|
6天前
|
存储 C语言
如何在 C 语言中实现结构体的深拷贝
在C语言中实现结构体的深拷贝,需要手动分配内存并逐个复制成员变量,确保新结构体与原结构体完全独立,避免浅拷贝导致的数据共享问题。具体方法包括使用 `malloc` 分配内存和 `memcpy` 或手动赋值。
22 10
|
5天前
|
存储 大数据 编译器
C语言:结构体对齐规则
C语言中,结构体对齐规则是指编译器为了提高数据访问效率,会根据成员变量的类型对结构体中的成员进行内存对齐。通常遵循编译器默认的对齐方式或使用特定的对齐指令来优化结构体布局,以减少内存浪费并提升性能。
|
10天前
|
编译器 C语言
共用体和结构体在 C 语言中的优先级是怎样的
在C语言中,共用体(union)和结构体(struct)的优先级相同,它们都是用户自定义的数据类型,用于组合不同类型的数据。但是,共用体中的所有成员共享同一段内存,而结构体中的成员各自占用独立的内存空间。
|
10天前
|
存储 C语言
C语言:结构体与共用体的区别
C语言中,结构体(struct)和共用体(union)都用于组合不同类型的数据,但使用方式不同。结构体为每个成员分配独立的内存空间,而共用体的所有成员共享同一段内存,节省空间但需谨慎使用。
|
14天前
|
编译器 C语言 C++
C语言结构体
C语言结构体
17 5
|
15天前
|
编译器 Linux C语言
C语言 之 结构体超详细总结
C语言 之 结构体超详细总结
12 0
|
20天前
|
存储 编译器 Linux
深入C语言:探索结构体的奥秘
深入C语言:探索结构体的奥秘
|
19天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
30 3
|
存储 C语言
【C语言】学习笔记8——结构struct(1)
1. 先看个例子 #include #include #define MAXTITL 41 #define MAXAUTL 31 struct book { /*结构模板,标记是 book */ char title[MAXTITL...
1135 0
|
存储 C语言
【C语言】学习笔记9——结构struct(2)
1.如果使用 malloc() 分配内存并使用指针存储该地址,那么在结构中使用指针处理字符串就会显得比较合理。 #include #include // 提供strcpy()、strlen() 的原型 #include // 提供malloc()、free() 的原型...
1042 0