C++对象模型
gcc14.2.0 -std=gnu++11
在C++中,有两种class data members:static和nonstatic,以及三种class member functions:static、nonstatic和virtual。
我们需要了解一个具有不同类型成员的类,在内存中是如何存在的。
从一个问题开始
C++一个对象所占的内存空间是多大?
下面这个实验会打印出当前环境上下面数据结构的大小:
- int、long、double、float、char
- 一个struct类型
- 一个class类型
- 只有数据成员的class
- 只有静态数据成员的class
- 只有非虚成员函数的class
- 存在非虚成员函数的class
- 存在虚成员函数的class
- 存在静态成员函数的class
头文件:
#ifndef _CPP_TEST_CLASS_H
#define _CPP_TEST_CLASS_H
struct S_normal {
int a;
double b;
float c;
char d;
long e;
};
struct S {
int a;
double b;
float c;
char d;
int e : 4;
int f : 2;
long g;
};
struct S_empty {};
class C_empty
{};
class C_normal
{
public:
int a;
double b;
private:
float c;
char d;
long e;
};
class C
{
public:
int a;
double b;
private:
float c;
char d;
int e : 4;
int f : 2;
long g;
};
class C_static
{
public:
static int a;
private:
static int b;
static const int c{3};
};
class C_onlynormal_function
{
public:
void foo();
void bar();
};
class C_novirtual_function
{
public:
C_novirtual_function();
int a;
double b;
void print();
private:
float c;
char d;
int e : 4;
int f : 2;
long g;
};
class C_onlyonevirtual_function
{
public:
virtual void foo();
};
class C_onlyvirtual_function
{
public:
virtual void foo();
virtual void bar();
};
class C_c
{
public:
C_c();
int a;
double b;
void print();
virtual void vprint();
private:
static int x;
float c;
char d;
int e : 4;
int f : 2;
long g;
};
class C_static_function
{
public:
C_static_function();
int a;
double b;
void print();
virtual void vprint();
static void foo();
private:
static int x;
float c;
char d;
int e : 4;
int f : 2;
long g;
};
void test_size();
#endif
测试文件
#include "test_class.h"
#include <iostream>
int C_static::a = 1;
int C_static::b = 2;
int C_c::x = 123;
void C_onlynormal_function::foo()
{
std::cout << "FOO" << std::endl;
}
void C_onlynormal_function::bar()
{
std::cout << "bar" << std::endl;
}
C_novirtual_function::C_novirtual_function() :
a(1), b(2), c(3), d('4'), e(5), f(1), g(9)
{}
void C_novirtual_function::print()
{
std::cout << a << ";" << b << ";" << c << ";" << d << ";" << e << ";" << f
<< ";" << g << ";" << std::endl;
}
C_c::C_c() : a(1), b(2), c(3), d('4'), e(5), f(1), g(9)
{}
void C_c::print()
{
std::cout << a << ";" << b << ";" << c << ";" << d << ";" << e << ";" << f
<< ";" << g << ";" << C_c::x << std::endl;
}
void C_c::vprint()
{
std::cout << a << ";" << b << ";" << c << ";" << d << ";" << e << ";" << f
<< ";" << g << std::endl;
}
C_static_function::C_static_function() :
a(1), b(2), c(3), d('4'), e(5), f(1), g(9)
{}
void C_static_function::print()
{
std::cout << a << ";" << b << ";" << c << ";" << d << ";" << e << ";" << f
<< ";" << g << ";" << C_static_function::x << std::endl;
}
void C_static_function::vprint()
{
std::cout << a << ";" << b << ";" << c << ";" << d << ";" << e << ";" << f
<< ";" << g << std::endl;
}
void C_static_function::foo()
{
std::cout << "foo" << std::endl;
}
void C_onlyonevirtual_function::foo()
{
std::cout << "foo" << std::endl;
}
void C_onlyvirtual_function::foo()
{
std::cout << "foo" << std::endl;
}
void C_onlyvirtual_function::bar()
{
std::cout << "bar" << std::endl;
}
void test_size()
{
S_normal s0{1, 2, 3, '4'};
S s1;
S_empty s2;
C_normal c0;
C c1;
C_static c2;
C_onlynormal_function c3;
C_novirtual_function c4;
C_onlyonevirtual_function c5;
C_onlyvirtual_function c6;
C_c c7;
C_static_function c8;
C_empty c9;
std::cout << "int size: " << sizeof(int) << std::endl;
std::cout << "long size: " << sizeof(long) << std::endl;
std::cout << "double size: " << sizeof(double) << std::endl;
std::cout << "float size: " << sizeof(float) << std::endl;
std::cout << "char size: " << sizeof(char) << std::endl;
std::cout << "S_normal:" << sizeof(S_normal) << ":" << sizeof(s0)
<< std::endl;
std::cout << "S:" << sizeof(S) << ":" << sizeof(s1) << std::endl;
std::cout << "S_empty:" << sizeof(S_empty) << ":" << sizeof(s2)
<< " align: " << alignof(S_empty) << std::endl;
std::cout << "C_normal:" << sizeof(C_normal) << ":" << sizeof(c0)
<< std::endl;
std::cout << "C:" << sizeof(C) << ":" << sizeof(c1) << std::endl;
std::cout << "C_static:" << sizeof(C_static) << ":" << sizeof(c2)
<< std::endl;
std::cout << "C_onlynormal_function:" << sizeof(C_onlynormal_function)
<< ":" << sizeof(c3) << std::endl;
std::cout << "C_novirtual_function:" << sizeof(C_novirtual_function) << ":"
<< sizeof(c4) << std::endl;
std::cout << "C_onlyonevirtual_function:"
<< sizeof(C_onlyonevirtual_function) << ":" << sizeof(c5)
<< std::endl;
std::cout << "C_onlyvirtual_function:" << sizeof(C_onlyvirtual_function)
<< ":" << sizeof(c6) << std::endl;
std::cout << "C_c:" << sizeof(C_c) << ":" << sizeof(c7) << std::endl;
std::cout << "C_static_function:" << sizeof(C_static_function) << ":"
<< sizeof(c8) << std::endl;
std::cout << "C_empty:" << sizeof(C_empty) << ":" << sizeof(c9)
<< " align: " << alignof(C_empty) << std::endl;
}
在我的环境上执行test_size()函数,输出结果如下:
int size: 4
long size: 8
double size: 8
float size: 4
char size: 1
S_normal:32:32
S:32:32
S_empty:1:1 align: 1
C_normal:32:32
C:32:32
C_static:1:1
C_onlynormal_function:1:1
C_novirtual_function:32:32
C_onlyonevirtual_function:8:8
C_onlyvirtual_function:8:8
C_c:40:40
C_static_function:40:40
C_empty:1:1 align: 1
从上面结果可以得到以下一些结果:
- struct和class在内存大小布局上没区别
- public、private这种访问控制符对内存大小没影响
- 存在字节对齐
- 静态成员变量不占用类内空间
- 非虚成员函数不占用类内空间
- 虚成员函数占用类内固定大小内存空间,不随虚函数数量增加而增加
- 静态成员函数不占用类内空间
- 当类中类有定义任何变量的时候,类的对象都是1个字节的,当类中没有任何变量的时候,这个类里没有任何真正的成员变量,所以大小应该是0,但0大小不好在内存中定位一个地址,所以,就规定它大小为0的对象要占一字节空间,以便让它拥有一个合法的地址。如果是有派生类的,还有考虑到内存对齐的问题的。
需要多少内存才能够表现一个class object:
- 其nonstatic data members的总和大小
- 加上任何由于字节对齐的需求而填补(padding)上去的空间(可能存在于members之间,也可能存在于集合体边界)
- 加上为了支持virtual而由内部产生的任何额外负担(overhead)
C++对象模型
在此模型中,非静态数据成员被配置在每一个类对象内,静态数据成员则被存放在类对象之外。静态和非静态成员函数也被放在类对象之外。虚函数通过下面两个步骤支持:
- 每一个class产生出一堆指向虚函数的指针,放在一个表之中,这个表称为virtual table(vtbl)
- 每一个类对象被安插一个指针,指向相关的virtual table。通常这个指针被称为vptr。vptr的设定(setting)和重置(resetting)都由每一个class的构造函数、析构函数和赋值运算符自动完成。每个class所关联的type_info object(支持runtime type indentification,RTTI)也经由virtual table被指出来,通常放到表格的第一个slot。

struct
C struct在C++中的一个合理用途,是当你要传递“一个复杂的class object的全部或部分”到某个C函数去时,struct声明可以将数据封装起来,并保证拥有与C兼容的空间布局。
这项保证只在组合的情况下才存在,如果是继承的话,编译器会决定是否应该有额外的data members被安插到base struct subobject中。
多态
C++以下列的方法支持多态:
- 经由一组隐式的转化操作。例如把一个derived class指针转化为一个指向其public base type的指针
- 经由虚函数机制
- 经由dynamic_cast和typid运算符
多态的主要用途是经由一个共同的接口来影响类型的封装,这个接口通常被定义在一个抽象的base class中。这个共享接口是以虚函数机制引发的,它可以在执行期根据object的真正类型解析出到底是哪一个函数实例被调用。

浙公网安备 33010602011771号