当前位置:首页技术教程第十四章:优化结构体中元素的布局,了解C++基本语言特性变量和类型
第十四章:优化结构体中元素的布局,了解C++基本语言特性变量和类型
°
  • 素材类型: 精品-素材
  • 上传时间:

在开始讨论之前,我们先看两个结构体类型,分别为 struct A和 struct B。

struct A     //A数据结构1
{
long 1A;     //lA long 数据
char cB;     // cB char 数据
short nC;    // nC short 数据
};
struct A
{
  char cB
  long 1A:
  short nC;
};

  在 32 位的机器上,char、short、long 3 种类型的长度分别为 1、2、4。在Visual Studio2010 上测试 struct A 和 struct B 的存储长度,sizeof(struct A)= 8,sizeof(struct A)= 12。这也许会让你惊讶,char、short、long分别占用1、2、4字节,但是按照不同的顺序组合成一个结构体后,结构体的长度会大于3个长度的总和。这就是本实用经验要重点讨论的内容结构体的内存布局。

  结构体元素的布局是结构体定义过程中需要考虑到的。优化结构体元素的布局主要有两个方面的原因:一是节省内存空间;二是提高数据存取速度。

  说到节省内存空间和提高数据存取速度,对齐是一个必须讨论的问题。现代计算机中内存空间都是按照 byte 划分的。理论上讲,似乎对任何类型变量的访问都可以从任何地址开始,但实际情况是在访问特定的变量时经常从特定的存储地址开始访问。这就要求各类型数据按照一定的规则在空间上排列,而不是顺序地一个接一个排放。这就是数据的对齐。

  然而为什么要进行数据对齐呢?各个硬件平台在存储空间的处理上有很大的不同,一些平台对某些特定类型的数据只能从某些特定地址开始存取,其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台的要求对数据存放进行对齐,就会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)数据存放在偶地址开始的地方,那么一个读周期就可以读出;而如果存放在奇地址开始的地方,就可能会需要两个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据,显然在读取效率上下降很多。这也是空间和时间的博弈。

  对齐的实现方式是什么?通常,我们不需要考虑对齐问题,编译器会默认选择目标平台的对齐策略。当然,我们也可以自己指定数据的对齐方法。

  内存对齐

  (1) 内存数据对齐,降低数据存取的CPU时钟周期;适当的内存对齐策略,可降低内存使用量。

  (2) 内存对齐一般由编译器完成,无特殊需要不需要人为干预。但是,正因为编译器替我们对数据存放做了对齐,但我们并不知道编译器替我们做了这些,所以我们常常会对一些问题感到迷惑,最常见的就是struct 数据结构的

  sizeof 结果。为此,我们需要对对齐算法有所了解。对齐通常会影响结构体、联合、类等复合类型数据的存储区域分布。

  一般情况下,对齐算法遵循下述4个规则:

  ● 在复合类型中,各数据成员按照它们的声明顺序在内存中顺序存储,第一个成员放到复合类型的起始位置(相对偏移为0)。

  ● 每个成员按照自己的对齐方式并最小化长度进行自身的数据存储。

  ● 复合类型的整体对齐按照类型中长度最大的数据成员和#pragma pack 指定值较小的那个值进行对齐。

  ● 整个复合类型的长度必须为所采用的对齐参数的整数倍,不够的补空字节。

  现在,我们按照上述的对齐准则,再次从理论的高度分析为什么会得到上述运算结果。首先我们分析 struct A。IA 为 long 类型4,采用 4 字节对齐。cB 为 char 型,采用1字节对齐。nC为 short类型,采用2字节对齐。整个 struct采用4字节对齐。所以可以得出如下结论:1A放置于 structA的起始位置,占用4字节;cB排列到1A之后之后占用1字节;nC为 short型2字节对齐,起始地址必须为2的整数倍,所以cB之后必须空闲1字节,其后面可以存储 short类型的nC。所以struct A在内存中的排列如图2-5所示。按照同样的道理,struct B的内存分布如图 2-6 所示。

第十四章:优化结构体中元素的布局,了解C++基本语言特性变量和类型

  可以看出,同样的3个数据由于排列顺序不同,导致 struct的占用空间发生了很大的变化。所以在定义 struct 数据类型时,struct中的数据排列顺序是需要重点考虑的。

  如果在空间紧张的情况下定义结构体类型时,数据变量的排列顺序应遵守如下原则:一是把结构体中的变量按照类型大小从小到大顺序声明,尽量减少中间的空闲填充字节;二是以空间换取时间,即显式地填补空间进行对齐,例如,有一种使用空间换时间的做法是显式地插入reserved 成员:

struct A
{
  char a;
  char reserved[3];  //使用空间换时间
  int b;
}
  提示
  (1)reserved对程序没有什么意义,它只是填补空间,以达到字节对齐的目的。
  (2)即使不加入reserved成员,通常编译器也会自动填补对齐。而加上它,只是显式的提醒作用。
