sizeof()的使用

上一篇 / 下一篇  2007-09-15 10:11:29 / 天气: 晴朗 / 心情: 高兴

在VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。
 A.参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可

能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。
 B. 参数为数组或指针。下面举例说明.int a[50]; //sizeof(a)=4*50=200; 求数组所占的空间大小int *a=new int[50];// sizeof(a)=4; a

为一个指针,sizeof(a)是求指针//的大小,在32位系统中,当然是占4个字节。
 C.参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,
   第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。第二、没有成员

变量的结构或类的大小为1,因为必须保证结构或类的每一个实例在内存中都有唯一的地址。下面举例说明,
     Class Test{
       int a;
       static double c};//sizeof(Test)=4.Test *s;//sizeof(s)=4,s为一个指针。Class test1{ };//sizeof(test1)=1;
 D. 参数为其他。下面举例说明。
    int func(char s[5]);{cout< //数的参数在传递的时候系统处理为一个指针,所//以sizeof(s)实际上为求指针的大小。return 1;}

sizeof(func(“1234”))=4//因为func的返回类型为int,所以相当于求sizeof(int).以上为sizeof 的基本用法,在实际的使用中要注意分析

VC的分配变量的分配策略,这样的话可以避免一些错误
sizeof()功能:计算数据空间的字节数
1.与strlen()比较
      strlen()计算字符数组的字符数,以"\0"为结束判断。
      而sizeof计算数据(包括数组、变量、类型、结构体等)所占内存空间,用字节数表示
2.指针与静态数组的sizeof操作
      指针均可看为变量类型的一种。所有指针变量的sizeof 操作结果均为4。
      注意:int *p; sizeof(p)=4;
                   但sizeof(*p)相当于sizeof(int);     
      对于静态数组,sizeof可直接计算数组大小;
      例:int a[10];char b[]="hello";
              sizeof(a)等于10;
              sizeof(b)等于7;
      注意:数组做型参时,数组名称当作指针使用!!
               void  fun(char p[])
               {sizeof(p)等于4}
   
  经典问题:

      double* (*a)[3][6];

      cout<<sizeof(a)<<endl; // 4
      cout<<sizeof(*a)<<endl; // 72
      cout<<sizeof(**a)<<endl; // 24
      cout<<sizeof(***a)<<endl; // 4
      cout<<sizeof(****a)<<endl; // 8

      a是一个很奇怪的定义,他表示一个指向double*[3][6]类型数组的指针。既然是指针,所以sizeof(a)就是4。

      既然a是执行double*[3][6]类型的指针,*a就表示一个double*[3][6]的多维数组类型,因此sizeof(*a)
     =3*6*sizeof(double*)=72。同样的,**a表示一个double*[6]类型的数组,所以sizeof(**a)=6*sizeof  (double*)=24。***a就表示其中

的一个元素,也就是double*了,所以sizeof(***a)=4。至于****a,就是一个double了,所以sizeof(****a)=sizeof(double)=8。
sizeof()功能:计算数据空间的字节数
 
3.格式的写法
   sizeof操作符,对变量或对象可以不加括号,但若是类型,须加括号
4.使用sizeof时string的注意事项
   string s="hello";
   sizeof(s)等于string类的大小,sizeof(s.c_str())得到的是与字符串长度。
5.union 与struct的空间计算
   总体上遵循两个原则:
   (1)整体空间是 占用空间最大的成员(的类型)所占字节数的整倍数
   (2)数据对齐原则----内存按结构成员的 先后顺序排列,当排到该成员变量时,其前面已摆放的空间大小必须是该成员类型大小的整倍数,

如果不够则补齐,以此向后类推。。。。。
   注意:数组按照单个变量一个一个的摆放,而不是看成整体。如果成员中有自定义的类、结构体,也要注意数组问题。
例:[引用其他帖子的内容]
因为对齐问题使结构体的sizeof变得比较复杂,看下面的例子:(默认对齐方式下)

struct s1
{
char a;
double b;
int c;
char d;
};

struct s2
{
char a;
char b;
int c;
double d;
};

cout<<sizeof(s1)<<endl; // 24
cout<<sizeof(s2)<<endl; // 16

  同样是两个char类型,一个int类型,一个double类型,但是因为对界问题,导致他们的大小不同。计算结构体大小可以采用元素摆放法,我

举例子说明一下:首先,CPU判断结构体的对界,根据上一节的结论,s1和s2的对界都取最大的元素类型,也就是double类型的对界8。然后开

始摆放每个元素。
  对于s1,首先把a放到8的对界,假定是0,此时下一个空闲的地址是1,但是下一个元素d是double类型,要放到8的对界上,离1最接近的地址

是8了,所以d被放在了8,此时下一个空闲地址变成了16,下一个元素c的对界是4,16可以满足,所以c放在了16,此时下一个空闲地址变成了

20,下一个元素d需要对界1,也正好落在对界上,所以d放在了20,结构体在地址21处结束。由于s1的大小需要是8的倍数,所以21-23的空间被

保留,s1的大小变成了24。
  对于s2,首先把a放到8的对界,假定是0,此时下一个空闲地址是1,下一个元素的对界也是1,所以b摆放在1,下一个空闲地址变成了2;下

一个元素c的对界是4,所以取离2最近的地址4摆放c,下一个空闲地址变成了8,下一个元素d的对界是8,所以d摆放在8,所有元素摆放完毕,

结构体在15处结束,占用总空间为16,正好是8的倍数。

  这里有个陷阱,对于结构体中的结构体成员,不要认为它的对齐方式就是他的大小,看下面的例子:

struct s1
{
char a[8];
};

struct s2
{
double d;
};

struct s3
{
s1 s;
char a;
};

struct s4
{
s2 s;
char a;
};

cout<<sizeof(s1)<<endl; // 8
cout<<sizeof(s2)<<endl; // 8
cout<<sizeof(s3)<<endl; // 9
cout<<sizeof(s4)<<endl; // 16;

  s1和s2大小虽然都是8,但是s1的对齐方式是1,s2是8(double),所以在s3和s4中才有这样的差异。

  所以,在自己定义结构体的时候,如果空间紧张的话,最好考虑对齐因素来排列结构体里的元素。



不要让double干扰你的位域

  在结构体和类中,可以使用位域来规定某个成员所能占用的空间,所以使用位域能在一定程度上节省结构体占用的空间。不过考虑下面的

代码:

struct s1
{
 int i: 8;
 int j: 4;
 double b;
 int a:3;
};

struct s2
{
 int i;
 int j;
 double b;
 int a;
};

struct s3
{
 int i;
 int j;
 int a;
 double b;
};

struct s4
{
 int i: 8;
 int j: 4;
 int a:3;
 double b;
};

cout<<sizeof(s1)<<endl; // 24
cout<<sizeof(s2)<<endl; // 24
cout<<sizeof(s3)<<endl; // 24
cout<<sizeof(s4)<<endl; // 16

  可以看到,有double存在会干涉到位域(sizeof的算法参考上一节),所以使用位域的的时候,最好把float类型和double类型放在程序的开始或者最后。
 

字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。
对于上面的准则,有几点需要说明:
1) 前面不是说结构体成员的地址是其大小的整数倍,怎么又说到偏移量了呢因为有了第1点存在,所以我们就可以只考虑成员的偏移量,这样思考起来简单。想想为什么。

