内存管理

深入理解内存管理

内存管理是C语言编程的核心技能,掌握内存管理对于编写高效、稳定的程序至关重要。东巴文(db-w.cn) 将带你深入理解内存管理的原理与实践。

💡 东巴文观点:C语言赋予了程序员对内存的直接控制权,这是优势也是责任。正确的内存管理是程序稳定运行的基础。

内存分区模型

五大内存区域

#include <stdio.h>
#include <stdlib.h>

int globalVar = 100;        // 全局区(已初始化)
int globalUninit;           // 全局区(未初始化)
const int constVar = 200;   // 常量区

void memoryLayout() {
    printf("=== 东巴文内存分区模型 ===\n\n");
    
    int localVar = 10;      // 栈区
    static int staticVar = 20;  // 全局区(静态)
    
    int *heapVar = (int *)malloc(sizeof(int));  // 堆区
    *heapVar = 30;
    
    printf("栈区变量:\n");
    printf("  localVar地址:%p\n", &localVar);
    
    printf("\n堆区变量:\n");
    printf("  heapVar地址:%p\n", heapVar);
    
    printf("\n全局区变量:\n");
    printf("  globalVar地址:%p\n", &globalVar);
    printf("  globalUninit地址:%p\n", &globalUninit);
    printf("  staticVar地址:%p\n", &staticVar);
    
    printf("\n常量区变量:\n");
    printf("  constVar地址:%p\n", &constVar);
    
    printf("\n代码区:\n");
    printf("  memoryLayout函数地址:%p\n", memoryLayout);
    
    free(heapVar);
}

int main() {
    memoryLayout();
    return 0;
}

东巴文内存分区表

区域 内容 特点 生命周期
栈区 局部变量、函数参数 自动管理,向下生长 函数调用期间
堆区 动态分配的内存 手动管理,向上生长 程序员控制
全局区 全局变量、静态变量 程序启动时分配 程序运行期间
常量区 常量、字符串字面量 只读 程序运行期间
代码区 程序代码 只读 程序运行期间

栈区

#include <stdio.h>

void stackDemo() {
    printf("=== 东巴文栈区演示 ===\n\n");
    
    int a = 10;
    int b = 20;
    int c = 30;
    
    printf("栈区变量地址(向下生长):\n");
    printf("  &a = %p\n", &a);
    printf("  &b = %p\n", &b);
    printf("  &c = %p\n", &c);
    
    printf("\n栈区特点:\n");
    printf("  - 自动分配和释放\n");
    printf("  - 空间有限\n");
    printf("  - 后进先出\n");
}

int main() {
    stackDemo();
    return 0;
}

堆区

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文堆区演示 ===\n\n");
    
    int *ptr1 = (int *)malloc(sizeof(int));
    int *ptr2 = (int *)malloc(sizeof(int));
    int *ptr3 = (int *)malloc(sizeof(int));
    
    printf("堆区变量地址(向上生长):\n");
    printf("  ptr1 = %p\n", ptr1);
    printf("  ptr2 = %p\n", ptr2);
    printf("  ptr3 = %p\n", ptr3);
    
    printf("\n堆区特点:\n");
    printf("  - 手动分配和释放\n");
    printf("  - 空间较大\n");
    printf("  - 灵活控制\n");
    
    // 释放内存
    free(ptr1);
    free(ptr2);
    free(ptr3);
    
    return 0;
}

全局区

#include <stdio.h>

// 全局变量
int globalInit = 100;      // 已初始化全局区
int globalUninit;          // 未初始化全局区

// 静态全局变量
static int staticGlobal = 200;

void globalDemo() {
    printf("=== 东巴文全局区演示 ===\n\n");
    
    // 静态局部变量
    static int staticLocal = 300;
    
    printf("全局区变量地址:\n");
    printf("  globalInit = %p\n", &globalInit);
    printf("  globalUninit = %p\n", &globalUninit);
    printf("  staticGlobal = %p\n", &staticGlobal);
    printf("  staticLocal = %p\n", &staticLocal);
    
    printf("\n全局区特点:\n");
    printf("  - 程序启动时分配\n");
    printf("  - 程序结束时释放\n");
    printf("  - 默认初始化为0\n");
}

int main() {
    globalDemo();
    return 0;
}

动态内存分配

malloc函数

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文malloc函数 ===\n\n");
    
    // 分配单个变量
    int *ptr = (int *)malloc(sizeof(int));
    
    if (ptr != NULL) {
        *ptr = 100;
        printf("分配的值:*ptr = %d\n", *ptr);
        printf("分配的地址:ptr = %p\n", ptr);
        
        free(ptr);
        ptr = NULL;
    }
    
    // 分配数组
    int size = 5;
    int *arr = (int *)malloc(size * sizeof(int));
    
    if (arr != NULL) {
        printf("\n分配数组:\n");
        for (int i = 0; i < size; i++) {
            arr[i] = i + 1;
            printf("arr[%d] = %d\n", i, arr[i]);
        }
        
        free(arr);
        arr = NULL;
    }
    
    return 0;
}

