设为首页 加入收藏

TOP

malloc与sizeof的合用的陷阱(三)
2014-11-23 21:42:12 来源: 作者: 【 】 浏览:53
Tags:malloc sizeof 合用 陷阱
可以看出sizeof(B)并不等于sizeof(int)+sizeof(double)+sizeof(int)=16。
struct A{
int num1;
int num2;
double num3;
};
struct B{
int num1;
double num3;
int num2;
};
如果您不了解结构体的成员对齐,你会感到非常惊讶:结构体A和B中包含的成员都一样,只不过顺序不同而已,为什么其大小不一样呢?要解释这个问题,就要了解结构体成员对齐的规则,由于结构体成员对齐非常复杂,我将用专题——C/C++刁钻问题各个击破之位域和成员对齐——进行讲解,这里我只简单地介绍其规则:
1、 结构体的大小等于结构体内最大成员大小的整数倍
2、 结构体内的成员的首地址相对于结构体首地址的偏移量是其类型大小的整数倍,比如说double型成员相对于结构体的首地址的地址偏移量应该是8的倍数。
3、 为了满足规则1和2编译器会在结构体成员之后进行字节填充!
基于上面三个规则我们来看看为什么sizeof(B)等于24:首先假设结构体的首地址为0,第一个成员num1的首地址是0(满足规则2,前面无须字节填充,事实上结构体绝对不会在第一个数据成员前面进行字节填充),它的类型是int,因此它占用地址空间0——3。第二个成员num3是double类型,它占用8个字节,由于之前的num1只占用了4个字节,为了满足规则2,需要使用规则3在num1后面填充4个字节(4——7),使得num3的起始地址偏移量为8,因此num3占用的地址空间是:8——15。第三个成员num2是int型,其大小为4,由于num1和num3一共占用了16个字节,此时无须任何填充就能满足规则2。因此num2占用的地址空间是16——19。那么是不是结构体的总大小就是0——19共20个字节呢?请注意,别忘了规则1!由于结构体内最大成员是double占用8个字节,因此最后还需要在num2后面填充4个字节,使得结构体总体大小为24。
按照上面的三个规则和分析过程,你可以很容易地知道为什么sizeof(A)等于16。特别需要说明的是,我这里给出了三个结论性的规则,而没有阐述为什么要这样。你或许有很多疑问:为什么要结构体成员对齐,为什么要定义规则1等。如果你有这样的疑问,并尝试去弄清楚的话,那么我敢断言,不久的将来你必定会有大成就,至少在学习c++上是这样。前面说过,我会再写一篇专题:C/C++刁钻问题各个击破之位域和成员对齐来详细回答这些问题,如果你急于要弄明白,那么你可以参考其他资料,比如说《高质量c++程序设计指南》。
最后再提醒一点,在进行设计时,最好仔细安排结构体中各个成员的顺序,因为你已经看到了上面的结构体B与结构体A包含的成员相同,只不过顺序略有差异,最终就导致了B比A多消耗了50%的空间,假如在工程中需要定义该结构体的数组,多消耗的空降将是巨大的。即使将来内存降价为白菜价格,你也不要忽视这个问题,勤俭节约是中国人民的优良传统,我们应该继承和保持!
特性9:sizeof不能用于求结构体的位域成员的大小,但是可以求得包含位域成员的结构体的大小!
首先解释一下什么是位域:类型的大小都是以字节(byte)为基本单位的,比如sizeof(char)为1byte,sizeof(int)为4byte等。我们知道某个类型的大小确定了该类型所能定义的变量的范围,比如sizeof(char)为1byte,而1byte等于8bit,所以char类型的变量范围是-128——127,或者0——255(unsigned char),总之它只能定义28=256个数!然而,要命的是bool类型只取值true和false,按理所只用1bit(即1/8byte)就够了,但事实上sizeof(bool)等于1。因此我们可以认为bool变量浪费了87.5%的存储空间!这在某些存储空间有限的设备(比如嵌入式设备)上是不合适的,为此需要提供一种能对变量的存储空间精打细算的机制,这就是位域。简单来说,在结构体的成员变量后面跟上的一个冒号+一个整数,就代表位域,请看如下的结构体:
Struct A
{
Bool b:1;
char ch1:4;
char ch2:4;
}item; 其中b,ch1,ch2都是位域成员,而i是普通成员。该结构体的试图让bool类型的变量b只占用1个bit,让ch1和ch2分别只占用4个bit,以此来达到对内存精打细算的功能(事实上使用位域对内存精打细算有时候能成功,有时候却未必,我将《C/C++刁钻问题各个击破之位域和成员对齐》进行论述)。另外需要特别注意的是:c语言规定位域只能用于int,signed int或者unsigned int类型,C++又补充了char和long类型!你不能这样使用位域:floatf:8;这是不能通过编译的。并且位域变量不能在函数或者全局区定义,只能在结构体,自定义类,联合(union)中使用!
基于上面的结构体,语句sizeof(item.b)和sizeof(item.ch1)等对位域成员求大小的语句均不能通过编译。其原因能再本篇的概论中找到:sizeof以byte为单位返回操作数的大小!
那么爱学好问的你可能要问,sizeof(A)能否通过编译呢?如何能,其结果又是多少呢?这是两给非常好的问题,事实上我之前没有看到任何关于这方面的论述(可能是我看的资料不足),我正是在看到sizeof(item.b)不能通过编译时想到了这两个问题,然后通过验证得出了后面的结论:对包含位域的结构体是可以使用sizeof求其大小的,但其求值规则比较复杂,不仅涉及到成员对齐,还与具体编译环境有关!在这里你只需要知道可以对包含位域的结构体使用sizeof求其大小,对于sizeof是根据什么规则来求这个大小的问题,我将会在专题:《C/C++刁钻问题各个击破之位域和成员对齐》中进行详细阐述。
后记:
至此,本专题差不多该结束了,需要说明的是,这里并没有包含所有关于sizeof的知识点,但是也几乎包含了所有的容易出错的特性。为了完成该文,我花了断断续续3天半时间,想想效率实在是底下。由于是本系列的第一个专题,我格外慎重,深怕讲错了误导大家。即便如此,也难免错误或不妥之处,还请各位朋友指正!
另外,我有几句话要对大学生朋友们说:教科书通常只是教授很基础的知识,要想深入学习,还需要翻阅其他资料,比如论文、网络资料、 论坛博文,最重要的一点是要在学习时经常总结、记录、归纳,积少成多,这样坚持下来一定受益匪浅。
首页 上一页 1 2 3 下一页 尾页 3/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇C语言的那些事――scanf()和gets(.. 下一篇Linux环境下的C/C+基础调试技术2..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: