C语言:内存管理

一、内存布局底层逻辑

C 语言程序运行时,内存会被划分成 5 个核心区域,不同区域的内存,生命周期、分配方式、使用规则完全不同

内存区域存储内容生命周期分配 / 释放方式大小限制核心特点
代码区编译后的机器指令(main等函数)程序运行期间存在操作系统自动分配释放通常较小只读(写操作会触发段错误)
常量存储区字符串常量(如"hello")、const全局变量程序运行期间存在操作系统自动分配释放较小只读(修改会触发段错误)
全局 / 静态存储区全局变量)、静态变量程序运行期间存在操作系统自动分配释放中等未初始化的全局 / 静态变量会默认置 0
堆(Heap)动态申请的内存(如数组、结构体)手动控制(mallocfree程序员手动分配(malloc等)、释放(free较大(取决于物理内存)内存碎片多,需手动管理生命周期
栈(Stack)局部变量(int a;)、函数参数、返回地址随函数调用创建,函数返回销毁编译器自动分配释放较小(默认几 MB,可配置)先进后出(栈帧机制),溢出会崩溃

二、动态内存管理

C 语言的动态内存管理,本质是 “操作堆内存”,通过malloc/calloc/realloc/free,实现堆内存的分配、扩容、释放

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // 1 malloc:分配未初始化内存
    int* malloc_arr = (int*)malloc(5 * sizeof(int));
    if (malloc_arr == NULL) {
        perror("malloc failed");
        return 1;
    }
    printf("=== malloc示例 ===\n");
    for (int i = 0; i < 5; i++) {
        malloc_arr[i] = i * 10;  // 手动初始化
        printf("%d ", malloc_arr[i]);  // 输出:0 10 20 30 40
    }
    printf("\n\n");


    // 2 calloc:分配并初始化为0的内存
    double* calloc_arr = (double*)calloc(3, sizeof(double));
    if (calloc_arr == NULL) {
        perror("calloc failed");
        free(malloc_arr);  // 释放已分配内存
        return 1;
    }
    printf("=== calloc示例 ===\n");
    for (int i = 0; i < 3; i++) {
        printf("%.1f ", calloc_arr[i]);  // 输出:0.0 0.0 0.0(自动初始化)
    }
    printf("\n\n");


    // 3 realloc:调整已分配内存大小(将malloc_arr从5个int扩容到8个)
    int* realloc_arr = (int*)realloc(malloc_arr, 8 * sizeof(int));
    if (realloc_arr == NULL) {
        perror("realloc failed");
        free(malloc_arr);  // 扩容失败时释放原内存
        free(calloc_arr);
        return 1;
    }
    // 原内存地址可能改变,更新指针
    malloc_arr = realloc_arr;
    printf("=== realloc示例 ===\n");
    // 初始化新增的3个元素
    for (int i = 5; i < 8; i++) {
        malloc_arr[i] = i * 10;
    }
    for (int i = 0; i < 8; i++) {
        printf("%d ", malloc_arr[i]);  // 输出:0 10 20 30 40 50 60 70
    }
    printf("\n\n");


    // 4 free:释放所有动态分配的内存
    printf("=== free示例 ===\n");
    free(malloc_arr);
    malloc_arr = NULL;  // 释放后置空,避免野指针
    free(calloc_arr);
    calloc_arr = NULL;
    printf("所有动态内存已释放\n");

    return 0;
}
    

三、陷阱

C 语言内存管理的难点在于如何避免错误使用

1. 内存泄漏(最常见,隐蔽性强)

堆内存分配后(malloc/calloc/realloc),未用 free 释放,且丢失了指向该内存的所有指针,导致系统无法回收,内存被永久占用。

void func() {
    int* p = (int*)malloc(10 * sizeof(int));
    // 业务逻辑...
    return; // 错误!p指向的堆内存未释放,函数返回后p销毁,内存泄漏
}

后果:短期程序影响不大;长期运行的程序(如服务器)会持续占用内存,最终耗尽堆内存,导致程序崩溃。

规避方法

遵循 “谁分配,谁释放” 原则:分配内存的模块 / 函数,负责释放。

2. 野指针

指向 “无效内存” 的指针:可能是 free 后未置 NULL 的指针,或未初始化的指针(随机指向某块内存)。

int* p = (int*)malloc(sizeof(int));
free(p); // 释放内存,但p仍指向原地址
*p = 10; // 错误!操作无效内存,可能崩溃或篡改其他数据
int* p; // 未初始化,p的值是随机的(野指针)
*p = 20; // 错误!随机操作内存,后果不可控
规避方法

free 后立即将指针置为 NULL(如 free(p); p = NULL;)。

指针声明时,若暂时不分配内存,先置为 NULL(如 int* p = NULL;)。

使用指针前,先判断是否为 NULL(如 if (p != NULL) { *p = ...; })。

3. 内存越界访问

访问的内存超出了分配的堆内存范围。

int* arr = (int*)malloc(10 * sizeof(int)); // 0~9索引有效
for (int i = 0; i <= 10; i++) {
    arr[i] = i; // 错误!i=10时越界,篡改相邻内存数据
}

后果:可能暂时不崩溃,但会 “污染” 相邻的堆内存(篡改其他变量数据),导致程序逻辑错误;严重时直接触发段错误。

规避方法

严格控制数组 / 内存的访问索引。

4. 重复释放

同一堆内存被 free 多次。

int* p = (int*)malloc(sizeof(int));
free(p); // 第一次释放
// ... 中间逻辑未置p=NULL
free(p); // 错误!第二次释放,程序崩溃
规避方法

free 后立即将指针置为 NULL,后续再 free(p) 时,free(NULL) 是安全的。

5. 释放非堆内存

用 free 释放栈内存、全局变量、常量内存等非堆内存。

// 场景1:释放栈内存(局部变量)
int a = 5;
free(&a); // 错误!

// 场景2:释放全局变量(全局区)
int g_val = 10;
free(&g_val); // 错误!

// 场景3:释放常量内存(常量区)
char* str = "hello"; // "hello"在常量区,str是栈上的指针
free(str); // 错误!

6. 未初始化内存的使用

使用 malloc 分配的内存(未初始化,垃圾值),未手动赋值就直接读取。

int* p = (int*)malloc(sizeof(int));
printf("%d\n", *p); // 错误!读取未初始化的内存,输出随机值

后果:程序逻辑错误(依赖随机值),结果不可控。

规避方法

分配后立即手动赋值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值