东巴文说明

  • malloc 在堆区分配内存
  • 分配的内存未初始化
  • 返回 void* 指针
  • 失败返回 NULL

calloc函数

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文calloc函数 ===\n\n");
    
    int size = 5;
    int *arr = (int *)calloc(size, sizeof(int));
    
    if (arr != NULL) {
        printf("calloc分配的数组(已初始化为0):\n");
        for (int i = 0; i < size; i++) {
            printf("arr[%d] = %d\n", i, arr[i]);
        }
        
        free(arr);
        arr = NULL;
    }
    
    return 0;
}

东巴文说明

  • calloc 分配并初始化为0
  • 两个参数:元素个数和元素大小
  • 适合分配数组

realloc函数

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文realloc函数 ===\n\n");
    
    // 初始分配
    int size = 5;
    int *arr = (int *)malloc(size * sizeof(int));
    
    if (arr != NULL) {
        printf("初始分配:\n");
        for (int i = 0; i < size; i++) {
            arr[i] = i + 1;
            printf("arr[%d] = %d\n", i, arr[i]);
        }
        
        // 扩展内存
        int newSize = 10;
        int *newArr = (int *)realloc(arr, newSize * sizeof(int));
        
        if (newArr != NULL) {
            arr = newArr;
            
            printf("\n扩展后:\n");
            for (int i = 0; i < newSize; i++) {
                printf("arr[%d] = %d\n", i, arr[i]);
            }
        }
        
        free(arr);
        arr = NULL;
    }
    
    return 0;
}

东巴文说明

  • realloc 调整已分配内存的大小
  • 可能移动内存位置
  • 原有数据保持不变
  • 失败返回 NULL,原内存不变

free函数

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文free函数 ===\n\n");
    
    int *ptr = (int *)malloc(sizeof(int));
    
    if (ptr != NULL) {
        *ptr = 100;
        printf("分配的值:*ptr = %d\n", *ptr);
        
        // 释放内存
        free(ptr);
        printf("内存已释放\n");
        
        // ✅ 推荐:释放后置NULL
        ptr = NULL;
        
        // ❌ 错误:释放后访问
        // printf("*ptr = %d\n", *ptr);  // 悬空指针
    }
    
    return 0;
}

东巴文说明

  • free 释放动态分配的内存
  • 释放后指针变为悬空指针
  • 建议释放后置 NULL
  • 不能重复释放

内存泄漏

内存泄漏的原因

#include <stdio.h>
#include <stdlib.h>

void memoryLeakDemo() {
    printf("=== 东巴文内存泄漏演示 ===\n\n");
    
    // ❌ 内存泄漏:忘记释放
    int *ptr1 = (int *)malloc(sizeof(int));
    *ptr1 = 100;
    printf("ptr1 = %d\n", *ptr1);
    // 忘记 free(ptr1);
    
    // ❌ 内存泄漏:指针丢失
    int *ptr2 = (int *)malloc(sizeof(int));
    *ptr2 = 200;
    printf("ptr2 = %d\n", *ptr2);
    ptr2 = (int *)malloc(sizeof(int));  // 原内存丢失
    // 忘记 free(ptr2);
    
    // ❌ 内存泄漏:重复分配
    int *ptr3 = (int *)malloc(sizeof(int));
    ptr3 = (int *)malloc(sizeof(int));  // 原内存丢失
    // 忘记 free(ptr3);
    
    printf("\n以上都是内存泄漏的例子\n");
}

int main() {
    memoryLeakDemo();
    return 0;
}

防止内存泄漏

#include <stdio.h>
#include <stdlib.h>

void noMemoryLeak() {
    printf("=== 东巴文防止内存泄漏 ===\n\n");
    
    // ✅ 正确:及时释放
    int *ptr1 = (int *)malloc(sizeof(int));
    if (ptr1 != NULL) {
        *ptr1 = 100;
        printf("ptr1 = %d\n", *ptr1);
        free(ptr1);
        ptr1 = NULL;
    }
    
    // ✅ 正确:先释放再重新分配
    int *ptr2 = (int *)malloc(sizeof(int));
    if (ptr2 != NULL) {
        *ptr2 = 200;
        printf("ptr2 = %d\n", *ptr2);
        free(ptr2);
        ptr2 = NULL;
        
        ptr2 = (int *)malloc(sizeof(int));
        if (ptr2 != NULL) {
            *ptr2 = 300;
            printf("ptr2 = %d\n", *ptr2);
            free(ptr2);
            ptr2 = NULL;
        }
    }
    
    printf("\n正确管理内存\n");
}

int main() {
    noMemoryLeak();
    return 0;
}

常见内存错误

1. 悬空指针

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文悬空指针 ===\n\n");
    
    int *ptr = (int *)malloc(sizeof(int));
    *ptr = 100;
    
    printf("释放前:*ptr = %d\n", *ptr);
    
    free(ptr);
    
    // ❌ 错误:悬空指针
    // printf("释放后:*ptr = %d\n", *ptr);
    
    // ✅ 正确:置NULL
    ptr = NULL;
    
    printf("正确处理悬空指针\n");
    
    return 0;
}

