1.空类
类本身是没有大小的。类的大小指的是类的对象所占的大小。如果用 sizeof 运算符对一个类型名操作,得到的是具有该类型实体的大小。
#include<iostream>
using namespace std;
class A
{
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
return 0;
}
C++标准指出,不允许一个对象的大小为 0,不同的对象不能具有相同的地址。这是由于:
- new 需要分配不同的内存地址,不能分配内存大小为 0 的空间
- 避免除以 sizeof(T) 时出现除以 0 的错误
因此,空类的 sizeof 为 1 的原因是为了让对象的实例能够相互区别。从上面代码可以看出,空类同样能够被实例化,所以编译器会给空类隐含地添加一个字节,这样空类实例化之后就会拥有独一无二的内存地址。
一旦空类中有其他的占用空间的成员,那么这 1 个字节就不计算在内,例如一个类只有一个 int 则占用 4 字节而不是 5 字节。
#include<iostream>
using namespace std;
class A
{
int x;
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
return 0;
}
2.一个类继承空类
当一个类继承空类时,空基类的一个字节并不会加到子类中去。
#include<iostream>
using namespace std;
class A
{
};
class B : public A
{
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
B objb;
cout << sizeof(objb) << endl;
return 0;
}
#include<iostream>
using namespace std;
class A
{
};
class B : public A
{
int x;
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
B objb;
cout << sizeof(objb) << endl;
return 0;
}
3.空类作为另一个类的成员
当空类作为另一个类的成员时,空类的一个字节会计算在内。
然而,你会发现下面代码在大多数编译器中,B 的 sizeof 为 8。这是由于空类的大小虽然为 1,但是为了内存对齐,编译器会额外加上一些字节,使其被放大到足够可以存放一个 int。
#include<iostream>
using namespace std;
class A
{
};
class B
{
int x;
A a;
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
B objb;
cout << sizeof(objb) << endl;
return 0;
}
4.非静态成员变量
非静态的成员变量(即普通成员变量)跟着类对象走,是包含在每个对象中的,占用对象的内存空间,即每个类对象都有自己的成员变量。
#include<iostream>
using namespace std;
class A
{
public:
int x;
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
return 0;
}
5.静态成员变量
静态的成员变量不保存在对象内部,所占用的内存空间和类对象无关。
#include<iostream>
using namespace std;
class A
{
public:
static int x;
static int y;
};
int A::x = 0;
int A::y = 0;
int main()
{
A obja;
cout << sizeof(obja) << endl;
return 0;
}
6.非静态成员函数和静态成员函数
不论是静态的成员函数,还是非静态的成员函数,虽然写在类的定义中,但是不占用类对象的内存空间。不管有几个成员函数,都不影响对象的 sizeof。
#include<iostream>
using namespace std;
class A
{
public:
void fun1() {};
void fun2() {};
void fun3() {};
static void sfun() {};
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
return 0;
}
7.构造函数和析构函数
构造函数和析构函数不影响类对象的 sizeof。
#include<iostream>
using namespace std;
class A
{
public:
A() {};
~A() {};
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
return 0;
}
8.虚函数
首先需要明确一点,不管什么类型的指针 char *p 、int *p 占用的内存大小是固定的,在 x86 中是 4 字节,在 x64 中是 8 字节。
#include<iostream>
using namespace std;
int main()
{
cout << sizeof(char*) << endl;
cout << sizeof(int*) << endl;
return 0;
}
类中有一个虚函数,那么这个类就会产生一个指向虚函数的指针;类中有两个虚函数,那么这个类就会产生两个指向虚函数的指针。
类本身指向虚函数的一个或一堆指针存放在一个表格里,这个表格被称为虚函数表(vtbl)。这个虚函数表一般是保存在可执行文件中的,在程序执行的时候载入到内存中来。虚函数表是基于类的,跟着类走的,和对象没有关系。
因为虚函数的存在,系统会往类对象中添加一个指针,这个指针正好指向这个虚函数表,这个指针被称为虚函数表指针(vptr)。vptr 的值由系统在适当的时机赋值,比如在构造函数中通过增加额外的代码来赋值。
因此,虚函数本身不计算在类对象的 sizeof 内,但是虚函数会让类对象的 sizeof 增加 4 个字节(x86)以容纳虚函数表指针。注意,不管有几个虚函数,类对象的 sizeof 都只增加 4 个字节(x86)。
#include<iostream>
using namespace std;
class A
{
public:
virtual void fun1() {};
virtual void fun2() {};
virtual void fun3() {};
virtual void fun4() {};
};
int main()
{
A obja;
cout << sizeof(obja) << endl;
return 0;
}
9.总结
影响类对象大小的因素:
- 普通成员变量
- 虚函数:因为虚函数会让类对象的 sizeof 增加 4 个字节(x86)以容纳虚函数表指针。
- 继承(单一继承,多重继承,重复继承,虚继承)
- 字节对齐:如果有多个数据成员,那么为了提高访问速度,某些编译器可能会将数据成员之间的内存占用比例进行调整。
不影响类对象大小的因素:
分析下面代码中类对象的大小:
#include<iostream>
using namespace std;
class myobject
{
public:
myobject() {};
virtual ~myobject() {};
float getvalue() const
{
return m_value;
}
static int s_getcount()
{
return ms_scount;
}
virtual void vfrandfunc() {};
protected:
float m_value;
static int ms_scount;
};
int myobject::ms_scount = 0;
int main()
{
myobject obj;
cout << sizeof(obj) << endl;
return 0;
}
|