这一章是 C++ 面向对象最接近逆向分析的一部分:对象布局、继承层次、虚函数表和动态绑定都会在二进制里留下痕迹。学习时既要能写语法,也要能想象对象在内存中的样子。

6.6 - 7.6 运算符重载

运算符重载让自定义类型也能使用 +==[]<< 等语法。它应该让代码更自然,而不是制造难懂的副作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

class Point {
public:
Point(int x, int y) : x(x), y(y) {}

Point operator+(const Point &other) const {
return Point(x + other.x, y + other.y);
}

bool operator==(const Point &other) const {
return x == other.x && y == other.y;
}

friend std::ostream &operator<<(std::ostream &os, const Point &p) {
os << "(" << p.x << ", " << p.y << ")";
return os;
}

private:
int x;
int y;
};
运算符 复盘重点
算术重载 返回新对象还是修改自身
比较重载 相等规则是否清晰
赋值重载 是否处理自赋值和资源释放
下标重载 是否检查边界
输出重载 通常用友元函数实现

7.7 继承概念引入

继承表达“is-a”关系。派生类复用基类接口和数据,也可能扩展新行为。

1
2
3
4
5
6
7
8
9
class Animal {
public:
void eat() {}
};

class Cat : public Animal {
public:
void meow() {}
};

7.8 继承访问权限

继承方式会影响基类成员在派生类中的可见性。

继承方式 public 成员 protected 成员
public public protected
protected protected protected
private private private

7.9 继承构造析构

构造顺序是基类先构造,再派生类构造;析构顺序相反。基类析构函数如果用于多态删除,应该写成 virtual。

8.0 继承基类调用

派生类可以显式调用基类成员:

1
2
3
4
5
6
class Derived : public Base {
public:
void run() {
Base::run();
}
};

8.1 继承对象赋值

基类对象接收派生类对象时可能发生对象切片,只保留基类部分。复盘多态时要优先使用引用或指针。

8.2 继承友元关系

友元关系不会因为继承自动传递。基类友元不等于派生类友元。

8.3 继承内存布局

派生类对象通常包含基类子对象,再加上自己的成员。逆向时可以通过成员偏移观察继承关系。

部分 说明
基类子对象 基类成员数据
派生类成员 新增成员数据
虚表指针 有虚函数时通常出现

8.4 继承权限限制

权限控制的是源码层面的可访问性。逆向里依然能看到对象内存,但写代码时要尊重封装边界。

8.5 单继承与多继承

单继承布局相对简单;多继承会让对象里出现多个基类子对象,指针转换也可能带来地址调整。

8.6 多继承机制

多继承下,派生类对象可能包含多个虚表指针和多个基类区域。复盘时重点看“当前指针指向对象的哪个子对象”。

8.7 菱形继承弊端

菱形继承会让公共基类被重复继承,可能出现多份基类数据。虚继承能解决重复数据问题,但布局更复杂。

8.8 继承权限以及布局

把继承复盘成两张图:一张是源码关系图,一张是内存布局图。两张图能对上,继承就真正理解了。

8.9 多态触发机制

多态通常需要三个条件:继承、虚函数、基类指针或引用指向派生类对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>

class Animal {
public:
virtual ~Animal() = default;
virtual void speak() const {
std::cout << "animal\n";
}
};

class Cat : public Animal {
public:
void speak() const override {
std::cout << "cat\n";
}
};

9.0 虚函数地址表

虚函数表保存虚函数入口地址。对象中通常有虚表指针指向虚表。调用虚函数时,程序通过虚表找到真正执行的函数。

9.1 虚析构执行体

基类指针删除派生类对象时,基类析构函数必须是虚析构,否则可能只析构基类部分。

9.2 纯虚函数抽象

纯虚函数让类变成抽象类,不能直接实例化。

1
2
3
4
5
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
};

9.3 - 9.5 多态底层原理

多态底层可以抓住三件事:对象地址、虚表指针、虚函数表项。逆向 C++ 时看到间接调用,很可能就和虚表有关。

观察点 说明
对象首部 可能保存虚表指针
虚表 保存虚函数地址
间接调用 通过表项调用真实函数
构造析构 会设置或调整虚表指针

学习检查清单

检查项 状态
能写出基本运算符重载并说明返回值设计 待复盘
能说明 public/protected/private 继承差异 待复盘
能画出单继承对象的大致内存布局 待复盘
能解释对象切片和虚析构问题 待复盘
能说明虚表和动态绑定的基本关系 待复盘