2. 重复释放

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文重复释放 ===\n\n");
    
    int *ptr = (int *)malloc(sizeof(int));
    *ptr = 100;
    
    printf("ptr = %d\n", *ptr);
    
    free(ptr);
    
    // ❌ 错误:重复释放
    // free(ptr);
    
    // ✅ 正确:置NULL后可以安全释放
    ptr = NULL;
    // free(ptr);  // 释放NULL是安全的
    
    printf("正确处理重复释放\n");
    
    return 0;
}

3. 越界访问

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文越界访问 ===\n\n");
    
    int size = 5;
    int *arr = (int *)malloc(size * sizeof(int));
    
    if (arr != NULL) {
        // 正确访问
        for (int i = 0; i < size; i++) {
            arr[i] = i + 1;
        }
        
        // ❌ 错误:越界访问
        // arr[5] = 100;  // 越界
        // arr[-1] = 100;  // 越界
        
        printf("正确访问数组\n");
        
        free(arr);
        arr = NULL;
    }
    
    return 0;
}

4. 未检查返回值

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文未检查返回值 ===\n\n");
    
    // ❌ 错误:未检查返回值
    // int *ptr = (int *)malloc(1000000000000);
    // *ptr = 100;  // 可能崩溃
    
    // ✅ 正确:检查返回值
    int *ptr = (int *)malloc(sizeof(int));
    if (ptr != NULL) {
        *ptr = 100;
        printf("ptr = %d\n", *ptr);
        free(ptr);
        ptr = NULL;
    } else {
        printf("内存分配失败\n");
    }
    
    return 0;
}

东巴文最佳实践

1. 及时释放内存

#include <stdio.h>
#include <stdlib.h>

void processData() {
    int *data = (int *)malloc(100 * sizeof(int));
    
    if (data != NULL) {
        // 使用数据...
        
        // ✅ 正确:及时释放
        free(data);
        data = NULL;
    }
}

int main() {
    printf("=== 东巴文及时释放内存 ===\n\n");
    
    processData();
    
    printf("内存已正确释放\n");
    
    return 0;
}

2. 使用NULL检查

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文使用NULL检查 ===\n\n");
    
    int *ptr = (int *)malloc(sizeof(int));
    
    // ✅ 正确:检查NULL
    if (ptr != NULL) {
        *ptr = 100;
        printf("ptr = %d\n", *ptr);
        free(ptr);
        ptr = NULL;
    } else {
        printf("内存分配失败\n");
    }
    
    return 0;
}

3. 配对使用malloc和free

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("=== 东巴文配对使用malloc和free ===\n\n");
    
    int *ptr1 = (int *)malloc(sizeof(int));
    int *ptr2 = (int *)calloc(5, sizeof(int));
    int *ptr3 = (int *)realloc(NULL, sizeof(int));
    
    // ✅ 正确:每个malloc对应一个free
    free(ptr1);
    free(ptr2);
    free(ptr3);
    
    ptr1 = NULL;
    ptr2 = NULL;
    ptr3 = NULL;
    
    printf("内存正确配对释放\n");
    
    return 0;
}

4. 使用内存池

#include <stdio.h>
#include <stdlib.h>

#define POOL_SIZE 100

typedef struct {
    int data[POOL_SIZE];
    int used;
} MemoryPool;

void initPool(MemoryPool *pool) {
    pool->used = 0;
}

int *allocateFromPool(MemoryPool *pool) {
    if (pool->used < POOL_SIZE) {
        return &pool->data[pool->used++];
    }
    return NULL;
}

int main() {
    printf("=== 东巴文使用内存池 ===\n\n");
    
    MemoryPool pool;
    initPool(&pool);
    
    // 从内存池分配
    int *ptr1 = allocateFromPool(&pool);
    int *ptr2 = allocateFromPool(&pool);
    
    if (ptr1 != NULL) *ptr1 = 100;
    if (ptr2 != NULL) *ptr2 = 200;
    
    printf("ptr1 = %d\n", *ptr1);
    printf("ptr2 = %d\n", *ptr2);
    
    printf("\n内存池优势:\n");
    printf("  - 减少内存碎片\n");
    printf("  - 提高分配效率\n");
    printf("  - 便于管理\n");
    
    return 0;
}

东巴文验证清单

完成本章学习后,请确认:

  • 理解内存分区模型
  • 理解栈区和堆区的区别
  • 掌握动态内存分配函数
  • 理解内存泄漏的原因
  • 掌握防止内存泄漏的方法
  • 了解常见内存错误
  • 掌握最佳实践

下一步学习

掌握内存管理后,你可以继续学习:

如果遇到问题,欢迎访问 东巴文(db-w.cn) 获取帮助!


东巴文(db-w.cn) - 让编程学习更简单

🎯 东巴文内存管理提示:正确的内存管理是程序稳定运行的基础。在 db-w.cn,我们会通过大量实例帮你掌握内存管理!