c_snake_game

第 8 课:内存管理 💾

C 语言允许你手动管理内存。这很强大,但也需要小心使用。


8.1 内存区域

程序运行时,内存分为几个区域:

┌─────────────────────┐
│     栈 (Stack)      │  ← 局部变量,自动管理
├─────────────────────┤
│         ↓           │
│         |           │
│         ↓           │
├─────────────────────┤
│     堆 (Heap)       │  ← 动态分配,手动管理
├─────────────────────┤
│   全局/静态区       │  ← 全局变量
├─────────────────────┤
│     代码区          │  ← 程序代码
└─────────────────────┘

8.2 栈内存(自动管理)

void function(void) {
    int x = 10;           // 栈上分配
    float arr[100];       // 栈上分配
    // ... 函数执行
}                         // 函数结束,自动释放

特点:


8.3 堆内存(手动管理)

#include <stdlib.h>

// 分配内存
int* ptr = malloc(sizeof(int) * 100);  // 分配 100 个 int

// 使用内存
ptr[0] = 10;
ptr[99] = 100;

// 释放内存
free(ptr);
ptr = NULL;  // 好习惯:释放后置为 NULL

特点:


8.4 malloc 和 free

malloc - 分配内存

#include <stdlib.h>

// 分配单个 int
int* num = malloc(sizeof(int));
*num = 42;

// 分配数组
int* arr = malloc(sizeof(int) * 10);
for (int i = 0; i < 10; i++) {
    arr[i] = i;
}

// 分配结构体
typedef struct {
    int x, y;
} Point;

Point* p = malloc(sizeof(Point));
p->x = 10;
p->y = 20;

free - 释放内存

free(num);
free(arr);
free(p);

8.5 calloc - 初始化为 0

// malloc:不初始化,内容是随机值
int* arr1 = malloc(sizeof(int) * 10);  // 内容未知

// calloc:初始化为 0
int* arr2 = calloc(10, sizeof(int));   // 所有元素 = 0

// 等价于
int* arr3 = malloc(sizeof(int) * 10);
memset(arr3, 0, sizeof(int) * 10);

8.6 realloc - 调整大小

// 初始分配
int* arr = malloc(sizeof(int) * 5);

// ... 使用中发现不够用

// 调整大小为 10
int* new_arr = realloc(arr, sizeof(int) * 10);

// realloc 可能返回新地址
// 原来的数据会被复制过去
if (new_arr) {
    arr = new_arr;
}

// 使用完释放
free(arr);

8.7 贪吃蛇中的内存管理

动态创建蛇

typedef struct Segment {
    int x, y;
    struct Segment* next;
} Segment;

typedef struct {
    Segment* head;
    int length;
} Snake;

// 创建蛇
Snake* snake_create(int start_x, int start_y) {
    Snake* snake = malloc(sizeof(Snake));
    
    Segment* head = malloc(sizeof(Segment));
    head->x = start_x;
    head->y = start_y;
    head->next = NULL;
    
    snake->head = head;
    snake->length = 1;
    
    return snake;
}

// 添加身体段
void snake_grow(Snake* snake) {
    Segment* new_segment = malloc(sizeof(Segment));
    new_segment->next = snake->head;
    snake->head = new_segment;
    snake->length++;
}

// 销毁蛇(重要!)
void snake_destroy(Snake* snake) {
    Segment* current = snake->head;
    
    // 遍历链表,释放每个段
    while (current) {
        Segment* next = current->next;
        free(current);
        current = next;
    }
    
    free(snake);
}

完整使用示例

int main(void) {
    // 创建蛇
    Snake* snake = snake_create(10, 10);
    
    // 游戏循环
    while (game_running) {
        // ... 游戏逻辑
    }
    
    // 清理内存(重要!)
    snake_destroy(snake);
    
    return 0;
}

8.8 常见内存错误

错误 1:内存泄漏

void leak_memory(void) {
    int* ptr = malloc(sizeof(int) * 100);
    // ... 使用
    // ❌ 忘记 free,内存泄漏!
}

错误 2:重复释放

int* ptr = malloc(sizeof(int));
free(ptr);
free(ptr);  // ❌ 重复释放,未定义行为!

错误 3:使用已释放的内存

int* ptr = malloc(sizeof(int));
*ptr = 10;
free(ptr);
*ptr = 20;  // ❌ 野指针!

错误 4:越界访问

int* arr = malloc(sizeof(int) * 10);
arr[0] = 1;
arr[9] = 10;
arr[10] = 11;  // ❌ 越界!

8.9 内存管理最佳实践

1. 谁分配,谁释放

Snake* create_snake(void) {
    Snake* s = malloc(sizeof(Snake));
    return s;
}

void destroy_snake(Snake* s) {
    free(s);
}

// 使用
Snake* s = create_snake();
// ... 使用
destroy_snake(s);  // 配对释放

2. 释放后置为 NULL

free(ptr);
ptr = NULL;  // 防止野指针

3. 检查分配是否成功

int* ptr = malloc(sizeof(int) * 100);
if (!ptr) {
    fprintf(stderr, "内存分配失败!\n");
    return -1;
}

4. 使用工具检测

# 使用 valgrind 检测内存问题
valgrind --leak-check=full ./snake_game

✅ 本课检查清单


📝 作业

  1. 创建一个动态数组,支持添加元素和扩容

  2. 实现一个函数,复制字符串(使用 malloc 分配新内存)

  3. 用 valgrind 检查你的贪吃蛇程序是否有内存泄漏


下一课:链表