C++ sizeof使用

来源:互联网 发布:罗马龟甲阵 知乎 编辑:程序博客网 时间:2024/06/03 23:43

说明:以下代码在VS2008中通过,在32位操作系统下。

1.      定义

sizeof是一个操作符(operator)。

其作用是返回一个对象或类型所占的内存字节数。

其返回值类型为size_t。(size_t在头文件stddef.h中定义,它依赖于编译系统的值,一般定义为 typedef unsigned int size_t;)

2.      语法

sizeof有三种语法形式:

1)  sizeof (object);  //sizeof (对象)

2)  sizeof object;   //sizeof 对象

3)  sizeof (type_name);  //sizeof (类型)

对象可以是各种类型的变量,以及表达式(一般sizeof不会对表达式进行计算)。

sizeof对对象求内存大小,最终都是转换为对对象的数据类型进行求值。

sizeof (表达式); //值为表达式的最终结果的数据类型的大小

 

例子:(32位机器下)

[cpp] view plaincopy
  1. int i;  
  2. sizeof(int); //值为4  
  3. sizeof(i); //值为4,等价于sizeof(int)  
  4. sizeof i; //值为4  
  5. sizeof(2); //值为4,等价于sizeof(int),因为2的类型为int  
  6. sizeof(2 + 3.14); //值为8,等价于sizeof(double),因为此表达式的结果的类型为double  

 

[cpp] view plaincopy
  1. char ary[sizeof(int) * 10]; //OK,编译无误  
  

最新的C99标准规定sizeof也可以在运行时刻进行计算。

如下面的程序在Dev-C++中可以正确执行:

[cpp] view plaincopy
  1. int n;  
  2.   
  3. n = 10; // n动态赋值  
  4.   
  5. char ary[n]; // C99也支持数组的动态定义  
  6.   
  7. cout<<sizeof(ary); // ok. 输出10  

但在没有完全实现C99标准的编译器中就行不通了,上面的代码在VC6中就通不过编译。所以我们最好还是认为sizeof是在编译期执行的,这样不会带来错误,让程序的可移植性强些。

1.      基本数据类型的sizeof

这里的基本数据类型是指short、int、long、float、double这样的简单内置数据类型。

由于它们的内存大小是和系统相关的,所以在不同的系统下取值可能不同。

2.      结构体的sizeof

结构体的sizeof涉及到字节对齐问题。

为什么需要字节对齐?计算机组成原理教导我们这样有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,依次类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。

字节对齐的细节和编译器的实现相关,但一般而言,满足三个准则:

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

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

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

    注意:空结构体(不含数据成员)的sizeof值为1。试想一个“不占空间“的变量如何被取地址、两个不同的“空结构体”变量又如何得以区分呢,于是,“空结构体”变量也得被存储,这样编译器也就只能为其分配一个字节的空间用于占位了。

例子:

[cpp] view plaincopy
  1. struct S1  
  2. {  
  3.     char a;  
  4.     int b;  
  5. };  
  6. sizeof(S1); //值为8,字节对齐,在char之后会填充3个字节。  
  7.   
  8. struct S2  
  9. {  
  10.     int b;  
  11.     char a;  
  12. };  
  13. sizeof(S2); //值为8,字节对齐,在char之后会填充3个字节。  
  14.   
  15. struct S3  
  16. {  
  17. };  
  18. sizeof(S3); //值为1,空结构体也占内存。  

3.      联合体的sizeof

结构体在内存组织上市顺序式的,联合体则是重叠式,各成员共享一段内存;所以整个联合体的sizeof也就是每个成员sizeof的最大值。

例子:

[cpp] view plaincopy
  1. union u  
  2. {  
  3.     int a;  
  4.     float b;  
  5.     double c;  
  6.     char d;  
  7. };  
  8.   
  9. sizeof(u); //值为8  

4.      数组的sizeof

数组的sizeof值等于数组所占用的内存字节数。

注意:1)当字符数组表示字符串时,其sizeof值将’/0’计算进去。

            2)当数组为形参时,其sizeof值相当于指针的sizeof值。 

例子1:

[c-sharp] view plaincopy
  1. char a[10];  
  2. char n[] = "abc";   
  3.   
  4. cout<<"char a[10]                 "<<sizeof(a)<<endl;//数组,值为10  
  5.   
  6. cout<<"char n[] = /"abc/"           "<<sizeof(n)<<endl;//字符串数组,将'/0'计算进去,值为4  
  7.   
  8.    

