# 21 内存管理函数 ## 21.1 完整概念讲解 ### 堆内存管理 C语言通过标准库函数管理堆内存: | 函数 | 功能 | 特点 | | --------- | ---------- | ------------------ | | `malloc` | 分配内存 | 不初始化,内容随机 | | `calloc` | 分配并清零 | 初始化为0 | | `realloc` | 调整大小 | 可能迁移内存位置 | | `free` | 释放内存 | 必须使用分配的指针 | ### 内存操作函数 | 函数 | 功能 | 注意事项 | | --------- | -------- | ------------ | | `memcpy` | 内存复制 | 不处理重叠 | | `memmove` | 内存移动 | 处理重叠情况 | | `memset` | 内存设置 | 按字节设置 | | `memcmp` | 内存比较 | 按字节比较 | ### 栈内存 vs 堆内存 | 特性 | 栈内存 | 堆内存 | | -------- | ---------------- | ---------------------- | | 分配速度 | 快(移动栈指针) | 慢(系统调用) | | 大小限制 | 较小(通常几MB) | 较大(受物理内存限制) | | 生命周期 | 函数结束自动释放 | 需手动free | | 碎片问题 | 无 | 可能产生碎片 | ### 嵌入式内存池 嵌入式系统中常用**内存池(Memory Pool)**预分配固定大小的内存块,避免动态分配的不确定性和碎片问题。 ## 21.2 核心API/语法 ```c #include // 内存分配 void *malloc(size_t size); void *calloc(size_t nmemb, size_t size); void *realloc(void *ptr, size_t size); void free(void *ptr); #include // 内存操作 void *memcpy(void *dest, const void *src, size_t n); void *memmove(void *dest, const void *src, size_t n); void *memset(void *s, int c, size_t n); int memcmp(const void *s1, const void *s2, size_t n); ``` ## 21.3 代码示例(完整可编译,附gcc命令) ```c #include #include #include void demonstrate_malloc(void) { printf("=== malloc ===\n"); int *arr = malloc(5 * sizeof(int)); if (arr == NULL) { fprintf(stderr, "malloc failed\n"); return; } // 注意:malloc不初始化,内容随机 printf("Uninitialized values: "); for (int i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf("\n"); // 手动初始化 for (int i = 0; i < 5; i++) { arr[i] = i * 10; } printf("After init: "); for (int i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf("\n"); free(arr); arr = NULL; // 防止野指针 } void demonstrate_calloc(void) { printf("\n=== calloc ===\n"); int *arr = calloc(5, sizeof(int)); if (arr == NULL) { fprintf(stderr, "calloc failed\n"); return; } // calloc自动初始化为0 printf("Zero-initialized: "); for (int i = 0; i < 5; i++) { printf("%d ", arr[i]); } printf("\n"); free(arr); arr = NULL; } void demonstrate_realloc(void) { printf("\n=== realloc ===\n"); int *arr = malloc(3 * sizeof(int)); if (arr == NULL) return; for (int i = 0; i < 3; i++) arr[i] = i; printf("Original (3): "); for (int i = 0; i < 3; i++) printf("%d ", arr[i]); printf("\n"); // 扩容到6 int *temp = realloc(arr, 6 * sizeof(int)); if (temp == NULL) { fprintf(stderr, "realloc failed\n"); free(arr); return; } arr = temp; // 新空间可能未初始化 for (int i = 3; i < 6; i++) arr[i] = i * 10; printf("Expanded (6): "); for (int i = 0; i < 6; i++) printf("%d ", arr[i]); printf("\n"); free(arr); arr = NULL; } void demonstrate_mem_functions(void) { printf("\n=== 内存操作函数 ===\n"); // memcpy int src[] = {1, 2, 3, 4, 5}; int dest[5]; memcpy(dest, src, sizeof(src)); printf("memcpy: "); for (int i = 0; i < 5; i++) printf("%d ", dest[i]); printf("\n"); // memmove(处理重叠) int data[] = {1, 2, 3, 4, 5, 6, 7, 8}; memmove(data + 2, data, 6 * sizeof(int)); // 重叠复制 printf("memmove: "); for (int i = 0; i < 8; i++) printf("%d ", data[i]); printf("\n"); // memset char buffer[20]; memset(buffer, 'A', sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = '\0'; printf("memset: %s\n", buffer); // memcmp int a[] = {1, 2, 3}; int b[] = {1, 2, 4}; printf("memcmp: %d\n", memcmp(a, b, 3 * sizeof(int))); } void demonstrate_memory_leak(void) { printf("\n=== 内存泄漏示例 ===\n"); // 危险:覆盖指针导致泄漏 int *p = malloc(100 * sizeof(int)); printf("Allocated at %p\n", (void *)p); // 错误:覆盖指针,原内存泄漏 // p = malloc(200 * sizeof(int)); // 原内存无法释放! free(p); p = NULL; printf("Freed and set to NULL\n"); } int main() { demonstrate_malloc(); demonstrate_calloc(); demonstrate_realloc(); demonstrate_mem_functions(); demonstrate_memory_leak(); return 0; } ``` **编译命令:** ```bash gcc -Wall -Wextra -o memory_manage memory_manage.c ./memory_manage ``` ## 21.4 注意事项与易错点 1. **malloc必须检查返回值**:内存不足时返回NULL 2. **free后指针置NULL**:防止野指针被误用 3. **realloc失败时原指针有效**:不要直接`p = realloc(p, size)`,应先用临时变量接收 4. **不能重复free**:同一指针多次free是未定义行为 5. **free非malloc返回的指针**:必须使用malloc/calloc/realloc返回的指针调用free 6. **memset对int数组**:`memset(arr, 0, n)`只适用于字节为0的情况 ## 21.5 面试要点(3-5个Q&A) **Q1: malloc和calloc有什么区别?** A: malloc不初始化内存,calloc初始化为零。calloc参数是元素数量和大小,malloc是总字节数。calloc可能稍慢。 **Q2: realloc扩容时会发生什么?** A: 如果原空间后有足够空间,直接扩展;否则分配新空间,复制数据,释放原空间。因此realloc后指针可能改变。 **Q3: 如何检测内存泄漏?** A: 使用valgrind、AddressSanitizer等工具。良好的习惯是malloc和free配对,free后置NULL,使用智能指针(C++)。 **Q4: 什么时候用栈,什么时候用堆?** A: 小型、生命周期短的数据用栈(快、自动管理)。大型、动态大小、需要跨函数生命周期的数据用堆。 **Q5: memcpy和memmove有什么区别?** A: memcpy不处理内存重叠,重叠时行为未定义。memmove处理重叠,更安全但可能稍慢。不确定是否重叠时用memmove。