目录
[1.1 内存分配函数总览](#1.1 内存分配函数总览)
[1.2 malloc - 动态分配内存](#1.2 malloc - 动态分配内存)
[1.3 calloc - 分配并初始化](#1.3 calloc - 分配并初始化)
[malloc vs calloc](#malloc vs calloc)
[1.4 realloc - 重新分配内存](#1.4 realloc - 重新分配内存)
[1.5 free - 释放内存](#1.5 free - 释放内存)
[2.1 动态数组实现](#2.1 动态数组实现)
[2.2 动态二维数组](#2.2 动态二维数组)
[2.3 动态字符串](#2.3 动态字符串)
[3.1 内存分区详解](#3.1 内存分区详解)
[3.2 各区域特点对比](#3.2 各区域特点对比)
[3.3 变量存储位置示例](#3.3 变量存储位置示例)
[3.4 内存泄漏演示](#3.4 内存泄漏演示)
[4.1 虚拟内存原理](#4.1 虚拟内存原理)
[4.2 延迟分配(Lazy Allocation)](#4.2 延迟分配(Lazy Allocation))
[4.3 内存过载测试](#4.3 内存过载测试)
[5.1 错误类型总结](#5.1 错误类型总结)
[5.2 安全编程模板](#5.2 安全编程模板)
[5.3 内存调试技巧](#5.3 内存调试技巧)
[6.1 学生成绩管理系统(动态版)](#6.1 学生成绩管理系统(动态版))
动态内存 :程序运行时根据需要手动申请和释放的内存空间
核心价值:灵活管理内存、突破栈空间限制、实现动态数据结构
1、动态内存分配函数
1.1 内存分配函数总览
| 函数 | 头文件 | 功能 | 返回值 | 特点 |
|---|---|---|---|---|
malloc() |
stdlib.h | 分配指定字节 | void* | 不初始化 |
calloc() |
stdlib.h | 分配并初始化为0 | void* | 自动清零 |
realloc() |
stdlib.h | 重新分配大小 | void* | 保留原数据 |
free() |
stdlib.h | 释放内存 | void | 必须配对使用 |
1.2 malloc - 动态分配内存
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 基本用法
// 申请10个int的连续空间(40字节)
int *p = (int*)malloc(10 * sizeof(int));
// 重要:检查是否分配成功
if (p == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 赋值(两种方式等价)
for (int i = 0; i < 10; i++) {
*(p + i) = (i + 1) * 10; // 指针运算
// p[i] = (i + 1) * 10; // 数组下标(推荐)
}
// 遍历
printf("malloc分配的数据:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", p[i]);
}
printf("\n");
// 释放内存(必须!)
free(p);
p = NULL; // 避免悬空指针
return 0;
}
malloc特点
| 特性 | 说明 |
|---|---|
| 单位 | 字节(byte) |
| 返回值 | void*(无类型指针) |
| 初始化 | ❌ 不初始化(内容是随机值) |
| 失败返回 | NULL |
| 类型转换 | C语言自动转换,C++需显式转换 |
cpp
// C语言(可省略强转)
int *p1 = malloc(100);
int *p2 = (int*)malloc(100); // 推荐写法,更清晰
// C++(必须强转)
// int *p = (int*)malloc(100);
1.3 calloc - 分配并初始化
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// calloc(元素个数, 单个元素大小)
// 自动初始化为0
int *p = (int*)calloc(10, sizeof(int));
if (p == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 验证初始化为0
printf("calloc初始值:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", p[i]); // 全部输出0
}
printf("\n");
free(p);
p = NULL;
return 0;
}
malloc vs calloc
| 特性 | malloc | calloc |
|---|---|---|
| 参数 | 总字节数 | (元素个数, 元素大小) |
| 初始化 | 随机值 | 自动清零 |
| 效率 | 稍高 | 稍低(多一步初始化) |
| 适用场景 | 会立即赋值 | 需要初始化为0 |
1.4 realloc - 重新分配内存
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 1. 初始分配5个int
int *p = (int*)malloc(5 * sizeof(int));
if (p == NULL) {
return 1;
}
// 2. 初始化数据
for (int i = 0; i < 5; i++) {
p[i] = (i + 1) * 10;
}
// 3. 扩容到10个int
// 重要:使用临时指针接收返回值
int *temp = (int*)realloc(p, 10 * sizeof(int));
if (temp == NULL) {
// 扩容失败,原空间仍可用
printf("扩容失败!\n");
free(p);
return 1;
}
// 扩容成功,更新指针
p = temp;
// 4. 验证原数据保留
printf("扩容后原数据:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]); // 10 20 30 40 50
}
printf("\n");
// 5. 使用新空间
for (int i = 5; i < 10; i++) {
p[i] = (i + 1) * 10;
}
printf("扩容后全部数据:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", p[i]);
}
printf("\n");
// 6. 缩容(也支持)
int *temp2 = (int*)realloc(p, 3 * sizeof(int));
if (temp2 != NULL) {
p = temp2;
printf("缩容后数据:\n");
for (int i = 0; i < 3; i++) {
printf("%d ", p[i]); // 10 20 30
}
printf("\n");
}
free(p);
p = NULL;
return 0;
}
realloc注意事项
| 情况 | 说明 |
|---|---|
| 地址可能变 | 扩容后地址可能改变 |
| 原数据保留 | 保留到新空间(按较小容量) |
| 失败返回NULL | 原空间仍有效,需手动free |
| 无需free原指针 | 成功时底层已处理 |
1.5 free - 释放内存
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = (int*)malloc(100 * sizeof(int));
if (p != NULL) {
// 使用内存...
// 正确释放
free(p);
p = NULL; // 避免悬空指针
}
// 错误做法
// free(p);
// free(p); // 重复释放,未定义行为!
// 错误做法
// free(p);
// *p = 10; // 使用已释放内存,危险!
return 0;
}
free使用规则
cpp
✅ 应该做的:
1. 谁malloc,谁free(配对使用)
2. free后立即置NULL
3. 只free动态分配的内存
4. 检查指针不为NULL再free
❌ 不应该做的:
1. 重复free同一指针
2. free后继续使用
3. free栈内存或常量区内存
4. free部分数组(必须整体释放)
2、动态内存实践
2.1 动态数组实现
cpp
#include <stdio.h>
#include <stdlib.h>
typedef struct {
int *data; // 数据指针
int size; // 当前元素个数
int capacity; // 总容量
} DynamicArray;
// 初始化
void initArray(DynamicArray *arr, int initialCapacity) {
arr->data = (int*)malloc(initialCapacity * sizeof(int));
if (arr->data == NULL) {
printf("初始化失败!\n");
arr->capacity = 0;
arr->size = 0;
return;
}
arr->size = 0;
arr->capacity = initialCapacity;
}
// 添加元素(自动扩容)
void pushBack(DynamicArray *arr, int value) {
// 容量不足时扩容(翻倍)
if (arr->size >= arr->capacity) {
int newCapacity = arr->capacity * 2;
int *temp = (int*)realloc(arr->data, newCapacity * sizeof(int));
if (temp == NULL) {
printf("扩容失败!\n");
return;
}
arr->data = temp;
arr->capacity = newCapacity;
printf("扩容到: %d\n", arr->capacity);
}
arr->data[arr->size++] = value;
}
// 获取元素
int get(DynamicArray *arr, int index) {
if (index < 0 || index >= arr->size) {
printf("索引越界!\n");
return -1;
}
return arr->data[index];
}
// 释放
void freeArray(DynamicArray *arr) {
if (arr->data != NULL) {
free(arr->data);
arr->data = NULL;
}
arr->size = 0;
arr->capacity = 0;
}
// 打印
void printArray(DynamicArray *arr) {
printf("[");
for (int i = 0; i < arr->size; i++) {
printf("%d", arr->data[i]);
if (i < arr->size - 1) printf(", ");
}
printf("]\n");
}
int main() {
DynamicArray arr;
initArray(&arr, 5);
// 添加15个元素(会触发多次扩容)
for (int i = 0; i < 15; i++) {
pushBack(&arr, i * 10);
}
printf("最终数组: ");
printArray(&arr);
printf("大小: %d, 容量: %d\n", arr.size, arr.capacity);
// 获取元素
printf("第5个元素: %d\n", get(&arr, 4));
// 释放
freeArray(&arr);
return 0;
}
2.2 动态二维数组
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int rows = 3, cols = 4;
// 方式1:指针数组(每行可不同长度)
int **arr1 = (int**)malloc(rows * sizeof(int*));
if (arr1 == NULL) {
return 1;
}
// 为每行分配内存
for (int i = 0; i < rows; i++) {
arr1[i] = (int*)malloc(cols * sizeof(int));
if (arr1[i] == NULL) {
// 释放已分配的行
for (int j = 0; j < i; j++) {
free(arr1[j]);
}
free(arr1);
return 1;
}
}
// 赋值
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
arr1[i][j] = i * cols + j;
}
}
// 打印
printf("动态二维数组:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%4d", arr1[i][j]);
}
printf("\n");
}
// 释放(先释放每行,再释放行指针)
for (int i = 0; i < rows; i++) {
free(arr1[i]);
}
free(arr1);
// 方式2:连续内存(性能更好)
int *arr2 = (int*)malloc(rows * cols * sizeof(int));
// 访问:arr2[i * cols + j]
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
arr2[i * cols + j] = i * cols + j;
}
}
free(arr2);
return 0;
}
2.3 动态字符串
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 动态字符串结构
typedef struct {
char *data;
int length;
int capacity;
} DynamicString;
// 初始化
void initString(DynamicString *str, const char *initial) {
if (initial == NULL) {
str->capacity = 16;
str->data = (char*)malloc(str->capacity);
str->data[0] = '\0';
str->length = 0;
} else {
str->length = strlen(initial);
str->capacity = str->length + 1;
str->data = (char*)malloc(str->capacity);
strcpy(str->data, initial);
}
}
// 追加字符串
void appendString(DynamicString *str, const char *append) {
if (append == NULL) return;
int appendLen = strlen(append);
int newLength = str->length + appendLen;
// 容量不足时扩容
if (newLength + 1 > str->capacity) {
int newCapacity = (newLength + 1) * 2;
char *temp = (char*)realloc(str->data, newCapacity);
if (temp == NULL) {
printf("扩容失败!\n");
return;
}
str->data = temp;
str->capacity = newCapacity;
}
// 追加
strcpy(str->data + str->length, append);
str->length = newLength;
}
// 释放
void freeString(DynamicString *str) {
if (str->data != NULL) {
free(str->data);
str->data = NULL;
}
str->length = 0;
str->capacity = 0;
}
int main() {
DynamicString str;
initString(&str, "Hello");
printf("初始: %s\n", str.data);
appendString(&str, ", ");
appendString(&str, "World");
appendString(&str, "!");
printf("追加后: %s\n", str.data);
printf("长度: %d, 容量: %d\n", str.length, str.capacity);
freeString(&str);
return 0;
}
3、C语言内存结构
3.1 内存分区详解
cpp
┌─────────────────────────────────────────┐
│ 高地址 │
├─────────────────────────────────────────┤
│ 栈区 (Stack) │
│ - 局部变量 │
│ - 函数参数 │
│ - 函数调用信息 │
│ - 自动分配和释放 │
│ - 大小有限(通常1-8MB) │
├─────────────────────────────────────────┤
│ ↓ 栈向下增长 │
│ ↑ 堆向上增长 │
├─────────────────────────────────────────┤
│ 堆区 (Heap) │
│ - malloc/calloc/realloc分配 │
│ - 手动管理(需要free) │
│ - 大小较大(受物理内存限制) │
│ - 可能产生碎片 │
├─────────────────────────────────────────┤
│ 未初始化静态区 (BSS) │
│ - 未初始化的全局变量 │
│ - 未初始化的static变量 │
│ - 自动初始化为0 │
├─────────────────────────────────────────┤
│ 已初始化静态区 (Data) │
│ - 已初始化的全局变量 │
│ - 已初始化的static变量 │
│ - 常量数据 │
├─────────────────────────────────────────┤
│ 常量区 (Read-Only) │
│ - 字符串常量 │
│ - const修饰的常量 │
│ - 只读,不可修改 │
├─────────────────────────────────────────┤
│ 代码区 (Text/Code) │
│ - 程序指令 │
│ - 函数代码 │
│ - 只读,可执行 │
└─────────────────────────────────────────┘
│ 低地址 │
3.2 各区域特点对比
| 区域 | 存储内容 | 生命周期 | 管理方式 | 大小限制 |
|---|---|---|---|---|
| 栈区 | 局部变量、参数 | 函数结束 | 自动 | 小(1-8MB) |
| 堆区 | 动态分配内存 | 手动free | 手动 | 大(受内存限制) |
| 静态区 | 全局/static变量 | 程序全程 | 自动 | 中等 |
| 常量区 | 字符串常量 | 程序全程 | 只读 | 中等 |
| 代码区 | 程序指令 | 程序全程 | 只读 | 中等 |
3.3 变量存储位置示例
cpp
#include <stdio.h>
#include <stdlib.h>
// 全局变量(已初始化静态区)
int globalVar = 100;
// 全局变量(未初始化静态区/BSS)
int globalVar2;
// 常量区
const char *strConst = "Hello World";
void func(int param) { // param在栈区
// 栈区
int localVar = 200;
static int staticVar = 300; // 已初始化静态区
// 堆区
int *heapVar = (int*)malloc(sizeof(int));
*heapVar = 400;
printf("全局变量地址: %p\n", (void*)&globalVar);
printf("全局变量2地址: %p\n", (void*)&globalVar2);
printf("局部变量地址: %p\n", (void*)&localVar);
printf("静态变量地址: %p\n", (void*)&staticVar);
printf("堆变量地址: %p\n", (void*)heapVar);
printf("常量字符串地址: %p\n", (void*)strConst);
printf("函数参数地址: %p\n", (void*)¶m);
printf("函数代码地址: %p\n", (void*)func);
free(heapVar);
}
int main() {
func(500);
return 0;
}
3.4 内存泄漏演示
cpp
#include <stdio.h>
#include <stdlib.h>
// 内存泄漏示例
void memoryLeak() {
int *p = (int*)malloc(100 * sizeof(int));
// 忘记free,程序结束后内存无法回收
}
// 正确做法
void noLeak() {
int *p = (int*)malloc(100 * sizeof(int));
if (p != NULL) {
// 使用...
free(p);
p = NULL;
}
}
// 悬空指针
void danglingPointer() {
int *p = (int*)malloc(100 * sizeof(int));
free(p);
*p = 10; // 危险!使用已释放内存
}
// 避免悬空指针
void safePointer() {
int *p = (int*)malloc(100 * sizeof(int));
free(p);
p = NULL; // 置NULL
// if (p != NULL) *p = 10; // 安全检查
}
int main() {
// 模拟多次调用导致内存泄漏
for (int i = 0; i < 1000; i++) {
memoryLeak(); // 每次泄漏400字节
}
printf("程序结束,泄漏的内存由操作系统回收\n");
return 0;
}
4、虚拟内存机制
4.1 虚拟内存原理
cpp
物理内存有限 → 虚拟内存扩展
程序看到的地址 = 虚拟地址
实际存储的地址 = 物理地址
操作系统通过页表映射虚拟地址到物理地址
4.2 延迟分配(Lazy Allocation)
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
// 申请1GB空间(虚拟地址)
char *p = (char*)malloc(1024 * 1024 * 1024);
if (p == NULL) {
printf("申请失败\n");
return 1;
}
printf("申请成功(虚拟地址)\n");
// 此时并未真正分配物理内存
// 只有实际写入数据时才会分配物理页
// 触发物理内存分配
p[0] = 'A'; // 第一页
p[1024 * 1024] = 'B'; // 第二页
printf("写入数据后,物理内存才真正分配\n");
free(p);
return 0;
}
4.3 内存过载测试
cpp
#include <stdio.h>
#include <stdlib.h>
int main() {
int count = 0;
size_t total = 0;
size_t blockSize = 10 * 1024 * 1024; // 10MB
printf("开始申请内存...\n");
while (1) {
char *p = (char*)malloc(blockSize);
if (p == NULL) {
printf("\n申请失败!\n");
printf("成功申请次数: %d\n", count);
printf("总申请大小: %.2f GB\n", total / (1024.0 * 1024.0 * 1024.0));
break;
}
// 写入数据触发物理分配
p[0] = 'A';
count++;
total += blockSize;
if (count % 10 == 0) {
printf("已申请: %d 块 (%.2f GB)\n",
count, total / (1024.0 * 1024.0 * 1024.0));
}
// 实际项目应该保存指针以便后续free
// 这里为了演示泄漏,故意不free
}
return 0;
}
5、常见错误与避坑指南
5.1 错误类型总结
| 错误类型 | 错误示例 | 后果 | 正确做法 |
|---|---|---|---|
| 忘记free | malloc后不释放 |
内存泄漏 | 配对使用free |
| 重复free | free(p); free(p); |
程序崩溃 | free后置NULL |
| 悬空指针 | free(p); *p=10; |
未定义行为 | free后置NULL |
| 越界访问 | p[100](只分配50) |
数据损坏 | 记录大小并检查 |
| 未检查NULL | 直接使用malloc返回值 | 可能崩溃 | if(p==NULL)检查 |
| 错误大小 | malloc(10)(应为10*sizeof(int)) |
空间不足 | 使用sizeof |
| free栈内存 | free(&localVar) |
程序崩溃 | 只free动态内存 |
| realloc丢失 | p = realloc(p, size) |
原指针丢失 | 用临时指针接收 |
5.2 安全编程模板
cpp
#include <stdio.h>
#include <stdlib.h>
// 安全的内存分配函数
void* safeMalloc(size_t size) {
void *p = malloc(size);
if (p == NULL) {
fprintf(stderr, "内存分配失败!需要 %zu 字节\n", size);
exit(1);
}
return p;
}
// 安全的realloc
void* safeRealloc(void *ptr, size_t newSize) {
void *temp = realloc(ptr, newSize);
if (temp == NULL && newSize > 0) {
fprintf(stderr, "内存重新分配失败!\n");
free(ptr); // 释放原内存
exit(1);
}
return temp;
}
// 安全的free
void safeFree(void **ptr) {
if (ptr != NULL && *ptr != NULL) {
free(*ptr);
*ptr = NULL;
}
}
int main() {
// 使用安全函数
int *p = (int*)safeMalloc(100 * sizeof(int));
// 使用...
// 安全释放
safeFree((void**)&p);
return 0;
}
5.3 内存调试技巧
cpp
#include <stdio.h>
#include <stdlib.h>
// 调试宏
#ifdef DEBUG
#define MALLOC(size) \
printf("malloc %zu bytes at %s:%d\n", size, __FILE__, __LINE__), \
malloc(size)
#define FREE(ptr) \
printf("free %p at %s:%d\n", ptr, __FILE__, __LINE__), \
free(ptr)
#else
#define MALLOC(size) malloc(size)
#define FREE(ptr) free(ptr)
#endif
int main() {
int *p = (int*)MALLOC(100 * sizeof(int));
// 使用...
FREE(p);
p = NULL;
return 0;
}
6、综合练习
6.1 学生成绩管理系统(动态版)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int id;
char *name;
float *scores;
int scoreCount;
} Student;
typedef struct {
Student *students;
int count;
int capacity;
} StudentManager;
// 初始化
void initManager(StudentManager *mgr, int initialCapacity) {
mgr->capacity = initialCapacity;
mgr->count = 0;
mgr->students = (Student*)malloc(initialCapacity * sizeof(Student));
}
// 添加学生
void addStudent(StudentManager *mgr, int id, const char *name, float scores[], int scoreCount) {
// 扩容
if (mgr->count >= mgr->capacity) {
mgr->capacity *= 2;
mgr->students = (Student*)realloc(mgr->students, mgr->capacity * sizeof(Student));
}
Student *s = &mgr->students[mgr->count];
s->id = id;
s->name = (char*)malloc(strlen(name) + 1);
strcpy(s->name, name);
s->scoreCount = scoreCount;
s->scores = (float*)malloc(scoreCount * sizeof(float));
memcpy(s->scores, scores, scoreCount * sizeof(float));
mgr->count++;
}
// 释放单个学生
void freeStudent(Student *s) {
if (s->name != NULL) {
free(s->name);
s->name = NULL;
}
if (s->scores != NULL) {
free(s->scores);
s->scores = NULL;
}
}
// 释放管理器
void freeManager(StudentManager *mgr) {
for (int i = 0; i < mgr->count; i++) {
freeStudent(&mgr->students[i]);
}
if (mgr->students != NULL) {
free(mgr->students);
mgr->students = NULL;
}
mgr->count = 0;
mgr->capacity = 0;
}
// 打印所有学生
void printAll(StudentManager *mgr) {
printf("\n=== 学生列表 ===\n");
for (int i = 0; i < mgr->count; i++) {
Student *s = &mgr->students[i];
printf("学号: %d, 姓名: %s, 成绩: ", s->id, s->name);
for (int j = 0; j < s->scoreCount; j++) {
printf("%.1f ", s->scores[j]);
}
printf("\n");
}
}
int main() {
StudentManager mgr;
initManager(&mgr, 5);
// 添加学生
float scores1[] = {85.0, 90.0, 78.0};
float scores2[] = {92.0, 88.0, 95.0};
float scores3[] = {76.0, 82.0, 80.0};
addStudent(&mgr, 1001, "张三", scores1, 3);
addStudent(&mgr, 1002, "李四", scores2, 3);
addStudent(&mgr, 1003, "王五", scores3, 3);
printAll(&mgr);
// 释放
freeManager(&mgr);
printf("\n内存已释放\n");
return 0;
}
7、最佳实践总结
cpp
内存管理原则:
1. 谁分配,谁释放(责任明确)
2. 分配后立即检查NULL
3. 释放后立即置NULL
4. 使用sizeof计算大小
5. 记录分配的大小
6. 避免内存泄漏
7. 避免悬空指针
8. 避免重复释放
代码规范:
1. 统一分配/释放风格
2. 使用辅助函数封装
3. 添加错误处理
4. 使用调试宏追踪
5. 定期用工具检测(valgrind)
性能优化:
1. 批量分配减少碎片
2. 合理预估容量
3. 避免频繁realloc
4. 及时释放不用的内存