例子2:

[c-sharp] view plaincopy
  1. void func(char a[3])  
  2. {  
  3.     int c = sizeof(a); //c = 4,因为这里a不在是数组类型,而是指针,相当于char *a。  
  4. }  
  5.   
  6. void funcN(char b[])  
  7. {  
  8.     int cN = sizeof(b); //cN = 4,理由同上。  
  9. }  

考虑下面问题: 

char a[] = "abcdef";
int b[20] = {3, 4};
char c[2][3] = {"aa", "bb"};

cout<<sizeof(a)<<endl; // 7
cout<<sizeof(b)<<endl; // 20*4
cout<<sizeof(c)<<endl; // 6


 
 数组a的大小在定义时未指定,编译时给它分配的空间是按照初始化的值确定的,也就是7。c是多维数组,占用的空间大小是各维数的乘积,也就是6。可以看出,数组的大小就是他在编译时被分配的空间,也就是各维数的乘积*数组元素的大小。 

  结论:数组的大小是各维数的乘积*数组元素的大小。 

  这里有一个陷阱: 

int *d = new int[10]; 
cout<<sizeof(d)<<endl; // 4


 
 d是我们常说的动态数组,但是他实质上还是一个指针,所以sizeof(d)的值是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。


5.     类sizeof

类是面向对象程序设计的核心,它实际是一种新的数据类型,也是实现抽象类型的工具,因为类是通过抽象数据类型的方法来实现的一种数据类型。类是对某一类对象的抽象;而对象是某一种类的实例,因此,类和对象是密切相关的。没有脱离对象的类,也没有不依赖于类的对象。

类的sizeof大小一般是类中的所有成员的sizeof大小之和,这个就不用多说。
不过有两点需要注意:

1)当类中含有虚成员函数的时候,例如:
class B
{
float a;
public:
virtual void fun(void);
}
此时sizeof(B)的大小为8,而不是4。因为在类中隐藏了一个指针,该指针指向虚函数表,正因为如此,
使得C++能够支持多态,即在运行时绑定函数的地址。
2)另一个要注意的是,当类中没有任何成员变量,也没有虚函数的时候,该类的大小是多少呢?
例如:
class B2
{
void fun(void);
}
此时sizeof(B2)的值是多少呢?在C++早期的编译器中,这个值为0;然而当创建这样的对象时,
它们与紧接着它们后面的对象有相同的地址。比如:
B2 b2;
int a;
那么对象b2与变量a有相同的地址,这样的话对对象b2地址的操作就会影响变量a。所以在现在大多数编译器中,该值的大小为1。
如果有虚函数,则sizeof值为类的数据成员的大小加上VTBL(指针,4字节),再加上其基类的数据成员的大小。如果是多重继承,还得加上各基类的VTBL。 

虚继承之单继承的内存布局

先看一段代码
class A
{
      virtual aa(){};
};

class B : public virtual  A
{
      char j[3];                                    //加入一个变量是为了看清楚class中的vfptr放在什么位置
      public:
            virtual bb(){};
};
class C : public virtual B
{
      char i[3];
      public:
            virtual cc(){};
};

这次先不给结果,先分析一下,也好加强一下印象。
1、对于class A,由于只有一个虚函数,那么必须得有一个对应的虚函数表,来记录对应的函数入口地址。同时在class A的内存空间中之需要有个vfptr_A指向该表。sizeof(A)也很容易确定,为4。
2、对于class B,由于class B虚基础了class A,同时还拥有自己的虚函数。那么class B中首先拥有一个vfptr_B,指向自己的虚函数表。还有char j[3],做一次alignment,一般大小为4。可虚继承该如何实现咧?首先要通过加入一个虚l类指针(记vbptr_B_A)来指向其父类,然后还要包含父类的所有内容。有些复杂了,不过还不难想象。sizeof(B)= 4+4+4+4=16(vfptr_B、char j[3]做alignment、vbptr_B_A和class A)。
3、在接着是class C了。class C首先也得有个vfptr_C,然后是char i[3],然后是vbptr_C_B,然后是class B,所以sizeof(C)=4+4+4+16=28(vfptr_C、char i[3]做alignment、vbptr_C_B和class B)。

在VC 6.0下写了个程序,把上面几个类的大小打印出来,果然结果为4、16、28。

VC中虚继承的内存布局——单继承
画了个图,简单表示一下我跟踪后的结果


