L6.1 - 指针与动态内存(拓展)

🎯 教学目标

  1. 掌握指针运算与内存地址操作
  2. 熟练使用new/delete管理动态内存
  3. 理解智能指针的工作原理
  4. 掌握函数指针的高级应用
  5. 预防内存泄漏与悬空指针问题

🔑 核心知识点

1. 指针基础概念

内存地址可视化模型

1
2
3
0x1000 [ 10 ] ← int a = 10
0x1004 [ 0x1000 ] ← int* p = &a
0x1008 [ 0x1004 ] ← int** pp = &p

指针声明三要素:

1
2
3
int* p;        // 声明指针变量
p = &a; // 取地址操作
cout << *p; // 解引用操作

指针运算原理

1
2
3
4
5
6
int arr[5] = {10,20,30,40,50};
int* ptr = arr;

ptr + 1 → 地址增加sizeof(int)(通常4字节)
*(ptr+3) ← 等价于arr[3]
ptr++ ← 移动到下一个元素

2. 动态内存管理

内存分配生命周期图

1
2
3
4
┌───────────┐       ┌────────────┐
│ 栈内存 │ │ 堆内存 │
│ 自动回收 │ ← new │ 手动管理 │
└───────────┘ └────────────┘

标准操作模板:

1
2
3
4
5
int* nums = new int[100]; // 分配数组
delete[] nums; // 释放数组

Widget* w = new Widget(); // 创建对象
delete w; // 销毁对象

多维数组动态分配

1
2
3
4
5
6
7
8
9
10
int** matrix = new int*[rows];
for(int i=0; i<rows; i++){
matrix[i] = new int[cols];
}

// 释放内存
for(int i=0; i<rows; i++){
delete[] matrix[i];
}
delete[] matrix;

3. 智能指针(Modern C++)

智能指针类型对比

类型 所有权 特点
unique_ptr 独占 零开销,禁止拷贝
shared_ptr 共享 引用计数
weak_ptr 观察者 解决循环引用问题

使用示例

1
2
3
4
5
6
7
8
9
10
// unique_ptr(独占资源)
auto uptr = make_unique<Widget>(args);

// shared_ptr(共享资源)
auto sptr1 = make_shared<Resource>(...);
auto sptr2 = sptr1; // 引用计数+1

// weak_ptr(打破循环引用)
shared_ptr<Node> node;
weak_ptr<Node> parent;

4. 函数指针高级应用

回调函数系统设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef void (*EventHandler)(int); // 定义函数指针类型

class Button {
vector<EventHandler> handlers;
public:
void addHandler(EventHandler h) {
handlers.push_back(h);
}

void click() {
for(auto h : handlers) {
h(42); // 触发所有回调
}
}
};

函数指针数组

1
2
3
4
5
6
7
8
void (*operations[])(int, int) = {
[](int a, int b){ cout << a+b; },
[](int a, int b){ cout << a-b; },
[](int a, int b){ cout << a*b; }
};

// 调用示例
operations[0](3,5); // 执行加法

💡 内存管理实战技巧

内存泄漏检测方案

1
2
3
4
5
6
#ifdef _DEBUG
#define new new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

// 在程序入口处添加
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

安全指针使用规范

  1. 初始化原则:声明时立即初始化
  2. NULL检查:解引用前验证有效性
  3. 所有权明确:遵循RAII原则
  4. 禁止野指针:释放后立即置空
1
2
delete ptr;
ptr = nullptr; // 防止重复删除

💣 高危内存问题解析

1. 悬空指针(Dangling Pointer)

1
2
3
4
5
6
7
8
9
int* createNumber() {
int num = 100;
return &num; // 返回局部变量地址(危险!)
}

int main() {
int* p = createNumber();
cout << *p; // 访问已释放的内存
}

2. 内存泄漏(Memory Leak)

1
2
3
4
5
void leakyFunc() {
int* arr = new int[1000];
// 忘记delete[] arr
return; // 内存永久丢失
}

3. 双重释放(Double Free)

1
2
3
int* data = new int[100];
delete[] data;
delete[] data; // 二次释放导致崩溃

🧩 经典数据结构实现

链表节点管理

1
2
3
4
5
6
7
8
9
10
11
12
13
struct Node {
int data;
unique_ptr<Node> next; // 自动内存管理

Node(int val) : data(val), next(nullptr) {}
};

// 链表插入操作
void insert(unique_ptr<Node>& head, int val) {
auto newNode = make_unique<Node>(val);
newNode->next = move(head);
head = move(newNode);
}

二维矩阵类

1
2
3
4
5
6
7
8
9
10
11
class Matrix {
unique_ptr<float[]> data;
int rows, cols;
public:
Matrix(int r, int c) : rows(r), cols(c),
data(make_unique<float[]>(r*c)) {}

float& at(int r, int c) {
return data[r*cols + c];
}
};

🏋️ 配套练习

1. 智能指针实战

  • 实现学生管理系统:
    • 使用shared_ptr管理学生对象
    • 实现课程选修关系(使用weak_ptr
    • 统计对象生命周期

2. 内存分配分析器

  • 编写内存跟踪器:
    • 重载new/delete记录分配信息
    • 生成内存使用报告(包含泄漏检测)

3. 函数工厂模式

  • 创建运算工厂:
    • 根据字符串"add"/"mul"返回对应函数指针
    • 支持动态扩展新运算

4. 自定义智能指针

  • 实现简化版shared_ptr

    • 包含引用计数器
    • 实现拷贝构造函数
    • 支持reset()方法

5. 循环引用破解

  • 设计双向链表:
    • 使用shared_ptrweak_ptr实现
    • 验证节点自动释放
    • 对比不同实现的内存消耗

🛠️ 调试工具指南

Valgrind使用示例

1
2
3
4
valgrind --leak-check=full \
--show-reachable=yes \
--track-origins=yes \
./your_program

AddressSanitizer配置

1
2
3
# CMakeLists.txt
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)

内存布局分析工具

1
2
3
cout << "变量地址:" << &var << endl;
cout << "指针大小:" << sizeof(int*) << endl;
cout << "内存对齐:" << alignof(MyClass) << endl;