C语言学习6-动态内存管理指南 malloc & free

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 核心要点回顾

  1. 分配前:确定需要的大小,检查参数有效性
  2. 分配时:检查malloc返回值,处理分配失败
  3. 使用时:确保不越界访问,及时处理错误
  4. 释放时:只free malloc返回的地址,释放后置NULL
  5. 设计时:谁分配谁释放,或明确所有权转移

掌握这些动态内存管理技术,你就能编写出高效、安全的C语言程序!

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容