在之前的学习中,我们使用的数组、结构体等变量都是在编译时确定大小的,这种内存分配方式称为静态内存分配(或自动分配)。
然而,在实际开发中,很多情况下我们无法提前知道需要多少内存,比如读取用户输入、处理可变长度的数据等。
动态内存管理允许程序在运行时从堆(heap)中请求和释放内存,极大地提高了程序的灵活性。
本章将介绍C语言中动态内存管理的四大函数:malloc、calloc、realloc 和 free。
C程序运行时,内存主要分为几个区域:
动态内存管理主要操作的是堆内存。
malloc 函数从堆中分配一块连续的内存,返回指向这块内存起始地址的指针,如果分配失败则返回 NULL。
void *malloc(size_t size);
参数 size 是需要分配的字节数。返回的指针类型为 void*,通常需要强制转换为目标类型。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p;
p = (int*)malloc(5 * sizeof(int)); // 分配可存放5个int的内存
if (p == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 使用分配的内存
for (int i = 0; i < 5; i++) {
p[i] = i * 10;
printf("%d ", p[i]);
}
printf("\n");
// 释放内存
free(p);
p = NULL; // 避免野指针
return 0;
}
malloc 分配的内存中的值是未初始化的,包含随机数据。如果需要初始化,可以使用 calloc 或 memset。
calloc 函数用于分配一块内存,并将其所有字节初始化为0。它接受两个参数:元素个数和每个元素的大小。
void *calloc(size_t nmemb, size_t size);
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)calloc(5, sizeof(int)); // 分配并清零
if (p == NULL) {
printf("分配失败\n");
return 1;
}
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]); // 输出 0 0 0 0 0
}
printf("\n");
free(p);
return 0;
}
malloc 不初始化,速度稍快;calloc 初始化为0,更适合需要清零的场景。
realloc 用于调整已分配内存块的大小,可以扩大或缩小。如果新大小大于原大小,新分配的部分不会初始化;如果缩小,多余部分被释放。
void *realloc(void *ptr, size_t size);
参数 ptr 是之前通过 malloc、calloc 或 realloc 返回的指针,size 是新的字节数。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(3 * sizeof(int));
if (p == NULL) return 1;
p[0] = 10; p[1] = 20; p[2] = 30;
// 扩容到5个元素
int *temp = (int*)realloc(p, 5 * sizeof(int));
if (temp == NULL) {
printf("扩容失败,原内存未被释放\n");
free(p);
return 1;
}
p = temp;
p[3] = 40;
p[4] = 50;
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
printf("\n");
free(p);
return 0;
}
realloc 可能移动原内存块到新的位置,因此返回的指针可能不同于原指针。一定要使用新指针,并检查是否为 NULL。
动态分配的内存必须由程序员手动释放,否则会造成内存泄漏。使用 free 函数释放内存,释放后的指针应置为 NULL 以避免野指针。
void free(void *ptr);
释放同一块内存两次会导致未定义行为(通常程序崩溃)。
int *p = (int*)malloc(100);
if (p != NULL) {
// 使用 p
free(p);
p = NULL; // 好习惯
}
动态数组允许在运行时根据用户需求决定数组大小,并可随时调整。
#include <stdio.h>
#include <stdlib.h>
int main() {
int n;
printf("请输入元素个数: ");
scanf("%d", &n);
// 动态分配数组
int *arr = (int*)malloc(n * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 输入元素
printf("请输入 %d 个整数: ", n);
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
// 输出
printf("数组内容: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 动态扩容:增加新元素
int new;
printf("请输入要追加的元素: ");
scanf("%d", &new);
int *temp = (int*)realloc(arr, (n + 1) * sizeof(int));
if (temp == NULL) {
printf("扩容失败\n");
free(arr);
return 1;
}
arr = temp;
arr[n] = new;
n++;
printf("扩容后数组: ");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *str = NULL;
char buffer[100];
int totalLen = 0;
printf("请输入多行文本(输入空行结束):\n");
while (1) {
fgets(buffer, sizeof(buffer), stdin);
if (buffer[0] == '\n') break; // 空行结束
int len = strlen(buffer);
// 去除换行符
if (buffer[len-1] == '\n') buffer[len-1] = '\0';
// 重新分配内存
char *temp = (char*)realloc(str, totalLen + strlen(buffer) + 1);
if (temp == NULL) {
printf("内存分配失败\n");
free(str);
return 1;
}
str = temp;
if (totalLen == 0) {
strcpy(str, buffer);
} else {
strcat(str, buffer);
}
totalLen += strlen(buffer);
}
printf("拼接后的字符串: %s\n", str);
free(str);
return 0;
}
NULL。free,会导致未定义行为。malloc/calloc/realloc 后检查返回值是否为 NULL。free 后立即将指针设为 NULL。malloc 分配的内存未初始化,直接读取会得到随机值。free 会导致错误。realloc 失败原指针会丢失,且原内存未释放。#include <stdio.h>
#include <stdlib.h>
typedef struct {
int id;
char name[50];
float score;
} Student;
int main() {
Student *students = NULL;
int count = 0;
int choice;
do {
printf("\n===== 学生成绩管理系统 =====\n");
printf("1. 添加学生\n");
printf("2. 查看所有学生\n");
printf("3. 删除所有学生\n");
printf("4. 退出\n");
printf("请选择: ");
scanf("%d", &choice);
switch (choice) {
case 1: {
// 添加学生,动态扩容
Student *temp = (Student*)realloc(students, (count + 1) * sizeof(Student));
if (temp == NULL) {
printf("内存分配失败!\n");
break;
}
students = temp;
printf("请输入学号: ");
scanf("%d", &students[count].id);
printf("请输入姓名: ");
scanf("%s", students[count].name);
printf("请输入成绩: ");
scanf("%f", &students[count].score);
count++;
printf("添加成功!\n");
break;
}
case 2:
if (count == 0) {
printf("暂无学生信息\n");
} else {
printf("\n学号\t姓名\t成绩\n");
for (int i = 0; i < count; i++) {
printf("%d\t%s\t%.1f\n", students[i].id, students[i].name, students[i].score);
}
}
break;
case 3:
if (students != NULL) {
free(students);
students = NULL;
count = 0;
printf("所有学生信息已删除\n");
} else {
printf("没有学生信息\n");
}
break;
case 4:
printf("退出系统\n");
break;
default:
printf("无效选择\n");
}
} while (choice != 4);
// 程序结束前释放内存
if (students != NULL) {
free(students);
}
return 0;
}
动态内存管理是C语言高级编程的核心,它赋予了程序运行时灵活分配内存的能力。熟练掌握 malloc、calloc、realloc 和 free 的使用,并养成良好的内存管理习惯,可以避免内存泄漏和野指针等问题。
下一章我们将学习结构体与联合体,这是组织复杂数据的重要工具。