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 分析出来的,不同的编译环境和操作系统,都可能会有不同的结果;而且即便是环境相同,编译器的配置和优化选项都有可能影响系统处理结果。结论并不重要,主要想告诉大家这一块隐藏陷阱,在以后处理类似问题时,要注意分析避让并掌握方法。

目录
相关文章
|
1天前
|
存储 编译器 Linux
C语言之自定义类型_结构体篇(1)
C语言之自定义类型_结构体篇(1)
7 1
|
4天前
|
C语言
C语言多文件编译、结构体、枚举及联合
C语言多文件编译、结构体、枚举及联合
5 0
|
4天前
|
C语言
C语言结构体传值和传址及内存使用
C语言结构体传值和传址及内存使用
6 0
|
4天前
|
C语言
C语言结构体
C语言结构体
4 0
|
6天前
|
编译器 程序员 Linux
C语言:自定义类型——结构体
C语言:自定义类型——结构体
|
28天前
|
存储 编译器 C语言
超全超详细的C语言结构体、位段、枚举、联合体详解
超全超详细的C语言结构体、位段、枚举、联合体详解
|
28天前
|
C语言
C语言基础专题 - 通过案例历学 - 结构体
C语言基础专题 - 通过案例历学 - 结构体
16 0
|
存储 C语言
【C语言】学习笔记8——结构struct(1)
1. 先看个例子 #include #include #define MAXTITL 41 #define MAXAUTL 31 struct book { /*结构模板,标记是 book */ char title[MAXTITL...
1085 0
|
存储 C语言
【C语言】学习笔记9——结构struct(2)
1.如果使用 malloc() 分配内存并使用指针存储该地址,那么在结构中使用指针处理字符串就会显得比较合理。 #include #include // 提供strcpy()、strlen() 的原型 #include // 提供malloc()、free() 的原型...
1001 0
|
1天前
|
安全 编译器 程序员
C语言之字符函数&字符串函数篇(1)
C语言之字符函数&字符串函数篇(1)
5 0