sizeof的那些事

来源:互联网 发布:买的淘宝号安全吗 编辑:程序博客网 时间:2024/04/27 20:31

一、sizeof是关键字而不是函数

  sizeofC语言中32个关键字中的一个,注意它是关键字而不是函数!举个例子:

int i;

A sizeof(int)   B sizeof(i)  C sizeof int  D sizeof i

32位机器上的VC6.0编译器下测试下可知ABD的值都是4,而C选项是一种错误的表示方法,在编译器下会报错。D选项足以证明sizeof是一个关键字而非函数,试想如果是函数的话会容许你不将参数用括号括起来吗?那么C选项为什么是错误的呢?原因和简单。int是一个类型,前面再加个sizeof作何解释?因为我们有见过unsigned intconst int等,那么sizeof int无法解释!因此,一句话,sizeof在计算变量所占字节大小时括号是可以省略的,而计算类型大小时括号不能省略。习惯的做法是在使用sizeof时候都加上括号,让它成为一个“披着函数皮的关键字”。

   函数求值是在运行时进行的,关键字sizeof求值是在编译时进行的。因此,若有程序中有下面的代码:

int a[5];

sizeof(a[5]);

程序依然是可以正确编译、链接和运行的,sizeof(a[5])=4。虽然并不存在a[5]这个元素,但是实际上并没有真正去访问a[5],而仅仅是根据数组元素的类型来确定其值的。

--------------------------------------------------------------------------------------------

二、sizeof是怎么计算的?

我们知道sizeof这个关键字是计算变量或者类型在内存中所占的字节的个数。那么先且看下面的程序例子:

#include<stdio.h>

struct {

       double a;

       char b;

       short c;

       char d;

}S_1;

 

struct {

       long a;

       char b;

       char d;

       short c;

}S_2;

 

struct{

       long a;

       short b;

       char d;

       int c;

}S_3;

 

struct{

       long a;

       short b;

       int c;

       char d;

}S_4;

 

struct{

       char a;

       short b;

       long c;

       char d;

       char e;

 

}S_5;

 

struct{

       long a;

       short b;

       long c;

       char d;

       char e;

}S_6;

 

int main()

{ 

       printf("&S_1.a=%d,&S_1.b=%d,&S_1.c=%d,&S_1.d=%d/n",&S_1.a,&S_1.b,&S_1.c,&S_1.d);

       printf("sizeof(S_1)=%d/n",sizeof(S_1));

       printf("sizeof(S_2)=%d/n",sizeof(S_2));

       printf("sizeof(S_3)=%d/n",sizeof(S_3));

       printf("sizeof(S_4)=%d/n",sizeof(S_4));

       printf("sizeof(S_5)=%d/n",sizeof(S_5));

       printf("sizeof(S_6)=%d/n",sizeof(S_6));

       return 0;            

}

在32位机器上用VC6.0编译后得到的运行结果为:

&S_1.a=4357680,&S_1.b=4357684,&S_1.c=4357686,&S_1.d=4357688

sizeof(S_1)=12

sizeof(S_2)=8

sizeof(S_3)=12

sizeof(S_4)=16

sizeof(S_5)=12

sizeof(S_6)=16

   细心观察上面的程序你会惊人地发现,S_1S_2两个结构体成员完全一样,唯一的差别仅仅是结构体内成员的定义顺序有所不同,可是sizeof的结果却居然不一样!这里面有什么秘密,难道入了魔道?可是现实就是这样!因为sizeof(S_3)sizeof(S_4)的命运也是这样的。这似乎在告诉我们,不要被外表所欺骗,表面上一致的东西实际上并不见得完全一致!

    产生这个现象的原因是编译器要考虑字节对齐情况!对结构体变量S_1来说,其成员变量所占字节分别为4121,但是观察上面程序打印出来的S_1的四个成员变量的地址我们发现a占了四个字节,b占一个,但是c是从4357686开始的,这就说明b后面的4357685是个空的,这个空的地址用0来补充,c占两个字节,bc合在一起刚好占4个字节,由于是4字节对齐情况,不足要补充4个字节,那么d后面空两个地址补充为0,故S_1总共占用12个字节。

     明白以上原因后,其他几个也就不难明白了。

--------------------------------------------------------------------------------------------

三、字节对齐的准则

    字节对齐是为了提高CPU的存取速度。对于结构体而言,当结构体内的元素的长度都小于处理器的位数的时候,以结构体里面最长的数据元素为对齐单位,即结构体的长度一定是最长的数据元素的整数倍;如果结构体内存在长度大于处理器位数的元素,就以处理器位数为对齐单位,但是结构体内类型相同的连续元素将在连续的空间内,和数组一样。字节对齐的细节和编译器实现相关,但一般而言,满足三个准则:

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailing padding)。   

--------------------------------------------------------------------------------------------

