相信大家对于结构体都不陌生。在此,分享出本人对C语言结构体的学习心得。如果你发现这个总结中有你以前所未掌握的,那本文也算是有点价值了。当然,水平有限,若发现不足之处恳请指出。代码文件test.c我放在下面。

在此,我会围绕以下2个问题来分析和应用C语言结构体:

1. C语言中的结构体有何作用

2. 结构体成员变量内存对齐有何讲究(重点)

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

==============================================================================================================================================

1. 结构体有何作用

三个月前,教研室里一个学长在华为南京研究院的面试中就遇到这个问题。当然,这只是面试中最基础的问题。如果问你你怎么回答?

我的理解是这样的,C语言中结构体至少有以下三个作用:

(1)有机地组织了对象的属性。

比如,在STM32的RTC开发中,我们需要数据来表示日期和时间,这些数据通常是年、月、日、时、分、秒。如果我们不用结构体,那么就需要定义6个变量来表示。这样的话程序的数据结构是松散的,我们的数据结构最好是“高内聚,低耦合”的。所以,用一个结构体来表示更好,无论是从程序的可读性还是可移植性还是可维护性皆是:

  1. typedef struct //公历日期和时间结构体
  2.  
  3. {
  4.  
  5. vu16 year;
  6.  
  7. vu8 month;
  8.  
  9. vu8 date;
  10.  
  11. vu8 hour;
  12.  
  13. vu8 min;
  14.  
  15. vu8 sec;
  16.  
  17. }_calendar_obj;
  18.  
  19. _calendar_obj calendar; //定义结构体变量

(2)以修改结构体成员变量的方法代替了函数(入口参数)的重新定义。

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

void DsipDateTime( _calendar_obj  DateTimeVal)

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

void DsipDateTime( vu16 year,vu8 month,vu8 date,vu8 hour,vu8 min,vu8 sec)

显然这样的形参很不可观,数据结构管理起来也很繁琐。如果某个函数的返回值得是一个表示日期和时间的数据,那就更复杂了。这只是一方面。

另一方面,如果用户需要表示日期和时间的数据中还要包含星期(周),这个时候,如果之前没有用机构体,那么应该在DsipDateTime()函数中在增加一个形参vu8 week:

void DsipDateTime( vu16 year,vu8 month,vu8 date,vu8 week,vu8 hour,vu8 min,vu8 sec)

可见这种方法来传递参数非常繁琐。所以以结构体作为函数的入口参数的好处之一就是

