Skip to content

C 内存管理

C 程序使用的内存分为几个段:文本段(代码)、数据段(全局和静态变量)、栈(局部变量、函数调用)和堆(动态内存)。本章重点介绍堆上的动态内存管理。

动态内存分配(Dynamic memory allocation)允许程序在运行时根据需要请求内存,而不是在编译时分配固定数量的内存。当所需的内存量事先未知或变化很大时,这一点至关重要。标准库 <stdlib.h> 提供了用于此目的的关键函数。

序号函数描述
1void *malloc(size_t size);从堆上分配 size 字节的未初始化内存块。返回指向该块起始位置的 void* 指针,如果分配失败则返回 NULL。
2void *calloc(size_t num, size_t size);分配一个包含 num 个元素、每个元素 size 字节的数组的内存。分配的内存会被初始化为零。返回一个 void* 指针,如果失败则返回 NULL。
3void *realloc(void *ptr, size_t new_size);将 ptr 指向的内存块大小更改为 new_size 字节。它可能会将该块移动到新的位置。返回指向可能已重定位的块的指针,如果重设大小失败(此情况下原块仍然分配着)则返回 NULL。如果 ptr 是 NULL,其行为类似于 malloc(new_size)。如果 new_size 是 0,它可能会释放内存并返回 NULL(行为可能因实现而异)。
4void free(void *ptr);释放之前由 malloc、calloc 或 realloc 分配并由 ptr 指向的内存块。将 NULL 指针传递给 free 是安全的,不会做任何事情。至关重要的是,不再需要时必须释放已分配的内存,以防止内存泄漏。

注意:size_t 是定义在 <stdlib.h>(及其他头文件)中的无符号整型类型,适用于表示内存中对象的大小。

如果您在编译时就知道确切大小,可以在栈(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 Ali
Description: Zara Ali is a student.

使用 calloc 类似,但它会将内存初始化为零:

// 分配并零初始化 200 个字符的内存
description = calloc(description_size, sizeof(char));
if (description == NULL) { /* ... error handling ... */ }
// 'description' 现在指向 200 字节的零

动态分配(Dynamic allocation)赋予您在运行时控制内存生命周期和大小的能力,这与固定大小数组不同。

虽然操作系统在程序退出时会回收内存,但作为最佳实践,至关重要的是在不再需要内存时使用 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 Ali
Full 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 上)之类的工具可以帮助检测内存错误。