接下来看下面这段代码片段:
unsigned int i    =0x12345678;
unsigned char * p =NULL;
unsigned short *p1 = NULL;
P     =  &i;
*p    =  0x00;
p1    =(unsigned short *)(p+1);
*p1   =0x0000;

  最后两句代码从奇数边界访问 unsigned short 型变量,显然不符合对齐规定。在x86上,这种操作只会影响效率,但是在 MIPS 或者 SPARC上,这种操作可能就是一个error,因为它们要求必须字节对齐。

  如果你从事的是网络协议栈开发,则可能会为这样的事情而烦恼:在定义协议报头时,由于协议报头的这段顺序是固定的,无法按照类型从小到大的顺序声明,最终导致程序出现异常现象。

  例如,在小端CPU格式下,IP协议头定义如下:

//IP头部,总长度20字节
struct IP HDR
{
unsigned char ihl:4;        // 首部长度
unsigned char version:4,   // 版本
unsigned char tos;        // 服务类型
unsigned short tot_len;    // 总长度
unsigned short id;        // 标志
unsigned short frag off:   // 分片偏移
unsigned char ttl;         // 生存时间
unsigned char protocol;   // 协议
unsigned short chk sum;  // 检验和
struct in addr srcaddr;    // 源IP地址
struct in addr dstaddr;    // 目的IP地址
};

  在IP协议头中任何两个逻辑上相邻的字段都必须在内置中相邻,中间不能出现因为对齐而添加的空闲字段。为了达到取消空闲字段的目的,编译器允许用户自己根据需要设置复合类型(结构体、联合、位段)的对齐方式实现。这就是 pragma pack()宏。

  它的功能说明如下:

#pragma pack([show]| [push| pop][, identifier], n)

  功能说明

  ● pack 提供数据声明级别的控制,对定义不起作用。

  ● 调用 pack时不指定参数,n 将被设成默认值。

  ● 一旦改变数据类型的对齐格式,直接效果就是占用内存的减少,但是性能会下降。

  语法说明

  ● show:可选参数;显示当前 packing alignment 的字节数,以 warning message的形式被显示。

  ● push:可选参数;将当前指定的 packing alignment 数值进行压栈操作。这里的栈是 the internal compiler stack,同时设置当前的 packing alignment 为 n;如果n 没有指定,则将当前的 packing alignment 数值压栈。

  ● pop:可选参数;从 internal compiler stack 中删除最顶端的 record;如果没有指定 n,则当前栈顶 record 即为新的 packing alignment 数值;如果指定了 n,则 n将成为新的 packing alignment 数值;如果指定了 identifier,则 internal compilerstack 中的 record 都将被弹出,直到 identifier 被找到,然后弹出,identifier,同时设置 packing alignment 数值为当前栈顶的 record;如果指定的 identifier 并不存在于 internal compiler stack,则弹出,操作被忽略。

  ● identifier:可选参数;当同 push 一起使用时,赋予当前被压入栈中的 record 一个名称;当同 pop 参数一起使用时,从 internal compiler stack 中弹出所有的record 直到 identifier 被弹出,如果 identifier 没有被找到,则忽略 pop 操作。

  ● n:可选参数;指定对齐的数值,以字节为单位;默认数值是8,合法的数值分别是1、2、4、8、16。最后,看一下 pragma pack()复合类型内存对齐的影响。对比下面 3 组代码片段,观察同一结构体在不同对齐方式下的长度。

  片段一:通过 pragma pack 指定对齐格式为 1。sizeof(struct A)=7。

#pragma pack(1)
struct A
{
  char b:
  int a;
  short c:
};
pragma pack()

  片段二:通过 pragma pack 指定对齐格式为 2。sizeof(struct A)=8。

#pragma pack(2)
struct A
{
  char b;
  int a;
  short c;
};
pragma pack()
  片段三:通过 pragma pack 指定对齐格式为 4。sizeof(struct A)= 12。
#pragma pack(4)
struct A
{
  char b;
  int a;
  short c;
};
pragma pack0

  可以看出,将结构体的对齐方式设置为1,结构体A就不会自动填充空闲字段,结构体A的长度就是各元素所占字节之和7。采用某种对齐方式,整个结构体的总长度就必须能被对齐方式整除。如对齐方式设置为2,结构体总长度为8可被2整除;对齐方式设置为4,结构体总长度为12可被4整除。

  请谨记

  掌握复合类型中元素的对齐规则,合理调整复合类型中元素的布局,不仅可以节省空间,还可以提高数据存取效率。

第十四章:优化结构体中元素的布局,了解C++基本语言特性变量和类型
温馨提示:

文章标题:第十四章:优化结构体中元素的布局,了解C++基本语言特性变量和类型

文章链接:https://www.xiciw.com/jsjc/1534.html

更新时间:2024年02月15日

本站大部分内容均收集于网络!若内容若侵犯到您的权益,请发送邮件至:xiciw#qq.com我们将第一时间处理!

资源所需价格并非资源售卖价格,是收集、整理、编辑详情以及本站运营的适当补贴,并且本站不提供任何免费技术支持。

                               

所有资源仅限于参考和学习,版权归原作者所有,更多请阅读菜鸟资源服务协议

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。

给TA打赏
共{{data.count}}人
人已打赏
技术教程

第十三章:typedef 使用的陷阱,了解C++基本语言特性变量和类型

2024-2-22 10:12:00

技术教程

最新的百度蜘蛛baiduSpider IP大全教程分享(持续更新)SEO知识

2024-2-25 16:58:28

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索