四、sizeof()和strlen()

1)sizeof操作符的结果类型是size_t,它在头文件中的typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。

2)sizeof是算符,strlen是函数。

3)sizeof可以用类型做参数,strlen只能用char *做参数,且必须是以"/0"结尾的。sizeof还可以用函数做参数,如short f(); printf("%d/n",sizeof(f()));输出的结果为2,即sizeof(short).

4)数组做sizeof的参数不退化,传递给strlen就退化为指针。

5)大部分编译程序在编译的时候就把sizeof计算过了,是类型或变量的长度。

6)strlen的结果是在运行的时候计算的,用来计算字符串的长度而不是类型占内存的大小。

7)sizeof后面如果是类型必须加括号,如果是变量名可以不加括号。这是因为sizeof是操作符而不是函数。

8)当使用了一个结构类型或变量时,sizeof返回实际的大小。当使用一静态的空间数组时,sizeof返回全部数组的尺寸。sizeof操作符不能返回被动态分配的数组或外部的数组的尺寸。

9)数组作为参数传递给函数时传的是指针而不是数组,传递的是数组的首地址,如fun(char [8])、fun(char [])都等价于fun(char *)。在C++里传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小。

10)计算结构变量的大小必须考虑数据对齐问题。

11)sizeof不能用于函数类型、不完全类型或位字段。不完全类型是指具有未知存储大小数据的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v[MAX]且MAX未知,sizeof(void)都不是正确形式。

看下面几个例子:

第一个例子:

char* ss = "0123456789";
sizeof(ss) 结果 4 ===》ss是指向字符串常量的字符指针
sizeof(*ss) 结果 1 ===》*ss是第一个字符


char ss[] = "0123456789";
sizeof(ss) 结果 11 ===》ss是数组,计算到/0位置,因此是10+1
sizeof(*ss) 结果 1 ===》*ss是第一个字符

char ss[100] = "0123456789";
sizeof(ss) 结果是100 ===》ss表示在内存中的大小 100×1
strlen(ss) 结果是10 ===》strlen是个函数内部实现是用一个循环计算到/0为止之前

int ss[100] = "0123456789";
sizeof(ss) 结果 400 ===》ss表示再内存中的大小 100×4
strlen(ss) 错误 ===》strlen的参数只能是char* 且必须是以''/0''结尾的

char q[]="abc";
char p[]="a/n";
sizeof(q),sizeof(p),strlen(q),strlen(p);
结果是 4 3 3 2    

第二个例子:

class X
{
int i;
int j;
char k;
};
X x;
cout<<sizeof(X)<<endl; 结果 12 ===》内存补齐
cout<<sizeof(x)<<endl; 结果 12 同上

第三个例子:

char szPath[MAX_PATH]
   如果在函数内这样定义,那么sizeof(szPath)将会是MAX_PATH,但是将szPath作为虚参声明时(void fun(char szPath[MAX_PATH])),sizeof(szPath)却会是4(指针大小) 。

--------------------------------------------------------------------------------------------

五、sizeof使用场合

1)sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如:

      void *malloc(size_t size), 
   size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream)

2)用它可以看看一类型的对象在内存中所占的单元字节。

     void * memset(void * s,int c,sizeof(s))

3)在动态分配一对象时,可以让系统知道要分配多少内存。

4)便于一些类型的扩充,在windows中就有很多结构内型就有一个专用的字段是用来放该类型的字节大小。
5)由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。

6)如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。

--------------------------------------------------------------------------------------------

此外下面几点特别需要引起注意:

 

1、sizeof是计算变量或类型在内存中所占字节数的个数,即计算栈中分配的大小,因此静态变量(static)和全局变量所在的静态存储区是不会计算在内的。因此如有:

class A{

public:

       int a;

       static int b;

       A();

};

sizeof(A)=4

2、空类的大小为1,多重继承的空类空间也为1,但是虚基类涉及到虚表(虚指针),因此其大小为4。如:

class A{ 

 

};

class A2{
 
};

class B:public A{
 
};

class C:public virtual B{
 
};

class D:public A,A2{
 
};

Sizeof(A)=1,sizeof(B)=1,sizeof(C)=4,sizeof(D)=1

3、注意抽象类中的虚函数和纯虚函数也要算空间,如:

class A{

void fun();

};

class B{

void fun1();

virtual void fun2();//virtual void fun2()=0;

};

Sizeof(A)=1,sizeof(B)=4,因为fun2是虚函数,涉及到指针问题。

4、指针(地址)的大小为4。如:char *b;有sizeof(b)=4。

 

参考资料

[1]《程序员面试宝典》

[2] http://baike.baidu.com/view/1078660.htm

 

转载请注明出处:http://blog.csdn.net/collier/archive/2010/09/04/5863365.aspx

原创粉丝点击