漫谈C语言结构体

2019-07-21 04:13发布

漫谈C语言结构体   相信大家对于结构体都不陌生。在此,分享出本人对C语言结构体的学习心得。如果你发现这个总结中有你以前所未掌握的,那本文也算是有点价值了。当然,水平有限,若发现不足之处恳请指出。代码文件test.c我放在下面。 在此,我会围绕以下2个问题来分析和应用C语言结构体:   1. C语言中的结构体有何作用 2. 结构体成员变量内存对齐有何讲究(重点)

对于一些概念的说明,我就不把C语言教材上的定义搬上来。我们坐下来慢慢聊吧。

==============================================================================================================================================
  1. 结构体有何作用

三个月前,教研室里一个学长在华为南京研究院的面试中就遇到这个问题。当然,这只是面试中最基础的问题。如果问你你怎么回答? 我的理解是这样的,C语言中结构体至少有以下三个作用:   (1)有机地组织了对象的属性。

比如,在STM32RTC开发中,我们需要数据来表示日期和时间,这些数据通常是年、月、日、时、分、秒。如果我们不用结构体,那么就需要定义6个变量来表示。这样的话程序的数据结构是松散的,我们的数据结构最好是“高内聚,低耦合”的。所以,用一个结构体来表示更好,无论是从程序的可读性还是可移植性还是可维护性皆是: typedef struct   //公历日期和时间结构体 {     vu16  year;     vu8   month;     vu8   date;     vu8   hour;     vu8   min;     vu8   sec;   }_calendar_obj;    _calendar_obj calendar;  //定义结构体变量   (2)以修改结构体成员变量的方法代替了函数(入口参数)的重新定义。

如果说结构体有机地组织了对象的属性表示结构体“中看”,那么以修改结构体成员变量的方法代替函数(入口参数)的重新定义就表示了结构体“中用”。继续以上面的结构体为例子,我们来分析。假如现在我有如下函数来显示日期和时间:
void DsipDateTime( _calendar_obj  DateTimeVal)

那么我们只要将一个_calendar_obj这个结构体类型的变量作为实参调用DsipDateTime()即可,DsipDateTime()通过DateTimeVal的成变量来实现内容的显示。如果不用结构体,我们很可能需要写这样的一个函数:
void DsipDateTime( vu16 yearvu8 monthvu8 datevu8 hourvu8 minvu8 sec)

显然这样的形参很不可观,数据结构管理起来也很繁琐。如果某个函数的返回值得是一个表示日期和时间的数据,那就更复杂了。这只是一方面。 另一方面,如果用户需要表示日期和时间的数据中还要包含星期(),这个时候,如果之前没有用机构体,那么应该在DsipDateTime()函数中在增加一个形参vu8 week

void DsipDateTime( vu16 yearvu8 monthvu8 datevu8 weekvu8 hourvu8 minvu8 sec)

可见这种方法来传递参数非常繁琐。所以以结构体作为函数的入口参数的好处之一就是 函数的声明void DsipDateTime( _calendar_obj  DateTimeVal)不需要改变,只需要增加结构体的成员变量,然后在函数的内部实现上对calendar.week作相应的处理即可。这样,在程序的修改、维护方面作用显著。 typedef struct   //公历日期和时间结构体 {     vu16  year;     vu8   month;     vu8   date;     vu8  week;     vu8   hour;     vu8   min;     vu8   sec;   }_calendar_obj;    _calendar_obj calendar;  //定义结构体变量       (3)结构体的内存对齐原则可以提高CPU对内存的访问速度(以空间换取时间)
    并且,结构体成员变量的地址可以根据基地址
