说了这么久的 C++ 终于说到类了,还是从内存出发来讨论一下 C++ 的类在内存中的存储方式(之前写过一篇内存对齐的文章,类同样在一定程度上遵循内存对齐原则,不过比结构体复杂一下)
如有侵权,请联系删除,如有错误,欢迎大家指正,谢谢
0.空类
class Test {
};
Test t0;
cout << sizeof(t0) << endl;
// 运行结果:1
解释:
- 空类,没有任何成员变量和成员函数,编译器是支持空类实例化对象的,对象必须要被分配内存空间才有意义,这里编译器默认分配了 1Byte 内存空间(不同的编译器可能不同)
1. 含有成员变量的类
// ====== 测试一 ======
class Test {
private:
int i;
char c;
double d;
};
Test t11;
cout << sizeof(t11) << endl;
// 运行结果:16
// ====== 测试二 ======
class A{};
class Test {
private:
int i;
char c;
double d;
A a;
};
Test t12;
cout << sizeof(t12) << endl;
// 运行结果:24
// ====== 测试三 ======
class A {
private:
double dd;
int ii;
int* pp;
};
class Test {
private:
int i;
A a;
double d;
char* p;
};
Test t13;
cout << sizeof(t13) << endl;
// x86目标平台运行结果:40;x64目标平台下运行结果:48
解释:
- 这里的类的内存对齐原则与前面写的结构体的内存对齐原则是一样的(不太了解的可以移步我之前的《C/C++中内存对齐问题的一些理解》查看)
- 测试三中,32bit 目标平台寻址空间是 4Byte(32bit),所以指针是 4Byte的;64bit 目标平台寻址空间是 8Byte(64bit),所以指针是 8Byte
- 另外,静态成员变量是在编译阶段就在静态区分配好内存的,所以静态成员变量的内存大小不计入类空间(不太了解C++内存分布的可以移步我之前写的《C/C++程序的内存分布》查看)
2.含有成员变量和成员函数的类
// ====== 测试一 ======
class Test {
private:
int n;
char c;
short s;
};
Test t21;
cout << sizeof(t21) << endl;
// 运行结果:8
// ====== 测试二 ======
class Test {
public:
Test() {
}
int func0() {
return n;
}
friend int func1();
int func2() const {
return s;
}
inline void func3() {
cout << "inline function" << endl;
}
static void func4() {
cout << "static function" << endl;
}
~Test() {
}
private:
int n;
char c;
short s;
};
int func1() {
Test t;
return t.c;
}
Test t22;
cout << sizeof(t22) << endl;
// 运行结果:8
// ====== 测试三 ======
class Test {
public:
Test() {
}
int func0() {
return n;
}
friend int func1();
int func2() const {
return s;
}
inline void func3() {
cout << "inline function" << endl;
}
static void func4() {
cout << "static function" << endl;
}
virtual void func5() {
cout << "virtual function" << endl;
}
~Test() {
}
private:
int n;
char c;
short s;
};
int func1() {
Test t;
return t.c;
}
Test t23;
cout << sizeof(t23) << endl;
// x86目标平台运行结果:12;x64目标平台下运行结果:16
解释:
- 因 C++中成员函数和非成员函数都是存放在代码区的,故类中一般成员函数、友元函数,内联函数还是静态成员函数都不计入类的内存空间,测试一和测试二对比可证明这一点
- 测试三中,因出现了虚函数,故类要维护一个指向虚函数表的指针,分别在 x86目标平台和x64目标平台下编译运行的结果可证明这一点
总结
- C++编译系统中,数据和函数是分开存放的(函数放在代码区;数据主要放在栈区和堆区,静态/全局区以及文字常量区也有),实例化不同对象时,只给数据分配空间,各个对象调用函数时都都跳转到(内联函数例外)找到函数在代码区的入口执行,可以节省拷贝多份代码的空间
- 类的静态成员变量编译时被分配到静态/全局区,因此静态成员变量是属于类的,所有对象共用一份,不计入类的内存空间
- 静态成员函数和非静态成员函数都是存放在代码区的,是属于类的,类可以直接调用静态成员函数,不可以直接调用非静态成员函数,两者主要的区别是有无this指针,更加详细的解释后面专门写一篇文章
- 内联函数(声明和定义都要加inline)也是存放在代码区,内联函数在被调用时,编译器会用内联函数的代码替换掉函数,避免了函数跳转和保护现场的开销(实际上到底替不替换还要由编译器决定,即使声明为内联函数也有可能不替换,未声明成内联函数也有可能被编译器替换到调用位置,主要由编译器决定),更详细的介绍后面也会专门写一篇文章
参考文章:
[1] C++成员函数在内存中的存储方式
[2] C++类的实例化对象的大小之sizeof() (该文章有一处错误,“如果是指针,则无论指针指向的是什么数据类型,都占4个字节的存储空间”,在x86目标平台下4Byte,在x64目标平台下8Byte)
如果未特殊说明,以上测试均是在win10 vs2017 64bit编译器下进行的