C 内存管理
C - 内存管理
Section titled “C - 内存管理”C 程序使用的内存分为几个段:文本段(代码)、数据段(全局和静态变量)、栈(局部变量、函数调用)和堆(动态内存)。本章重点介绍堆上的动态内存管理。
动态内存分配(Dynamic memory allocation)允许程序在运行时根据需要请求内存,而不是在编译时分配固定数量的内存。当所需的内存量事先未知或变化很大时,这一点至关重要。标准库 <stdlib.h> 提供了用于此目的的关键函数。
| 序号 | 函数 | 描述 |
|---|---|---|
| 1 | void *malloc(size_t size); | 从堆上分配 size 字节的未初始化内存块。返回指向该块起始位置的 void* 指针,如果分配失败则返回 NULL。 |
| 2 | void *calloc(size_t num, size_t size); | 分配一个包含 num 个元素、每个元素 size 字节的数组的内存。分配的内存会被初始化为零。返回一个 void* 指针,如果失败则返回 NULL。 |
| 3 | void *realloc(void *ptr, size_t new_size); | 将 ptr 指向的内存块大小更改为 new_size 字节。它可能会将该块移动到新的位置。返回指向可能已重定位的块的指针,如果重设大小失败(此情况下原块仍然分配着)则返回 NULL。如果 ptr 是 NULL,其行为类似于 malloc(new_size)。如果 new_size 是 0,它可能会释放内存并返回 NULL(行为可能因实现而异)。 |
| 4 | void free(void *ptr); | 释放之前由 malloc、calloc 或 realloc 分配并由 ptr 指向的内存块。将 NULL 指针传递给 free 是安全的,不会做任何事情。至关重要的是,不再需要时必须释放已分配的内存,以防止内存泄漏。 |
注意:size_t 是定义在 <stdlib.h>(及其他头文件)中的无符号整型类型,适用于表示内存中对象的大小。
动态分配内存
Section titled “动态分配内存”如果您在编译时就知道确切大小,可以在栈(stack)上或静态内存中使用数组(arrays)。例如,存储一个简短名称:
char short_name[50]; // 最多 49 个字符 + 空终止符然而,考虑存储一个未知长度的用户提供的描述。我们无法定义一个固定大小的数组,既能保证足够大,又不会浪费大量内存。动态分配解决了这个问题。我们使用一个指针(pointer)并通过 malloc 在堆(heap)上分配内存:
#include <stdio.h> // 标准输入/输出库#include <stdlib.h> // 用于 malloc, free, fprintf, stderr#include <string.h> // 用于 strcpy
int main(void) { // 使用标准 main 函数签名 char name[100]; // 在栈上分配的已知最大大小的缓冲区 char *description = NULL; // 指向动态分配内存的指针,初始化为 NULL
strcpy(name, "Zara Ali");
// 为 description 动态分配内存 // 假设我们最初估计需要 200 个字符 size_t description_size = 200; description = malloc(description_size * sizeof(char)); // sizeof(char) 总是 1,但这是一种好的习惯
// *** 始终检查 malloc 的返回值!*** if (description == NULL) { fprintf(stderr, "错误:无法为 description 分配所需内存。\n"); return 1; // 表示失败 }
// 内存分配成功,现在可以使用它了 strcpy(description, "Zara Ali is a student."); // 确保字符串适合分配的大小!
printf("姓名 = %s\n", name); printf("描述:%s\n", description);
// *** 重要提示:使用完毕后释放分配的内存 *** free(description); description = NULL; // 释放后将指针设为 NULL 是个好习惯
return 0; // 表示成功}编译并执行后,可能会产生以下输出:
Name = Zara AliDescription: Zara Ali is a student.使用 calloc 类似,但它会将内存初始化为零:
// 分配并零初始化 200 个字符的内存description = calloc(description_size, sizeof(char));if (description == NULL) { /* ... error handling ... */ }
// 'description' 现在指向 200 字节的零动态分配(Dynamic allocation)赋予您在运行时控制内存生命周期和大小的能力,这与固定大小数组不同。
重设大小和释放内存
Section titled “重设大小和释放内存”虽然操作系统在程序退出时会回收内存,但作为最佳实践,至关重要的是在不再需要内存时使用 free() 明确地释放它。未能这样做会导致内存泄漏(memory leaks),即您的程序随着时间推移消耗越来越多的内存,可能会导致程序本身或系统崩溃。
有时,最初分配的大小不够。您可以使用 realloc() 重设先前分配的块的大小。让我们修改前面的示例来存储更长的描述:
#include <stdio.h> // 标准输入/输出库#include <stdlib.h> // 用于 malloc, realloc, free, fprintf, stderr#include <string.h> // 用于 strcpy, strcat
int main(void) { char name[100]; char *description = NULL;
strcpy(name, "Zara Ali");
// 初始分配 (例如,用于短描述) size_t initial_size = 30; description = malloc(initial_size * sizeof(char)); if (description == NULL) { fprintf(stderr, "错误:初始内存分配失败。\n"); return 1; } strcpy(description, "Zara is a student."); // 适合初始大小
printf("初始描述:%s\n", description);
// 现在,假设我们需要添加更多信息 const char *additional_info = " She studies Computer Science."; size_t current_len = strlen(description); size_t additional_len = strlen(additional_info); size_t required_size = current_len + additional_len + 1; // +1 用于空终止符
// 如果需要,重设内存块大小 if (required_size > initial_size) { char *new_description = realloc(description, required_size);
// *** 始终检查 realloc 的返回值!*** if (new_description == NULL) { fprintf(stderr, "错误:内存重新分配失败。\n"); // 重要提示:'description' 仍然指向旧块! // 我们必须在退出前仍然释放原始块。 free(description); return 1; } // 重新分配成功,更新指针 description = new_description; }
// 现在空间足够了,追加额外信息 strcat(description, additional_info); // 现在 strcat 是安全的了
printf("姓名 = %s\n", name); printf("完整描述:%s\n", description);
// *** 释放最终分配的内存 *** free(description); description = NULL;
return 0;}执行此代码将输出:
Initial Description: Zara is a student.Name = Zara AliFull Description: Zara is a student. She studies Computer Science.如果在上面的例子中没有使用 realloc() 就尝试使用 strcat(),您很可能会导致缓冲区溢出(buffer overflow),即写入超出分配内存边界。这会导致未定义行为(undefined behavior),经常使程序崩溃或损坏数据,并可能成为严重的安全漏洞(security vulnerability)。
- **内存泄漏 (Memory Leaks):**忘记调用
free()释放分配的内存。 - 悬空指针 (Dangling Pointers): 在内存被
free()释放后仍然使用指向该内存的指针。 - 重复释放 (Double Free): 对同一个指针调用两次
free()。 - 无效释放 (Invalid Free): 对非
malloc/calloc/realloc返回的指针调用free()(例如,栈变量的地址)。 - 未检查
malloc/calloc/realloc返回值: 未检查是否为NULL,假设分配成功。
诸如 Valgrind(在 Linux/macOS 上)之类的工具可以帮助检测内存错误。