无继承
空类
class A { }; void test1() { A a; std::cout << "class A size:" << sizeof(a) << "\n"; A array_a[10]; std::cout << "class array a size:" << sizeof(array_a) << "\n"; }
从上图的输出结果可以看出,一个空类的大小并非想象中的0,而是1。
而且从上图中也可以看出,在数组情况下,其值为未初始化的默认栈值。
仅存在数据元素的类
class B { uint8_t var1; }; class C { uint16_t var1; }; class D { uint8_t var1; uint32_t var2; }; void test2() { B b; std::cout << "class B size:" << sizeof(b) << "\n"; C c; std::cout << "class C size:" << sizeof(c) << "\n"; D d; std::cout << "class D size:" << sizeof(d) << "\n"; }
从上图中可知,
当仅定义一个 uint8_t 类型时,所占用的空间和空类是相同的。
当定义的数据类型为 uint16_t 类型时,占用的空间与数据元素保持相同。
当同时定义多个数据类型时,则由于数据对齐的原因,导致数据大小大于了实际的数据类型的相加的总大小。
仅有普通方法的类
class E { public: void fun1() { } int fun2() { return 0; } }; void test3() { E e; std::cout << "class E size:" << sizeof(e) << "\n"; printf("e position:%08X\n", &e); printf("E::fun1 position:%08X\n", &E::fun1); printf("E::fun2 position:%08X\n", &E::fun2); }
从上述截图中可知,仅有普通方法的类,其大小并没有变化,与空类相同。
有虚函数的类
class F { public: void fun1() { } virtual int fun2() { return 0; } }; void test4() { F f; std::cout << "class F size:" << sizeof(f) << "\n"; printf("f position:%08X\n", &f); printf("F::fun1 position:%08X\n", &F::fun1); printf("F::fun2 position:%08X\n", &F::fun2); std::cout << "Hello World!\n"; }
从上图中输出的信息中可以看出,当函数具备虚函数时,则所占空间大小变为了4。同时从后面图中,可以得知,所占用的4个字节所存储的内容为vfptr,即虚函数表指针。通过比较前后函数的地址,可知虚函数表中的内容即为虚函数的地址。
之后我们继续添加虚函数的个数。
class F { public: void fun1() { } virtual int fun2() { return 0; } virtual int fun3() { return 0; } }; void test4() { F f; std::cout << "class F size:" << sizeof(f) << "\n"; printf("f position:%08X\n", &f); printf("F::fun1 position:%08X\n", &F::fun1); printf("F::fun2 position:%08X\n", &F::fun2); std::cout << "Hello World!\n"; }
通过继续添加虚函数的个数,可知,多个虚函数并不会影响类对象所占空间的大小。但却会影响虚函数表的内容。如第二个图中所示,虚函数表中的内容变为了两个函数指针。
多情况混合
class G { public: uint32_t var1; void fun1() { } virtual int fun2() { return 0; } virtual int fun3() { return 0; } uint32_t var2; }; void test5() { G g; std::cout << "class G size:" << sizeof(g) << "\n"; printf("g position:%08X\n", &g); printf("G::fun1 position:%08X\n", &G::fun1); printf("G::fun2 position:%08X\n", &G::fun2); std::cout << "Hello World!\n"; }
由上图可知,在几种元素都存在的情况下,数据元素会按照实际情况占用空间大小。虚函数存在时则会有一个固定4字节的空间用来存放虚函数表指针。
单继承
父类与子类仅有数据元素
class H { public: uint32_t var1; uint32_t var2; }; class I : public H { public : uint32_t var3; uint8_t var4; }; void test6() { I i; std::cout << "class I size:" << sizeof(i) << "\n"; std::cout << "Hello World!\n"; }
如上图所示可知,在这种情况下,父类数据元素会与子类数据元素进行顺序叠加,其中父类数据元素在子类数据元素之前。
父类与子类仅有普通方法
class J { public: void fun1() { } int fun2() { return 0; } }; class K : public J { public: void fun3() { } int fun4() { return 0; } }; void test7() { K k; std::cout << "class K size:" << sizeof(k) << "\n"; printf("k position:%p\n", &k); printf("J::fun1 position:%p\n", &J::fun1); printf("J::fun2 position:%p\n", &J::fun2); printf("K::fun3 position:%p\n", &K::fun3); printf("K::fun4 position:%p\n", &K::fun4); std::cout << "Hello World!\n"; }
可知,单纯的普通方法并不会影响到类对象的大小。无论是否继承。
父类无虚函数,子类有虚函数
与有虚函数的类相同结论。
父类有虚函数,子类无虚函数
class L { public: void fun1() { } virtual int fun2() { return 0; } }; class M : public L { public: void fun3() { } int fun4() { return 0; } }; void test8() { M m; std::cout << "class M size:" << sizeof(m) << "\n"; printf("m position:%p\n", &m); printf("L::fun1 position:%p\n", &L::fun1); printf("L::fun2 position:%p\n", &L::fun2); printf("M::fun3 position:%p\n", &M::fun3); printf("M::fun4 position:%p\n", &M::fun4); std::cout << "Hello World!\n"; }
如上图所示,在继承了虚函数后,则也会占据4个字节,用来存放虚函数表指针。其中虚函数表中存放了父类的虚函数表。
父类与子类均有虚函数
class N { public: void fun1() { } virtual int fun2() { return 0; } }; class O : public N { public: void fun3() { } virtual int fun4() { return 0; } }; void test9() { O o; std::cout << "class O size:" << sizeof(o) << "\n"; printf("o position:%p\n", &o); printf("N::fun1 position:%p\n", &N::fun1); printf("N::fun2 position:%p\n", &N::fun2); printf("O::fun3 position:%p\n", &O::fun3); printf("O::fun4 position:%p\n", &O::fun4); std::cout << "Hello World!\n"; }
从上面可知,父子均有虚函数时,父子类的虚函数表中会顺序存放两者的虚函数,父类的虚函数在前,子类的虚函数在后,同时会用0x00000000来结束虚函数表。
多种情况混合
class P { public: uint32_t var1; void fun1() { } virtual int fun2() { return 0; } }; class Q : public P { public: uint32_t var2; void fun3() { } virtual int fun4() { return 0; } }; void test10() { Q q; std::cout << "class q size:" << sizeof(q) << "\n"; printf("q position:%p\n", &q); printf("P::fun1 position:%p\n", &P::fun1); printf("P::fun2 position:%p\n", &P::fun2); printf("Q::fun3 position:%p\n", &Q::fun3); printf("Q::fun4 position:%p\n", &Q::fun4); std::cout << "Hello World!\n"; }
没有什么奇特的,同样都是最开始为虚函数表指针,之后为父类的数据元素,最后为子类的数据元素。虚函数表中,父类的虚函数在前,子类的虚函数在后。最后以0x00000000结束。
多继承
多种情况混合
class R { public: uint32_t var1; void fun1() { } virtual int fun2() { printf("class R fun2\n"); return 0; } virtual int fun3() { return 0; } }; class S { public: uint32_t var1; void fun1() { } virtual int fun2() { printf("class S fun2\n"); return 0; } }; class T : public R, public S { public: uint32_t var2; int fun3() { return 2; } virtual int fun4() { return 0; } }; void test11() { T t; std::cout << "class t size:" << sizeof(t) << "\n"; void* tmpt; R* rt; S* st; rt = &t; st = &t; tmpt = &t; // t.fun2(); rt->fun2(); st->fun2(); printf("R::fun1 position:%p\n", &R::fun1); printf("R::fun2 position:%p\n", &R::fun2); printf("R::fun3 position:%p\n", &R::fun3); printf("S::fun1 position:%p\n", &S::fun1); printf("S::fun2 position:%p\n", &S::fun2); printf("T::fun3 position:%p\n", &T::fun3); printf("T::fun4 position:%p\n", &T::fun4); std::cout << "Hello World!\n"; }
如图中可知,当在多继承中,如果父类中丢存在虚函数,则在最终的子类中,会按照顺序依次排布父类相关内容。
首先排布第一个父类的虚函数表指针,之后是第一个父类的数据元素。
然后排布的是第二个父类的虚函数表指针,之后是第二个父类的数据元素。
最后才是子类所定义的数据元素。
而子类中的虚函数则会存放在第一个父类的虚函数表中。结束时依旧以 0x00000000 结尾。
同时上面比较神奇的是,
rt = &t;
st = &t;
但是最终 rt 和 st的值并不相同。此处估计有个类型转换。