#include<iostream>
using namespace std;
class classA
{
private:
intA;
intB;
voidprin1() {}
voidprin2() {}
virtualvoid prin3() { }
};
class ClassB : public ClassA
{
public:
intC;
intD;
voidprin4() { }
voidprin5() { }
virtual void prin6() { }
};
intmain(intargc, char*
argv[]) {
cout<<sizeof(ClassA)<<endl; // 16
cout<<sizeof(ClassB)<<endl; //24
return0;
}
测试环境:
Ubuntu 12.04.5 LTS x86_64 GNU/Linux
g++ (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3 编译参数 -m64
结果是
homer@homer-pc:~/Desktop$ g++ testMem.c -o testMem && ./testMem
16
24
结果为什么是这样?
32位系统int占4个字节,64位系统int也占4个字节(不是8个字节),而一个类中所有的虚函数通过一个虚函数指针管理,类对象的大小只包含这个vptr指针(在64位机器上指针sizeof为8个字节),其他虚函数是放在别的内存空间中管理,vptr指针在64位机器上是8个字节大小(32位机器上是4个字节)。注意到普通成员函数并不占类对象的大小空间,因为普通成员函数通过this指针管理,一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。
sizeof(ClassA)
1)int A 和 int B 各占4个字节,考虑64位机器编译器对其规则,合并为8个字节
2)virtual void prin3() 虚函数的vptr指针,在64位机器编译器上占8个字节
3)合计 sizeof(ClassA)为 8 + 8 = 16个字节
this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。这个this指针会因编译器不同而有不同的放置位置,可能是栈,也可能是寄存器,甚至全局变量。
子类其实不管如何继承,用sizeof()算该类的大小都会把父类中的私有成员变量所占的空间算进去,也就是说,私有变量也在子类中分配了内存,但你却不可以直接访问,这起到一个保护作用,这如同一个珠宝,共有继承就是开放性的展览,而私有继承是把珠宝锁起来,你却不能动,要动珠宝如果有管家(基类的public中定义了一些对其私有变量操作的成员函数,)只能让管家帮你代劳。
sizeof(ClassB)
1)int A 和 int B 各占4个字节,是父类ClassA中的私有变量,合并占用8个字节
2)virtual void prin3() 虚函数的vptr指针,父类ClassA在子类中不会分配空间
3)int C 和 int D 各占4个字节,在子类中会分配空间,合并占用8个字节
4)virtual void prin6() 虚函数的vptr指针,在64位机器编译器上占8个字节
5)合计 sizeof(ClassB)为 8 + 8 + 8 = 24个字节
注明: 上述示例在32位编译器上测试,sizeof(ClassA)和sizeof(ClassB)分别为12和20字节(虚函数指针占用空间少了4字节)
只有虚函数会占用一个指针大小的内存,原因是系统用一个指针维护这个类的虚函数表,并且注意这个虚函数无论含有多少项(类中含有多个虚函数)都不会影响类的大小。
知识延伸:
1) 空 ClassA(验证空Class占用空间大小)
class ClassA
{
private:
voidprin1() { }
voidprin2() { }
};
cout<<sizeof(ClassA)<<endl; // 1
2) ClassA 只有一个char(验证编译器对其规则)
class ClassA
{
private:
charC;
voidprin1() { }
voidprin2() { }
};
cout<<sizeof(ClassA)<<endl; // 1
3)ClassA 有一个char和一个虚函数(或一个long型)(验证编译器对其规则二)
classClassA
{
private:
charC;
voidprin1() { }
voidprin2() { }
virtualvoid prin3() { }
};
cout<<sizeof(ClassA)<<endl; // 16
4)ClassA 有一个char和一个虚函数(或一个long型),且对调char和虚函数的先后顺序(验证编译器对其规则三)
class ClassA
{
voidprin1() { }
voidprin2() { }
virtualvoid prin3() { }
private:
charC;
};
cout<<sizeof(ClassA)<<endl; // 16
5)ClassA 有一个char,一个int,一个虚函数(或一个long型)(验证编译器对其规则四)
class ClassA
{
private:
charC;
intA;
voidprin1() { }
voidprin2() { }
virtualvoid prin3() { }
};
cout<<sizeof(ClassA)<<endl; // 16
6)ClassA 有一个char,一个int(验证编译器对其规则五)
class ClassA
{
private:
charC;
intA;
voidprin1() { }
voidprin2() { }
};
cout<<sizeof(ClassA)<<endl; // 8
7)ClassA 只有一个int(验证编译器对其规则六)
class ClassA
{
private:
intA;
voidprin1() { }
voidprin2() { }
};
cout<<sizeof(ClassA)<<endl; // 4
总结如下:
1) 空类的 sizeof 为1个字节
2) 只有一个char的类,sizeof为一个字节
3) 类中含有char和虚函数,将以最大的变量或指针为编译器对齐规则,例如:虚函数指针占8个字节(64位编译器),则char虽然只占1个字节,但对齐后空余了7个字节,合并类占8(指针) + 1(char) + 7(对齐的空字节) = 16个字节
4) 对齐规则,跟变量或虚函数的先后顺序无关,只跟最大变量类型或函数指针有关,函数指针跟编译器最大对齐位数有关(不太好理解,请继续往下看)
5) char和int合占8个字节,虚函数指针占8个字节,且以最大的虚函数指针的8字节对其,其中char占一个空余3个字节合并占4个,int占4个字节,按8位规则对齐,合计16字节
6) 一个char和int合并占8个字节,无虚函数,此时以最大变量类型int对齐,因此char占1字节空3字节占4字节
7)一个int占4位,自己便是最大的对齐规则; 2)一个char同理也占1个字节,此处跟编译器最大64位对齐规则无关(即一个int或一个char,不会拓展到占用8字节)
这里,又延伸出了一个很有意思的问题,空类sizeof为什么不为0,而为1?
回答这个问题,需要回到编译器和类的实例化问题上来,在学校看过《深度探索C++对象模型》,类分抽象类和普通类,空类属于普通类,是可以被实例化的。
每个类的实例,在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,因此1)中的空类ClassA默认会占用1个字节。