结构体某个成员相对于结构体首地址的偏移量可以通过宏offsetof()来获得,这个宏也在stddef.h中定义,如下:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
例如,想要获得S2中c的偏移量,方法为size_t pos = offsetof(S2, c);// pos等于4
2) 基本类型是指前面提到的像char、short、int、float、double这样的内置数据类型,这里所说的“数据宽度”就是指其sizeof的大 小。由于结构体的成员可以是复合类型,比如另外一个结构体,所以在寻找最宽基本类型成员时,应当包括复合类型成员的子成员,而不是把复合成员看成是一个整 体。但在确定复合类型成员的偏移位置时则是将复合类型作为整体看待。这里叙述起来有点拗口,思考起来也有点挠头,还是让我们看看例子吧(具体数值仍以 VC6为例,以后不再说明):
struct S3
{
    char c1;
    S1 s;
    char c2
};
S1 的最宽简单成员的类型为int,S3在考虑最宽简单类型成员时是将S1“打散”看的,所以S3的最宽简单类型为int,这样,通过S3定义的变量,其存储 空间首地址需要被4整除,整个sizeof(S3)的值也应该被4整除。c1的偏移量为0,s的偏移量呢这时s是一个整体,它作为结构体变量也满足前面三 个准则,所以其大小为8,偏移量为4,c1与s之间便需要3个填充字节,而c2与s之间就不需要了,所以c2的偏移量为12,算上c2的大小为13,13 是不能被4整除的,这样末尾还得补上3个填充字节。最后得到sizeof(S3)的值为16。
通过上面的叙述,我们可以得到一个公式:
结构体的大小等于最后一个成员的偏移量加上其大小再加上末尾的填充字节数目,即:

