当前位置:C++技术网 > 资讯 > 结构体对齐问题分析

结构体对齐问题分析

更新时间:2015-06-24 16:06:23浏览次数:1+次

    在做项目时在定义结构体时,因为是对数据成员都进行了字节拆分,并且从头到尾,数据类型交错出现,这个是对数据结构的定义,并不是我故意为之,就好像TCP/IP协议的字段一样。然而在定义结构体时,确实是按照一个个的类型来写的,确定无误后,发现运行结果还是有错误。但是通过断点跟踪发现,前几个字段的值是正确的,后面的都错误。再通过内存窗口查看(在调试状态下,菜单“调试”->“窗口”->“内存”->“内存1/2/3/4”选择其中一个,然后输入变量或指针名,回车),这样就看到了内存的数据。奇怪的是,这些数据是正确的。为什么放到结构体中就不对了呢?
    然后灵感一闪,似乎之前也碰见过,解决了这个问题,那些代码没有这个问题。找到那个代码一看,仔细比对后,突然发现是结构体定义的头和尾有编译指令,我加进去后,结果正确了。
    这让我想起之前网站一篇文章,《关于成员对齐方式》的作者写的很好。那是我还没有这方面的经验,也没怎么看懂。说来惭愧,向他学习了。今天又回去看看,一字一句沉下心看,发现了不少宝贵的经验。看了一下,文章里也用了编译器指令#pragma pack. 解释很到位 。不过刚开始接触这个的,或许还是有点迷糊。我在此稍作解释,这也是我的学习心得,分享一下。
    编译器指令#pragma pack这个的用法,我另外写一篇文章详细解释。在此就只解释这种原理。为什么我的结构体取到数据和预期的不一样呢?为什么结构体的大小并不是所有成员大小的总和呢?
    引用《关于成员对齐方式》的一句话,“结构体或类的数据成员的类型,声明顺序,采用的对齐方式等都会影响对象的实际大小和访问效率。”这句话包含了很多信息,如果不仔细体会,将错过很多精彩内容。对于这些知识的验证,请访问《关于成员对齐方式》。我这里是在理解上加以补充。
    结构体中,成员的类型决定的是这个成员的最基本的大小,成员的排列顺序,则决定了编译器处理的方式。
    成员的类型就是数据类型,数据类型反映在内部就是数据长度,这是最基本的一点,在此也就需要考虑这一点,其他的不考虑。因为内存是有对齐问题的。一个内存单元的大小就做内存页,内存页中有多个更小的单元组成,基本上是按字节作为最基本的单元了。位是最小单位了,但是数据类型中最小的就是字节了,比如char。那么编译器为了提高效率,自然存取数据时需要按照默认的单元来处理的,各个编译器不一样。如果结构体中全部是char,自然是以字节为单位,这样存取的效率是最高的。具体哪样,看编译器的策略,在此只是大概说说而已,帮助理解。如果有几种不同的类型,也就是说,成员中的内存长短不一,为了提高效率,默认的做法就是按照最大的类型的长度做为存取的单位,这样存取一个大类型的就不用按照小单位取多次。但是这样带来的问题就是,小内存的类型就被填充足够的长度来对齐。使之与这个最大的长度保持一致,这样存取效率就高了,而不用每次判断长度,再取。而是直接挨个存取就可以,执行效率高了。但是就是使内存浪费了。还有一个就是我遇见的那个问题,因为自动对齐了,就将短类型的高位填充补零使长度保持一致,这样,我按位来读取指定的数据,自然就是错误的,因为对齐后,就不是原样的数据了。
    内存中的数据没变,只是编译器处理了中间的存取过程,比如将一个字节的值扩充成几个字节,然后赋值给了结构体,然后取之后的,如果短,继续对齐,而最大的那个就直接赋值。这样,所有的都对齐了,但是你想要的数据就没有了。但是内存中的数据还是正确的。修改变量的时候,会按照内存中实际存储的大小写进去。这样一来,只是在中间进行了加工而已。但是你就是得不到想要的数据。
    所以,你现在知道你得到的结构体的实际大小并不是你定义的大小,而是处理过的大小。
    为了解决这个问题,我们就要阻止默认的对齐方式,按照我们自己的方式来做。一种是取消对齐方式。可以自己设置,方法是:菜单“项目”->“项目属性”->“配置属性”->“C/C++”->“代码生成”->“结构成员对齐方式”,修改一下。修改成1字节即可。1字节即最小的类型大小,也就保证了实际取出来的大小和你自己的预期的大小是一致的。修改对齐字节为1字节后,得到的大小和预期的一样。这是在集成环境修改的。你也可以通过编译器指令来修改,如#pragma pack(1)
    括号中的1表示按1字节对齐,就保持我们原本的样子.建议使用编译器指令,就不依赖工程的设置 。通过验证,也说明了上面的原理,请仔细体会。代码验证请阅读《关于成员对齐方式》。