函数的声明void DsipDateTime( _calendar_obj  DateTimeVal)不需要改变,只需要增加结构体的成员变量,然后在函数的内部实现上对calendar.week作相应的处理即可。这样,在程序的修改、维护方面作用显著。

  1. typedef struct //公历日期和时间结构体
  2.  
  3. {
  4.  
  5. vu16 year;
  6.  
  7. vu8 month;
  8.  
  9. vu8 date;
  10.  
  11. vu8 week;
  12.  
  13. vu8 hour;
  14.  
  15. vu8 min;
  16.  
  17. vu8 sec;
  18.  
  19. }_calendar_obj;
  20.  
  21. _calendar_obj calendar; //定义结构体变量

    (3)结构体的内存对齐原则可以提高CPU对内存的访问速度(以空间换取时间)
    并且,结构体成员变量的地址可以根据基地址(以偏移量offset)计算。我们先来看看下面的一段简单的程序,对于此程序的分析会在第2部分结构体成员变量内存对齐中详细说明。

  1. #include<stdio.h>
  2.  
  3. int main()
  4.  
  5. {
  6. struct //声明结构体char_short_long
  7. {
  8.  
  9. char c;
  10.  
  11. short s;
  12.  
  13. long l;
  14.  
  15. }char_short_long;
  16.  
  17. struct //声明结构体long_short_char
  18.  
  19. {
  20.  
  21. long l;
  22.  
  23. short s;
  24.  
  25. char c;
  26. }long_short_char;
  27.  
  28. struct //声明结构体char_long_short
  29.  
  30. {
  31.  
  32. char c;
  33.  
  34. long l;
  35.  
  36. short s;
  37.  
  38. }char_long_short;
  39.  
  40. printf(" \n");
  41.  
  42. printf(" Size of char = %d bytes\n",sizeof(char));
  43.  
  44. printf(" Size of shrot = %d bytes\n",sizeof(short));
  45.  
  46. printf(" Size of long = %d bytes\n",sizeof(long));
  47.  
  48. printf(" \n");//char_short_long
  49.  
  50. printf(" Size of char_short_long = %d bytes\n",sizeof(char_short_long));
  51.  
  52. printf(" Addr of char_short_long.c = 0x%p (10进制:%d)\n",&char_short_long.c,&char_short_long.c);
  53.  
  54. printf(" Addr of char_short_long.s = 0x%p (10进制:%d)\n",&char_short_long.s,&char_short_long.s);
  55.  
  56. printf(" Addr of char_short_long.l = 0x%p (10进制:%d)\n",&char_short_long.l,&char_short_long.l);
  57.  
  58. printf(" \n");
  59.  
  60. printf(" \n");//long_short_char
  61.  
  62. printf(" Size of long_short_char = %d bytes\n",sizeof(long_short_char));
  63.  
  64. printf(" Addr of long_short_char.l = 0x%p (10进制:%d)\n",&long_short_char.l,&long_short_char.l);
  65.  
  66. printf(" Addr of long_short_char.s = 0x%p (10进制:%d)\n",&long_short_char.s,&long_short_char.s);
  67.  
  68. printf(" Addr of long_short_char.c = 0x%p (10进制:%d)\n",&long_short_char.c,&long_short_char.c);
  69.  
  70. printf(" \n");
  71.  
  72. printf(" \n");//char_long_short
  73.  
  74. printf(" Size of char_long_short = %d bytes\n",sizeof(char_long_short));
  75.  
  76. printf(" Addr of char_long_short.c = 0x%p (10进制:%d)\n",&char_long_short.c,&char_long_short.c);
  77.  
  78. printf(" Addr of char_long_short.l = 0x%p (10进制:%d)\n",&char_long_short.l,&char_long_short.l);
  79.  
  80. printf(" Addr of char_long_short.s = 0x%p (10进制:%d)\n",&char_long_short.s,&char_long_short.s);
  81.  
  82. printf(" \n");
  83.  
  84. return ;
  85.  
  86. }

 

程序的运行结果如下(注意:括号内的数据是成员变量的地址的十进制形式):

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

首先,我们来分析一下上面程序的运行结果。前三行说明在我的程序中,char型占1个字节,short型占2个字节,long型占4个字节。char_short_long、long_short_char和char_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)的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小的整数倍开始(比如int在32位机为4字节,则要从4的整数倍地址开始存储)。

原则2  结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。

*原则3  结构体作为成员时,结构体成员要从其内部最大元素大小的整数倍地址开始存储。(struct a里存有struct b,b里有char,int,double等元素时,那么b应该从8的整数倍地址处开始存储,因为sizeof(double) = 8 bytes)

这里,我们结合上面的程序来分析(暂时不讨论原则3)。

先看看char_short_long和long_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)。

至此,一个结构体的内存布局完成了。

