C语言学习1-数组、字符串
C语言学习2-指针的使用、const 指针
C语言学习3-指针和数组
C语言学习4-结构体
C语言学习5-函数
一、为什么需要动态内存分配?
1.1 静态数组的局限性
#include <stdio.h>
// 问题:数组大小必须在编译时确定
void static_array_limitations() {
// ✅ 可以:编译时常量
int fixed_array[100]; // 固定100个元素
// ❌ 错误:运行时变量作为数组大小
int n;
printf("请输入需要的数组大小: ");
scanf("%d", &n);
// int dynamic_array[n]; // 编译错误!C89标准不支持
}
1.2 动态内存分配的优势
- 按需分配:程序运行时决定需要多少内存
- 灵活调整:可以随时申请和释放
- 避免浪费:不预先分配过多内存
二、malloc 和 free 基础
2.1 内存管理器(MM)概念
系统中有一个内存管理器负责管理闲置内存:
- 应用程序通过
malloc借出内存 - 使用完后通过
free归还内存 - 所有程序共享同一个内存池(堆)
2.2 malloc 函数详解
#include <stdio.h>
#include <stdlib.h>
void malloc_basic_usage() {
// 申请 1024 字节内存
void* raw_ptr = malloc(1024);
// 转换为具体类型
char* char_ptr = (char*)malloc(100); // 100字节字符数组
int* int_ptr = (int*)malloc(10 * sizeof(int)); // 10个整数的数组
double* double_ptr = (double*)malloc(5 * sizeof(double)); // 5个双精度数组
printf("字符指针: %p\n", (void*)char_ptr);
printf("整型指针: %p\n", (void*)int_ptr);
printf("双精度指针: %p\n", (void*)double_ptr);
// 记得释放!
free(raw_ptr);
free(char_ptr);
free(int_ptr);
free(double_ptr);
}
2.3 free 函数详解
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Contact {
int id;
char name[32];
char phone[16];
};
void free_basic_usage() {
// 申请内存
int size = 5 * sizeof(struct Contact);
struct Contact* contacts = (struct Contact*)malloc(size);
if (contacts != NULL) {
// 使用内存
contacts[0].id = 1;
strcpy(contacts[0].name, "张三");
strcpy(contacts[0].phone, "13800138000");
printf("联系人: %s, 电话: %s\n", contacts[0].name, contacts[0].phone);
// 释放内存
free(contacts);
contacts = NULL; // 良好习惯:释放后置为NULL
}
}
三、动态内存的实际应用
3.1 用户自定义大小的数组
#include <stdio.h>
#include <stdlib.h>
void dynamic_array_example() {
int count;
printf("请输入需要存储的联系人数量: ");
scanf("%d", &count);
// 动态分配足够的内存
struct Contact* contacts = (struct Contact*)malloc(count * sizeof(struct Contact));
if (contacts == NULL) {
printf("错误:内存分配失败!\n");
return;
}
// 输入数据
for (int i = 0; i < count; i++) {
printf("\n请输入第 %d 个联系人的信息:\n", i + 1);
contacts[i].id = i + 1;
printf("姓名: ");
scanf("%s", contacts[i].name);
printf("电话: ");
scanf("%s", contacts[i].phone);
}
// 显示数据
printf("\n=== 所有联系人 ===\n");
for (int i = 0; i < count; i++) {
printf("%d. %s - %s\n", contacts[i].id, contacts[i].name, contacts[i].phone);
}
// 释放内存
free(contacts);
contacts = NULL;
}
3.2 指针数组的动态分配
#include <stdio.h>
#include <stdlib.h>
void pointer_array_example() {
int array_size = 5;
// 分配指针数组:包含5个double指针
double** pointer_array = (double**)malloc(array_size * sizeof(double*));
if (pointer_array == NULL) {
printf("指针数组分配失败!\n");
return;
}
// 为每个指针分配内存
for (int i = 0; i < array_size; i++) {
pointer_array[i] = (double*)malloc(sizeof(double));
if (pointer_array[i] == NULL) {
printf("元素 %d 分配失败!\n", i);
// 释放已分配的内存
for (int j = 0; j < i; j++) {
free(pointer_array[j]);
}
free(pointer_array);
return;
}
*pointer_array[i] = i * 1.5; // 赋值
}
// 使用数据
printf("指针数组内容:\n");
for (int i = 0; i < array_size; i++) {
printf("pointer_array[%d] = %.2f\n", i, *pointer_array[i]);
}
// 正确释放:先释放元素,再释放数组
for (int i = 0; i < array_size; i++) {
free(pointer_array[i]);
pointer_array[i] = NULL;
}
free(pointer_array);
pointer_array = NULL;
}
3.3 跨函数使用动态内存
#include <stdio.h>
#include <stdlib.h>
// 修改动态内存内容的函数
void modify_dynamic_memory(int* ptr, int new_value) {
if (ptr != NULL) {
*ptr = new_value;
}
}
// 创建动态内存的函数
int* create_dynamic_integer(int initial_value) {
int* ptr = (int*)malloc(sizeof(int));
if (ptr != NULL) {
*ptr = initial_value;
}
return ptr;
}
// 释放动态内存的函数
void destroy_dynamic_integer(int* ptr) {
if (ptr != NULL) {
free(ptr);
// 注意:这里不能将外部指针置为NULL,调用者需要自己处理
}
}
void cross_function_example() {
// 在一个函数中创建
int* number = create_dynamic_integer(42);
if (number != NULL) {
printf("初始值: %d\n", *number); // 输出: 42
// 在另一个函数中修改
modify_dynamic_memory(number, 100);
printf("修改后: %d\n", *number); // 输出: 100
// 在另一个函数中释放
destroy_dynamic_integer(number);
number = NULL; // 调用者负责置为NULL
}
}
四、对象与动态内存
4.1 对象的概念
在C语言中,对象指的是一块内存区域,用于存储数据。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 汽车结构体
struct Car {
char maker[32];
int price;
};
// 市民结构体
struct Citizen {
char name[32];
int deposit;
struct Car* car; // 指向Car对象的指针
};
// 购买汽车
void buy_car(struct Citizen* owner) {
if (owner == NULL) return;
// 动态创建Car对象
struct Car* new_car = (struct Car*)malloc(sizeof(struct Car));
if (new_car == NULL) {
printf("购车失败:内存不足!\n");
return;
}
strcpy(new_car->maker, "雪佛兰");
new_car->price = 100000;
if (owner->deposit >= new_car->price) {
owner->car = new_car;
owner->deposit -= new_car->price;
printf("%s 购买了 %s,花费 %d 元\n", owner->name, new_car->maker, new_car->price);
} else {
printf("%s 存款不足,无法购买!\n", owner->name);
free(new_car); // 立即释放
}
}
// 报废汽车
void discard_car(struct Citizen* owner) {
if (owner != NULL && owner->car != NULL) {
printf("%s 报废了 %s\n", owner->name, owner->car->maker);
free(owner->car);
owner->car = NULL;
}
}
// 转让汽车
void transfer_car(struct Citizen* from, struct Citizen* to) {
if (from == NULL || to == NULL || from->car == NULL) return;
printf("%s 将 %s 转让给 %s\n", from->name, from->car->maker, to->name);
to->car = from->car;
from->car = NULL; // 原主人不再拥有汽车
}
void object_management_example() {
// 创建市民对象(栈上)
struct Citizen alice = {"Alice", 200000, NULL};
struct Citizen bob = {"Bob", 50000, NULL};
// Alice 购买汽车
buy_car(&alice);
printf("Alice 剩余存款: %d\n", alice.deposit);
// 转让给 Bob
transfer_car(&alice, &bob);
// Bob 报废汽车
discard_car(&bob);
}
五、malloc 和 free 的注意事项
5.1 必须遵守的规则
#include <stdio.h>
#include <stdlib.h>
void malloc_free_rules() {
// ✅ 规则1:只能free通过malloc分配的内存
int* valid_ptr = (int*)malloc(sizeof(int));
free(valid_ptr); // 正确
// ❌ 错误:free非malloc的指针
int stack_variable = 10;
int* stack_ptr = &stack_variable;
// free(stack_ptr); // 运行时错误!
// ✅ 规则2:必须free首地址
char* full_block = (char*)malloc(100);
free(full_block); // 正确:释放整个块
// free(full_block + 50); // 错误:不是首地址
// ✅ 规则3:free后立即置为NULL
int* ptr = (int*)malloc(sizeof(int));
free(ptr);
ptr = NULL; // 防止悬空指针
// ❌ 错误:使用已free的内存
// *ptr = 100; // 未定义行为!
}
5.2 内存泄漏检测
#include <stdio.h>
#include <stdlib.h>
void memory_leak_example() {
// ❌ 内存泄漏:分配后没有释放
for (int i = 0; i < 10; i++) {
int* leak = (int*)malloc(sizeof(int));
*leak = i;
// 忘记 free(leak)!
}
// ✅ 正确:及时释放
for (int i = 0; i < 10; i++) {
int* proper = (int*)malloc(sizeof(int));
if (proper != NULL) {
*proper = i;
// 使用...
free(proper); // 及时释放
proper = NULL;
}
}
}
void infinite_memory_leak() {
// ❌ 危险:无限循环分配内存
/*
while (1) {
void* memory = malloc(1024 * 1024); // 每次1MB
if (memory == NULL) {
printf("内存耗尽!\n");
break;
}
// 没有free!
}
*/
}
5.3 返回值检查
#include <stdio.h>
#include <stdlib.h>
void safe_malloc_usage() {
// 申请大量内存
void* large_block = malloc(1024 * 1024 * 1024); // 1GB
if (large_block == NULL) {
printf("警告:内存分配失败!系统可能内存不足。\n");
// 尝试分配较小的内存
large_block = malloc(1024 * 1024); // 1MB
}
if (large_block != NULL) {
printf("内存分配成功!\n");
// 使用内存...
free(large_block);
} else {
printf("所有内存分配尝试都失败了!\n");
}
}
六、内存操作函数
6.1 memset - 内存设置
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void memset_example() {
// 分配并初始化内存
int* numbers = (int*)malloc(10 * sizeof(int));
if (numbers != NULL) {
// 将内存块全部设置为0
memset(numbers, 0, 10 * sizeof(int));
// 验证初始化结果
for (int i = 0; i < 10; i++) {
printf("numbers[%d] = %d\n", i, numbers[i]); // 全部为0
}
// 设置为特定值(注意:对于int类型要小心)
memset(numbers, 0xFF, 10 * sizeof(int)); // 全部设置为-1
free(numbers);
}
// 字符数组的memset使用
char* text = (char*)malloc(100);
if (text != NULL) {
memset(text, 'A', 99); // 前99字节设置为'A'
text[99] = '\0'; // 最后1字节设置为字符串结束符
printf("文本: %s\n", text);
free(text);
}
}
6.2 memcpy - 内存拷贝
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void memcpy_example() {
int source[5] = {1, 2, 3, 4, 5};
int* destination = (int*)malloc(5 * sizeof(int));
if (destination != NULL) {
// 拷贝整个数组
memcpy(destination, source, 5 * sizeof(int));
printf("拷贝结果:\n");
for (int i = 0; i < 5; i++) {
printf("dest[%d] = %d\n", i, destination[i]);
}
free(destination);
}
// 结构体拷贝
struct Point {
int x, y;
};
struct Point p1 = {10, 20};
struct Point* p2 = (struct Point*)malloc(sizeof(struct Point));
if (p2 != NULL) {
memcpy(p2, &p1, sizeof(struct Point));
printf("点坐标: (%d, %d)\n", p2->x, p2->y);
free(p2);
}
}
6.3 memmove - 安全内存移动
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void memmove_example() {
char buffer[] = "Hello, World!";
printf("移动前: %s\n", buffer);
// 重叠内存区域的移动
// 将 "Hello" 移动到 "World" 的位置
memmove(buffer + 7, buffer, 5);
printf("移动后: %s\n", buffer); // 输出: Hello, Hello!
// 与memcpy的对比
char data1[] = "ABCDEFGHIJ";
char data2[] = "ABCDEFGHIJ";
// memcpy在重叠时可能出错
// memcpy(data1 + 2, data1, 5); // 未定义行为
// memmove总是安全的
memmove(data2 + 2, data2, 5);
printf("memmove结果: %s\n", data2);
}
七、最佳实践总结
7.1 动态内存使用原则
#include <stdio.h>
#include <stdlib.h>
// 模板:安全的动态内存使用模式
void safe_dynamic_memory_pattern() {
// 1. 声明指针并初始化为NULL
int* data = NULL;
size_t element_count = 100;
// 2. 分配内存并检查成功
data = (int*)malloc(element_count * sizeof(int));
if (data == NULL) {
printf("错误:内存分配失败\n");
return; // 早期返回
}
// 3. 使用内存前可以初始化
memset(data, 0, element_count * sizeof(int));
// 4. 安全使用内存
for (size_t i = 0; i < element_count; i++) {
data[i] = (int)(i * i);
}
// 5. 使用完毕后立即释放
free(data);
data = NULL; // 防止悬空指针
printf("动态内存使用完成\n");
}
7.2 错误处理模式
#include <stdio.h>
#include <stdlib.h>
// 带完整错误处理的动态数组
int* create_dynamic_array(size_t size, int initial_value) {
if (size == 0) {
printf("错误:数组大小不能为0\n");
return NULL;
}
int* array = (int*)malloc(size * sizeof(int));
if (array == NULL) {
printf("错误:无法分配 %zu 个整数的内存\n", size);
return NULL;
}
// 初始化数组
for (size_t i = 0; i < size; i++) {
array[i] = initial_value;
}
return array;
}
void cleanup_dynamic_array(int* array) {
if (array != NULL) {
free(array);
// 注意:这里不能修改外部指针,调用者需要自己置NULL
}
}
void demonstrate_best_practices() {
int* numbers = NULL;
numbers = create_dynamic_array(100, 0);
if (numbers == NULL) {
return; // 分配失败,直接返回
}
// 使用数组...
for (int i = 0; i < 100; i++) {
numbers[i] = i * 2;
}
// 清理
cleanup_dynamic_array(numbers);
numbers = NULL; // 调用者负责置NULL
}
7.3 核心要点回顾
- 分配前:确定需要的大小,检查参数有效性
- 分配时:检查malloc返回值,处理分配失败
- 使用时:确保不越界访问,及时处理错误
- 释放时:只free malloc返回的地址,释放后置NULL
- 设计时:谁分配谁释放,或明确所有权转移
掌握这些动态内存管理技术,你就能编写出高效、安全的C语言程序!