八股文底层 Day1

C++对象模型

12e443jd.vjg.png|300

组件类型 存储位置 是否占用对象空间
非静态数据成员 对象内部 ✔️ 是
虚函数表指针(vptr) 对象头部(通常) ✔️ 是
普通成员函数 代码段(.text) ❌ 否
虚函数 代码段(.text) ❌ 否
虚函数表(vtable) 只读数据段(.rodata) ❌ 否
静态数据成员 全局/静态数据段 ❌ 否
静态成员函数 代码段(.text) ❌ 否

没有虚函数(平凡类型)

  • 指针类型是为了在解引用的时候确认读取多少个字节
  • 结构体和类在C++的内存实现上一样的,只是在编译器层面添加类权限访问控制
  • 结构体或者类,本质上类似与数组相同,是一块连续的内存,可以进行强制类型转换。访问结构体或者类的成员的时候,相当于根据成员的类型来计算偏移量
  • 在继承的时候,根据子类继承声明的顺序,将多个父类的成员添加到子类成员之前
  • 成员函数只是在一般函数的基础上由编译器隐式的在参数列表添加this指针

有虚函数

  • 在类顶部中隐式添加一个指向虚函数表的指针,会与成员变量进行内存对齐
  • 包含虚函数的对象的函数地址大小为2个void*,非虚函数前半部分表示函数地址,后半部分填充0,虚函数前半部分为函数在虚函数表的偏移量,后半部分为函数地址

继承模型

1. 单继承模型

1
2
3
4
5
6
7
8
9
class Base {
int base_data;
virtual void vfunc() {}
};

class Derived : public Base {
int derived_data;
void vfunc() override {}
};

内存布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Derive的内存布局
+------------------+
| vptr (Derived) | // 指向Derived的vtable
+------------------+
| Base::base_data | // 基类数据成员
+------------------+
| Derived::derived | // 派生类数据成员
+------------------+

# Base的内存布局
+------------------+
| vptr (Base) | // 指向Base的vtable
+------------------+
| Base::base_data | // 基类数据成员
+------------------+

2. 多继承模型

1
2
3
4
5
6
class Base1 { virtual void f1() {} };
class Base2 { virtual void f2() {} };
class Derived : public Base1, public Base2 {
void f1() override {}
void f2() override {}
};

内存布局:

1
2
3
4
5
6
7
8
9
10
11
+------------------+
| vptr (Base1) | // 指向Derived的Base1 vtable
+------------------+
| Base1 data |
+------------------+
| vptr (Base2) | // 指向Derived的Base2 vtable
+------------------+
| Base2 data |
+------------------+
| Derived data | // 派生类特有数据
+------------------+

3. 虚继承模型(解决菱形继承)

1
2
3
4
class A { int a; };
class B : virtual public A { int b; };
class C : virtual public A { int c; };
class D : public B, public C { int d; };

内存布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
+------------------+
| vptr (B) |
+------------------+
| B::b |
+------------------+
| vptr (C) |
+------------------+
| C::c |
+------------------+
| D::d |
+------------------+
| A::a | // 虚基类共享部分
+------------------+

虚函数的调用原理

1
2
3
Derived d;
Base* pb = &d;
pb->vfunc();

有点像类型的强制转换,由于

最佳实践

  1. 多态使用原则
1
2
3
4
5
6
7

// 正确:通过指针/引用保持多态
void func(Base1& b) { b.f1(); }

// 错误:按值传递导致切片
void func(Base1 b) { b.f1(); } // 失去多态

  1. 安全的指针转换
1
2
3
4
5
6
7
8
9
10
Base2* pb2 = get_object();

// 推荐方式
if (Derived* pd = dynamic_cast<Derived*>(pb2)) {
// 安全使用pd
}

// 危险方式
Derived* pd = (Derived*)pb2; // 可能导致非法内存访问

  1. 内存布局验证
1
2
3
4
Derived d;
printf("Base1 offset: %td\n", (char*)static_cast<Base1*>(&d) - (char*)&d);
printf("Base2 offset: %td\n", (char*)static_cast<Base2*>(&d) - (char*)&d);

  1. 避免多继承陷阱
    • 优先使用单继承+接口继承
    • 谨慎使用虚继承
    • 使用 final 防止意外重写
1
2
3
4
class SafeBase {
public:
virtual void critical() final; // 禁止重写
};

虚函数机制失效

1. 对象切片(Object Slicing)

场景:将派生类对象按值赋值给基类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
virtual void func() { cout << "Base\n"; }
};

class Derived : public Base {
public:
void func() override { cout << "Derived\n"; }
};

int main() {
Derived d;
Base b = d; // 对象切片
b.func(); // 输出 "Base" 而不是 "Derived"
}

失效原因

  • 仅复制了基类部分的数据成员
  • vptr被重置为基类的虚函数表
  • 派生类特有的数据和方法完全丢失

2. 构造函数中的虚函数调用

场景:在构造函数中调用虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
Base() { func(); } // 危险!
virtual void func() { cout << "Base\n"; }
};

class Derived : public Base {
public:
Derived() : Base() {}
void func() override { cout << "Derived\n"; }
};

int main() {
Derived d; // 输出 "Base" 而不是 "Derived"
}

失效原因

  • 对象构造顺序:基类 → 成员 → 派生类
  • 基类构造期间,对象被视为基类类型
  • vptr在基类构造期间指向基类的虚函数表

3. 析构函数中的虚函数调用

场景:在析构函数中调用虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
virtual ~Base() { func(); } // 危险!
virtual void func() { cout << "Base\n"; }
};

class Derived : public Base {
public:
~Derived() {}
void func() override { cout << "Derived\n"; }
};

int main() {
Derived d; // 析构时输出 "Base" 而不是 "Derived"
}

失效原因

  • 对象析构顺序:派生类 → 成员 → 基类
  • 基类析构期间,对象被视为基类类型
  • vptr在基类析构期间指向基类的虚函数表

4. 非指针/引用的直接调用

场景:通过对象实例(而非指针/引用)调用虚函数

1
2
3
4
5
Derived d;
d.func(); // 输出 "Derived" (静态绑定)

Base b = d;
b.func(); // 输出 "Base" (静态绑定)

失效原因

  • 编译器在编译期即可确定对象确切类型
  • 直接生成静态函数调用指令
  • 完全绕过虚函数表机制

5. 隐藏而非重写(Hidden vs Overridden)

场景:派生类声明同名函数但未正确重写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base {
public:
virtual void func(int) { cout << "Base\n"; }
};

class Derived : public Base {
public:
// 隐藏而非重写基类函数
void func() { cout << "Derived\n"; }
};

int main() {
Derived d;
Base* pb = &d;
pb->func(1); // 输出 "Base" (未调用派生类实现)
}

失效原因

  • 函数签名不同(参数列表)
  • 未使用override关键字进行显式声明
  • 派生类函数隐藏了基类函数而非重写

6. 虚函数表损坏

场景:内存错误导致vptr或vtable损坏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Base {
public:
virtual void func() { cout << "Base\n"; }
int data;
};

class Derived : public Base {
public:
void func() override { cout << "Derived\n"; }
};

int main() {
Derived d;

// 危险的内存越界操作
int* p = &d.data;
for (int i = 0; i < 100; i++) p[i] = 0; // 覆盖vptr

Base* pb = &d;
pb->func(); // 未定义行为(通常崩溃)
}

失效原因

  • 内存越界覆盖了对象的vptr
  • vptr指向无效内存地址
  • 访问虚函数表时导致未定义行为
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2023-2025 John Doe
  • Visitors: | Views:

请我喝杯咖啡吧~

支付宝
微信