下面我们按照上述原则,来验证这样的分析是不是正确。按上面的分析,地址单元1244973、1244974、1244975以及1244982、1244983都是空的(至少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中,当你需要定义一个结构体时,只要你稍微留心结构体成员变量内存对齐这一现象,就可以在很大程度上节约MCU的RAM。这一点不仅仅应用于实际编程,在很多大型公司,比如IBM、微软、百度、华为的笔试和面试中,也是常见的。

附件1: test.c
 
出处:http://www.openedv.com/posts/list/27512.htm

漫谈C语言结构体的更多相关文章

  1. 漫谈C语言结构体struct、公用体union空间占用

    先用代码说话: #include<stdio.h> union union_data0{ int a ;//本身占用4个字节 char b ;//本身占用1个字节 int c ; }; u ...

  2. 漫谈C语言结构体【转】

    相信大家对于结构体都不陌生.在此,分享出本人对C语言结构体的学习心得.如果你发现这个总结中有你以前所未掌握的,那本文也算是有点价值了.当然,水平有限,若发现不足之处恳请指出.代码文件test.c我放在 ...

  3. 解析C语言结构体对齐(内存对齐问题)

    C语言结构体对齐也是老生常谈的话题了.基本上是面试题的必考题.内容虽然很基础,但一不小心就会弄错.写出一个struct,然后sizeof,你会不会经常对结果感到奇怪?sizeof的结果往往都比你声明的 ...

  4. 不可或缺 Windows Native (8) - C 语言: 结构体,共用体,枚举,类型定义符

    [源码下载] 不可或缺 Windows Native (8) - C 语言: 结构体,共用体,枚举,类型定义符 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 结构体 ...

  5. Go语言结构体(struct)

    Go 语言结构体 Go 语言中数组可以存储同一类型的数据,但在结构体中我们可以为不同项定义不同的数据类型. 结构体是由一系列具有相同类型或不同类型的数据构成的数据集合. 结构体表示一项记录,比如保存图 ...

  6. C语言结构体定义的几种方法

    什么是结构体? 在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)的一类.结构体可以被声明为变量.指针或数组等,用以实现较复杂的数据 ...

  7. 对嵌入式开发C语言结构体的一点总结

    今天冬至居然不上班,公司的良心啊!这回有心情写博客和日志了,好了,废话不多说.直接看下文: 鉴于嵌入式开发过程中,C语言结构体的使用当然是必不可少.话说,基础什么的比你会更牛逼的算法更重要,基础不牢, ...

  8. C语言结构体变量私有化

    操作系统 : CentOS7.3.1611_x64 gcc版本 :4.8.5 问题描述 C语言结构体定义中的变量默认是公有(Public)属性,如果实现成员变量的私有(Private)化? 解决方案 ...

  9. 在C语言结构体中添加成员函数

    我们在使用C语言的结构体时,经常都是只定义几个成员变量,而学过面向对象的人应该知道,我们定义类时,不只是定义了成员变量,还定义了成员方法,而类的结构和结构体非常的相似,所以,为什么不想想如何在C语言结 ...

随机推荐

  1. soj#532 set p3175

    传送门 分析 代码 #include<bits/stdc++.h> using namespace std; ; <<],Ans; int n,m,N; inline int ...

  2. WPF自定义控件(三)

    今天我们开始制作我们的按钮,主要的效果就是一个按钮正常状态.鼠标滑过.按下三态显示不同的图片. 首先我们需要给扩展按钮添加三个属性,分别是正常状态图片,鼠标滑过图片,按钮按下图片. 先贴出Button ...

  3. CentOS安装系统时硬盘分区建议

      一.常见挂载点的情况说明一般来说,在linux系统中都有最少两个挂载点,分别是/ (根目录)及 swap(交换分区),其中,/ 是必须的: 详细内容见下文: 安装系统时选择creat custom ...

  4. b/s 起点

    1.Web前端: JavaScript (1)脚本语言.JavaScript是一种解释型的脚本语言,C.C++等语言先编译后执行,而JavaScript是在程序的运行过程中逐行进行解释. (2)基于对 ...

  5. linux下部署springboot vue项目

    使用的工具是 XFTP5 XSHELL5 docker pull gmaslowski/jdk 拉取jdk docker images 查询下载的镜像ID (如:390b58b1be42) docke ...

  6. C语言|博客作业4

    一.本周教学内容:用C语言编写程序-循环结构 2.4 输出华氏-摄氏温度转换表.要求学生掌握使用for循环语句实现指定次数的循环程序设计. 二.本周作业头 问题 答案 这个作业属于哪个内容 C语言程序 ...

  7. VM虚拟机启动centos出现内部错误

    内部错误 解决办法 1.关闭虚拟机后,单击VM,右键,以管理员身份运行.   2.找到电脑的 管理— Vmware workstation server ,默认状态下是手动,把他改为自动重启就可以啦.

  8. C#隐式类型和显示类型

    一,在程序中我们经常会遇到:无法将类型“XXX”隐式装换为“XXX”,如下例子: static void Main(string[] args) { int i; i = "Hello Wo ...

  9. python3学习笔记——数字、字符串、列表、字典、元组

    什么是python解释器? python代码在解释器中运行.解释器是代码与计算机硬件之间的软件逻辑层. python的执行过程                                      ...

  10. C常量与变量

    /** * C中的常量与变量 * 常量的值在程序中是不可变化的,其在定义时必须给一个初始值 * 常量的定义方式: * 1.#define 定义宏常量 * 2.const 定义const常量 * 对于# ...