(以偏移量
offset)计算。我们先来看看下面的一段简单的程序,对于此程序的分析会在第2部分结构体成员变量内存对齐中详细说明。   #include<stdio.h>   int main() {     struct  //声明结构体char_short_long     {         char  c;         short s;         long  l;     }char_short_long;       struct  //声明结构体long_short_char     {         long  l;         short s;         char  c;
    }long_short_char;
      struct  //声明结构体char_long_short     {         char  c;         long  l;         short s;     }char_long_short;       printf("  ");     printf(" Size of char   = %d bytes ",sizeof(char));     printf(" Size of shrot  = %d bytes ",sizeof(short));     printf(" Size of long   = %d bytes ",sizeof(long));     printf("  "); //char_short_long     printf(" Size of char_short_long       = %d bytes ",sizeof(char_short_long));     printf("     Addr of char_short_long.c = 0x%p (10进制:%d) ",&char_short_long.c,&char_short_long.c);     printf("     Addr of char_short_long.s = 0x%p (10进制:%d) ",&char_short_long.s,&char_short_long.s);     printf("     Addr of char_short_long.l = 0x%p (10进制:%d) ",&char_short_long.l,&char_short_long.l);     printf("  ");       printf("  "); //long_short_char     printf(" Size of long_short_char       = %d bytes ",sizeof(long_short_char));     printf("     Addr of long_short_char.l = 0x%p (10进制:%d) ",&long_short_char.l,&long_short_char.l);     printf("     Addr of long_short_char.s = 0x%p (10进制:%d) ",&long_short_char.s,&long_short_char.s);     printf("     Addr of long_short_char.c = 0x%p (10进制:%d) ",&long_short_char.c,&long_short_char.c);     printf("  ");       printf("  "); //char_long_short     printf(" Size of char_long_short       = %d bytes ",sizeof(char_long_short));     printf("     Addr of char_long_short.c = 0x%p (10进制:%d) ",&char_long_short.c,&char_long_short.c);     printf("     Addr of char_long_short.l = 0x%p (10进制:%d) ",&char_long_short.l,&char_long_short.l);     printf("     Addr of char_long_short.s = 0x%p (10进制:%d) ",&char_long_short.s,&char_long_short.s);     printf("  ");     return 0; }   程序的运行结果如下(注意:括号内的数据是成员变量的地址的十进制形式)


  2. 结构体成员变量内存对齐

首先,我们来分析一下上面程序的运行结果。前三行说明在我的程序中,char型占1个字节,short型占2个字节,long型占4个字节。char_short_longlong_short_charchar_long_short是三个结构体成员相同但是成员变量的排列顺序不同。并且从程序的运行结果来看, 

       Size   of   char_short_long    = 8 bytes
    Size of long_short_char = 8 bytes
    Size of char_long_short = 12 bytes  //比前两种情况大4 byte 

    并且,还要注意到,1 byte (char)+