虚基础之单继承时的内存布局图

class A的情况太简单,没问题。从class B的内存布局图可以得出下面的结论。
1、vf_ptr B放在了类的首部,那么如果要想直接拿memcpy完成类的复制是很危险的,用struct也是不行的。

2、vbtbl_ptr_B,为什么不是先前我描述的vbptr_B_A呢?因为这个指针与我先前猜测的内容有很大区别。这个指针指向的是class B的虚类表。看看VB table,VB table有两项,第一项为FFFFFFFC,这一项的值可能没啥意义,可能是为了保证虚类表不为空吧。第二项为8,看起来像是class B中的class A相对该vbtbl_ptr_B的位移,也就是一个offset。类似的方法在C++ Object Model(P121)有介绍,可以去看看。
class C的内存布局就比较复杂了,不过它的内存布局也更一步说明我对vbtbl_ptr_B中的内容,也就是虚类表的理解是正确的。不过值得关注的是class B中的class A在布局时被移到前面去了,虽然整个大小没变,但这样一来如果做这样的操作  C c; B *b;b=&c;时b的操作如何呢?此时只要从c的虚类表里获得class B的位置既可赋值给b。但是在构建class C时会复杂一些,后面的使用还是非常简单的,效率也比较高。class A的内存布局被前移可能是考虑倒C的虚继承顺序吧。

结论
1、VC在编译时会把vfptr放到类的头部;
2、VC采用虚表指针(vbtbl_ptr)来确定某个类所继承的虚类。
3、VC会重新调整虚继承的父类在子类中内存布局。(具体规则还不清楚)
4、VC中虚类表中的第一项是无意义的,可能是为了保证sizeof(虚类表)!=0;后面的内容为父类在子类中相对该虚类表指针的偏移量。

还有一些具体细节可参考:

http://www.360doc.com/content/12/0315/17/3349869_194600377.shtml

6.      指针的sizeof

指针是用来记录另一个对象的地址,所以指针的内存大小当然就等于计算机内部地址总线的宽度。

在32位计算机中,一个指针变量的返回值必定是4。

指针变量的sizeof值与指针所指的对象没有任何关系。

      例子:

  

[cpp] view plaincopy
  1. char *b = "helloworld";  
  2. char *c[10];  
  3. double *d;  
  4. int **e;  
  5. void (*pf)();    
  6.   
  7. cout<<"char *b = /"helloworld/"     "<<sizeof(b)<<endl;//指针指向字符串,值为4  
  8. cout<<"char *b                    "<<sizeof(*b)<<endl; //指针指向字符,值为1  
  9. cout<<"double *d                  "<<sizeof(d)<<endl;//指针,值为4  
  10. cout<<"double *d                  "<<sizeof(*d)<<endl;//指针指向浮点数,值为8  
  11. cout<<"int **e                  "<<sizeof(e)<<endl;//指针指向指针,值为4  
  12. cout<<"char *c[10]                "<<sizeof(c)<<endl;//指针数组,值为40  
  13. cout<<"void (*pf)();              "<<sizeof(pf)<<endl;//函数指针,值为4 

7.      函数的sizeof

sizeof也可对一个函数调用求值,其结果是函数返回值类型的大小,函数并不会被调用。

对函数求值的形式:sizeof(函数名(实参表))

注意:1)不可以对返回值类型为空的函数求值。 

            2)不可以对函数名求值。

           3)对有参数的函数,在用sizeof时,须写上实参表。

      例子:

[cpp] view plaincopy
  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. float FuncP(int a, float b)  
  5. {  
  6.     return a + b;  
  7. }  
  8.   
  9. int FuncNP()  
  10. {  
  11.     return 3;  
  12. }  
  13.   
  14. void Func()  
  15. {  
  16. }  
  17.   
  18. int main()  
  19. {  
  20. cout<<sizeof(FuncP(3, 0.4))<<endl; //OK,值为4,sizeof(FuncP(3,0.4))相当于sizeof(float)  
  21. cout<<sizeof(FuncNP())<<endl; //OK,值为4,sizeof(FuncNP())相当于sizeof(int)  
  22. /*cout<<sizeof(Func())<<endl; //error,sizeof不能对返回值为空类型的函数求值*/  
  23. /*cout<<sizeof(FuncNP)<<endl; //error,sizeof不能对函数名求值*/  
  24. }  
0 0
原创粉丝点击