sizeof( struct ) = offsetof( last item ) + sizeof( last item ) + sizeof( tr
ailing padding )

到这里,朋友们应该对结构体的sizeof 有了一个全新的认识,但不要高兴得太早,有一个影响sizeof的重要参量还未被提及,那便是编译器的pack指令。它是用来调整结构体对齐方式的,不同 编译器名称和用法略有不同,VC6中通过#pragma pack实现,也可以直接修改/Zp编译开关。#pragma pack的基本用法为:#pragma pack( n ),n为字节对齐数,其取值为1、2、4、8、16,默认是8,如果这个值比结构体成员的sizeof值小,那么该成员的偏移量应该以此值为准,即是说, 结构体成员的偏移量应该取二者的最小值,公式如下:
offsetof( item ) = min( n, sizeof( item ) )
再看示例:
#pragma pack(push) // 将当前pack设置压栈保存
#pragma pack(2)// 必须在结构体定义之前使用
struct S1
{
    char c;
    int i;
};
struct S3
{
    char c1;
    S1 s;
    char c2
};
#pragma pack(pop) // 恢复先前的pack设置
计 算sizeof(S1)时,min(2, sizeof(i))的值为2,所以i的偏移量为2,加上sizeof(i)等于6,能够被2整除,所以整个S1的大小为6。同样,对于sizeof (S3),s的偏移量为2,c2的偏移量为8,加上sizeof(c2)等于9,不能被2整除,添加一个填充字节,所以sizeof(S3)等于10。
现在,朋友们可以轻松的出一口气了,:)
还有一点要注意,“空结构体”(不含数据成员)的大小不为0,而是1。试想一个“不占空间”的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。如下:
struct S5 { };
sizeof( S5 ); // 结果为1

含位域结构体的sizeof:
前面已经说过,位域成员不能单独被取sizeof值,我们这里要讨论的是含有位域的结构体的sizeof,只是考虑到其特殊性而将其专门列了出来。
C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在。
使用位域的主要目的是压缩存储,其大致规则为:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字
段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字
段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方
式,Dev-C++采取压缩方式;
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。

还是让我们来看看例子。
示例1
struct BF1
{
    char f1 : 3;
    char f2 : 4;
    char f3 : 5;
};
其内存布局为:
 |__f1___|____f2___ |__|____f3______|______|
 |__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|__|

位域类型为char,第1个字节仅能容纳下f1f2,所以f2被压缩到第1个字节中,而f3
能从下一个字节开始。因此sizeof(BF1)的结果为2
示例2
struct BF2
{
    char f1 : 3;
    short f2 : 4;
    char f3 : 5;
};
由于相邻位域类型不同,在VC6中其sizeof6,在Dev-C++中为2
示例3
struct BF3
{
    char f1 : 3;
    char f2;
    char f3 : 5;
};
非位域字段穿插在其中,不会产生压缩,在VC6Dev-C++中得到的大小均为3



TAG:

 

评分:0

我来说两句

显示全部

:loveliness: :handshake :victory: :funk: :time: :kiss: :call: :hug: :lol :'( :Q :L ;P :$ :P :o :@ :D :( :)

Open Toolbar