2 byte (short)+ 4 byte (long) = 7 byte,而不是8 byte。

   所以,结构体成员变量的放置顺序影响着结构体所占的内存空间的大小。一个结构体变量所占内存的大小不一定等于其成员变量所占空间之和。如果一个用户程序或者操作系统(比如uC/OS-II)中存在大量结构体变量时,这种内存占用必须要进行优化,也就是说,结构体内部成员变量的排列次序是有讲究的。 结构体成员变量到底是如何存放的呢? 在这里,我就不卖关子了,直接给出如下结论,在没有#pragma pack宏的情况下:   原则1  结构(struct或联合union)的数据成员,第一个数据成员放在offset0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int32位机为4字节,则要从4的整数倍地址开始存储)。 原则2  结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。    *原则3  结构体作为成员时,结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct bb里有charintdouble等元素,那b应该从8的整数倍地址处开始存储,因为sizeof(double) = 8 bytes   这里,我们结合上面的程序来分析(暂时不讨论原则3) 先看看char_short_longlong_short_char这两个结构体,从它们的成员变量的地址可以看出来,这两个结构体符合原则1和原则2。注意,在 char_short_long的成员变量的地址中, char_short_long.s的地址是1244994,也就是说,1244993是“空的”,只是被“占位”了! 再看看char_long_short这个结构体,char_long_short的地址分布情况如下表:   成员变量 成员变量十六进制地址 成员变量十进制地址 char_long_short.c 0x0012FF2C 1244972 char_long_short.l 0x0012FF30 1244976 char_long_short.s 0x0012FF34 1244980   可见,其内存分布图如下,共12 bytes 地址 1244972 1244973 1244974 1244975 1244976 1244977 1244978 1244979 1244980 1244981 1244982 1244983 成员 .c       .l .s       首先,1244972能被1整除,所以char_long_short.c放在1244972处没有问题(其实,就char型成员变量自身来说,其放在任何地址单元处都没有问题),根据原则1,在之后的1244973~1244975中都没有能被4(因为sizeof(long)=4bytes)整除的,1244976能被4整除,所以char_long_short.l应该放在1244976处,那么同理,最后一个.s(sizeof(short)=2 bytes)是应该放在1244980处。 是不是这样就结束了?不是,还有原则2。根据原则2的要求,char_long_short这个结构体所占的空间大小应该是其占内存空间最大的成员变量的大小的整数倍。如果我们到此就结束了,那么char_long_short所占的内存空间是1244972~1244981共计10bytes,不符合原则2,所以,必须在最后补齐2个 bytes(1244982~1244983) 至此,一个结构体的内存布局完成了。 下面我们按照上述原则,来验证这样的分析是不是正确。按上面的分析,地址单元124497312449741244975以及12449821244983都是空的(至少char_long_short未用到,只是“占位”了)。如果我们的分析是正确的,那么,定义这样一个结构体,其所占内存也应该是12 bytes struct  //声明结构体char_long_short_new {      char  c;

     char  add1;  //补齐空间
         char  add2;  //补齐空间
         char  add3;  //补齐空间        long  l;      short s;      
         char  add4;  //补齐空间
         char  add5;  //补齐空间   }char_long_short_new;   运行结果如下:


  可见,我们的分析是正确的。至于原则3,大家可以自己编程验证,这里就不再讨论了。
    所以,无论你是在VC6.0还是Keil C51,还是Keil MDK中,当你需要定义一个结构体时,只要你稍微留心
结构体成员变量内存对齐这一现象,就可以在很大程度上节约MCURAM。这一点不仅仅应用于实际编程,在很多大型公司,比如IBM、微软、百度、华为的笔试和面试中,也是常见的。


友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
49条回答
solo
1楼-- · 2019-07-21 07:27
如果这么问我,我肯定不会答。。。稍微有点偏重理论,如果说个问题用结构体的方法去解决应该没问题。说起来结构体的用法还是蛮多,技巧也很多,可以模仿着写一些面向对象思想的东西,例如:我经常用结构体来写菜单,重定义数据结构什么的,属于一次定义多次受用的那种。同时结合指针可以简化大量代码,使程序更加紧凑。

    for(i=0;i<16;i++)//根据通道复制通道01的数据
    {
          (*input[channel].c_data.data) = (*input[0].c_data.data);
     } 
这几行代码操作了4个通道,每个通道16个变量,也就是说根据条件可以自由操作64个变量。其中变量的定义是自由的,结构体通过指针引用过来,对处理多通道多数据,结构体不失为一个好方法。
阿拓
2楼-- · 2019-07-21 08:55

struct  //声明结构体char_long_short_new

{

     char  c;

     char  add1;  //补齐空间
         char  add2;  //补齐空间
         char  add3;  //补齐空间

 

     long  l;

     short s;

     
         char  add4;  //补齐空间
         char  add5;  //补齐空间

 

}char_long_short_new;

 

运行结果不一定是12吧,比如下表所示:
1   2   3   4    1   2   3   4    1   2   3   4    1   2   3   4    
.c ad1 ad2 ad3               l 要 占4    个   sz占两个 ad4 ad5

这样就要占16byte了吧

styleno1
3楼-- · 2019-07-21 12:41
回复【楼主位】shr5791:
---------------------------------
结论没问题,稍有偏颇,从编译结果逆推语法是不太合适的。
745021926
4楼-- · 2019-07-21 17:01
顶,内存对齐,很实用
正点原子
5楼-- · 2019-07-21 19:04
 精彩回答 2  元偷偷看……
gampt
6楼-- · 2019-07-21 23:52
学习了,RAM不是ARM,不过都应该能明白

一周热门 更多>