【20天学C语言】Day 15: 动态内存管理
📅 学习时间:4-5小时
🎯 学习目标:掌握malloc、calloc、realloc、free的使用
💡 难度:★★★★☆
📝 Day 14 练习题答案
练习1答案:反转单词顺序
#include <stdio.h>
#include <string.h>
void reverse(char *start, char *end) {
while (start < end) {
char temp = *start;
*start++ = *end;
*end— = temp;
}
}
void reverse_words(char *s) {
int len = strlen(s);
// 先整体反转
reverse(s, s + len – 1);
// 再反转每个单词
char *start = s;
while (*start) {
while (*start == ' ') start++;
if (!*start) break;
char *end = start;
while (*end && *end != ' ') end++;
reverse(start, end – 1);
start = end;
}
}
int main(void)
{
char str[] = "Hello World";
reverse_words(str);
printf("结果: %s\\n", str); // "World Hello"
return 0;
}
练习2答案:删除空格
#include <stdio.h>
void remove_spaces(char *s) {
char *p = s;
while (*s) {
if (*s != ' ') {
*p++ = *s;
}
s++;
}
*p = '\\0';
}
int main(void)
{
char str[] = "H e l l o W o r l d";
remove_spaces(str);
printf("结果: %s\\n", str); // "HelloWorld"
return 0;
}
练习3答案:大小写转换
#include <stdio.h>
void to_upper(char *s) {
while (*s) {
if (*s >= 'a' && *s <= 'z') {
*s -= 32;
}
s++;
}
}
void to_lower(char *s) {
while (*s) {
if (*s >= 'A' && *s <= 'Z') {
*s += 32;
}
s++;
}
}
int main(void)
{
char s1[] = "Hello World";
char s2[] = "Hello World";
to_upper(s1);
to_lower(s2);
printf("大写: %s\\n", s1);
printf("小写: %s\\n", s2);
return 0;
}
练习4答案:字母异位词
#include <stdio.h>
#include <string.h>
int is_anagram(const char *s1, const char *s2) {
int count[26] = {0};
while (*s1) {
if (*s1 >= 'a' && *s1 <= 'z') count[*s1 – 'a']++;
s1++;
}
while (*s2) {
if (*s2 >= 'a' && *s2 <= 'z') count[*s2 – 'a']—;
s2++;
}
for (int i = 0; i < 26; i++) {
if (count[i] != 0) return 0;
}
return 1;
}
int main(void)
{
printf("listen和silent: %s\\n",
is_anagram("listen", "silent") ? "是" : "否");
return 0;
}
1. 知识点概述
今天我们将学习:
- 静态内存 vs 动态内存
- malloc函数
- calloc函数
- realloc函数
- free函数
- 内存泄漏问题
- 悬空指针问题
- 动态数组的实现
2. 详细讲解
2.1 内存区域
程序内存布局:
+——————+ 高地址
| 栈区 | 局部变量、函数参数
+——————+
| ↓ | 栈向下增长
| |
| ↑ | 堆向上增长
+——————+
| 堆区 | 动态分配 (malloc/calloc/realloc)
+——————+
| 全局/静态区 | 全局变量、static变量
+——————+
| 代码区 | 程序代码、字符串常量
+——————+ 低地址
2.2 为什么需要动态内存
#include <stdio.h>
int main(void)
{
// 静态数组:编译时确定大小
int arr[100]; // 如果需要更多怎么办?
// 变长数组(VLA):有局限性
int n;
printf("输入数组大小: ");
scanf("%d", &n);
int vla[n]; // C99支持,但有栈溢出风险
// 动态内存:运行时决定大小,空间更大
// 使用malloc等函数
return 0;
}
2.3 malloc函数
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// malloc: memory allocation
// void *malloc(size_t size);
// 分配size字节的内存,返回首地址,失败返回NULL
// 分配10个int的空间
int *arr = (int*)malloc(10 * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\\n");
return 1;
}
// 使用动态数组
for (int i = 0; i < 10; i++) {
arr[i] = i * 10;
}
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\\n");
// 释放内存
free(arr);
arr = NULL; // 避免悬空指针
return 0;
}
2.4 calloc函数
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// calloc: clear allocation
// void *calloc(size_t num, size_t size);
// 分配num个size大小的元素,并初始化为0
// 分配10个int,全部初始化为0
int *arr = (int*)calloc(10, sizeof(int));
if (arr == NULL) {
printf("内存分配失败\\n");
return 1;
}
// 验证全为0
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]); // 全是0
}
printf("\\n");
free(arr);
return 0;
}
2.5 realloc函数
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
// realloc: re-allocation
// void *realloc(void *ptr, size_t new_size);
// 调整已分配内存的大小
int *arr = (int*)malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
printf("扩容前: ");
for (int i = 0; i < 5; i++) printf("%d ", arr[i]);
printf("\\n");
// 扩容到10个int
int *new_arr = (int*)realloc(arr, 10 * sizeof(int));
if (new_arr == NULL) {
printf("扩容失败\\n");
free(arr);
return 1;
}
arr = new_arr; // 更新指针
// 新增的空间需要初始化
for (int i = 5; i < 10; i++) {
arr[i] = i + 1;
}
printf("扩容后: ");
for (int i = 0; i < 10; i++) printf("%d ", arr[i]);
printf("\\n");
free(arr);
return 0;
}
2.6 free函数
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = (int*)malloc(sizeof(int));
*p = 100;
// free释放内存
free(p);
// 释放后p成为悬空指针
// *p = 200; // ❌ 危险!
// 置空避免悬空指针
p = NULL;
// free(NULL)是安全的
free(NULL);
return 0;
}
2.7 内存泄漏
#include <stdio.h>
#include <stdlib.h>
// ❌ 内存泄漏示例
void memory_leak(void) {
int *p = (int*)malloc(100 * sizeof(int));
// 忘记free,内存泄漏!
}
void memory_leak2(void) {
int *p = (int*)malloc(100 * sizeof(int));
p = (int*)malloc(200 * sizeof(int)); // 第一块内存泄漏!
free(p);
}
// ✅ 正确做法
void no_leak(void) {
int *p = (int*)malloc(100 * sizeof(int));
// 使用p…
free(p);
p = NULL;
}
int main(void)
{
for (int i = 0; i < 1000000; i++) {
memory_leak(); // 会耗尽内存!
}
return 0;
}
2.8 悬空指针
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = (int*)malloc(sizeof(int));
*p = 100;
free(p);
// p现在是悬空指针,指向已释放的内存
// ❌ 以下操作都是危险的
// *p = 200; // 写悬空指针
// printf("%d", *p); // 读悬空指针
// free(p); // 重复释放
// ✅ 正确做法:置空
p = NULL;
return 0;
}
2.9 动态二维数组
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int rows = 3, cols = 4;
// 方法1:指针数组
int **matrix = (int**)malloc(rows * sizeof(int*));
for (int i = 0; i < rows; i++) {
matrix[i] = (int*)malloc(cols * sizeof(int));
}
// 使用
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
printf("%3d ", matrix[i][j]);
}
printf("\\n");
}
// 释放(先释放内层,再释放外层)
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
return 0;
}
3. 代码示例
示例1:动态增长的数组
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data;
int size;
int capacity;
} DynamicArray;
void init(DynamicArray *arr) {
arr->capacity = 4;
arr->size = 0;
arr->data = (int*)malloc(arr->capacity * sizeof(int));
}
void push(DynamicArray *arr, int value) {
if (arr->size >= arr->capacity) {
arr->capacity *= 2;
arr->data = (int*)realloc(arr->data, arr->capacity * sizeof(int));
}
arr->data[arr->size++] = value;
}
void destroy(DynamicArray *arr) {
free(arr->data);
arr->data = NULL;
arr->size = arr->capacity = 0;
}
int main(void)
{
DynamicArray arr;
init(&arr);
for (int i = 0; i < 20; i++) {
push(&arr, i * 10);
printf("size=%d, capacity=%d\\n", arr.size, arr.capacity);
}
printf("数组内容: ");
for (int i = 0; i < arr.size; i++) {
printf("%d ", arr.data[i]);
}
printf("\\n");
destroy(&arr);
return 0;
}
示例2:动态字符串
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* concat_strings(const char *s1, const char *s2) {
int len1 = strlen(s1);
int len2 = strlen(s2);
char *result = (char*)malloc(len1 + len2 + 1);
if (result == NULL) return NULL;
strcpy(result, s1);
strcat(result, s2);
return result; // 调用者负责free
}
int main(void)
{
char *str = concat_strings("Hello, ", "World!");
if (str) {
printf("%s\\n", str);
free(str);
}
return 0;
}
示例3:读取文件到动态内存
#include <stdio.h>
#include <stdlib.h>
char* read_file(const char *filename) {
FILE *fp = fopen(filename, "r");
if (!fp) return NULL;
// 获取文件大小
fseek(fp, 0, SEEK_END);
long size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// 分配内存
char *content = (char*)malloc(size + 1);
if (!content) {
fclose(fp);
return NULL;
}
// 读取内容
fread(content, 1, size, fp);
content[size] = '\\0';
fclose(fp);
return content;
}
int main(void)
{
char *content = read_file("test.txt");
if (content) {
printf("%s\\n", content);
free(content);
}
return 0;
}
4. 常见错误(踩坑指南)
错误1:忘记检查返回值
// ❌ 危险代码
int *p = (int*)malloc(sizeof(int));
*p = 100; // 如果malloc失败,p是NULL
// ✅ 正确代码
int *p = (int*)malloc(sizeof(int));
if (p == NULL) {
printf("分配失败\\n");
return;
}
*p = 100;
错误2:内存泄漏
// ❌ 泄漏
void func(void) {
int *p = malloc(100);
// 没有free
}
// ✅ 正确
void func(void) {
int *p = malloc(100);
// 使用…
free(p);
}
错误3:重复释放
// ❌ 危险
int *p = malloc(sizeof(int));
free(p);
free(p); // 重复释放!
// ✅ 正确
int *p = malloc(sizeof(int));
free(p);
p = NULL;
free(p); // free(NULL)是安全的
错误4:realloc后使用旧指针
// ❌ 危险
int *p = malloc(10 * sizeof(int));
realloc(p, 20 * sizeof(int)); // 忽略返回值!
// ✅ 正确
int *p = malloc(10 * sizeof(int));
int *new_p = realloc(p, 20 * sizeof(int));
if (new_p) {
p = new_p;
} else {
// 处理失败,原p仍然有效
}
5. 练习题目
练习1:动态整数数组(难度:★★☆☆☆)
实现一个函数,从用户输入读取n个整数到动态数组。
练习2:动态字符串复制(难度:★★☆☆☆)
实现strdup函数:复制字符串到新分配的内存。
练习3:动态矩阵(难度:★★★☆☆)
实现动态二维数组的创建、填充、打印和释放函数。
练习4:简易内存池(难度:★★★★☆)
实现一个简单的内存池管理器。
6. 小结
今日核心要点
[内存分配函数]
1. malloc(size) – 分配,不初始化
2. calloc(n, size) – 分配并初始化为0
3. realloc(ptr, size) – 调整大小
4. free(ptr) – 释放
[使用规则]
5. 始终检查返回值
6. 配对使用:malloc对应free
7. 释放后置空
[常见问题]
8. 内存泄漏:忘记free
9. 悬空指针:使用已释放的指针
10. 重复释放:free两次
11. 越界访问:超出分配的范围
[最佳实践]
12. 谁分配,谁释放
13. 使用完立即释放
14. 释放后置NULL
下一篇预告: Day 16 – 结构体
我们将学习结构体的定义、使用、结构体指针等内容。
练习题答案将在 Day 16 开头公布
网硕互联帮助中心



评论前必须登录!
注册