收集飞花令碎片——【C语言】动态内存管理&&数据存储的内存结构

「C++ 40 周年」主题征文大赛(有机会与C++之父现场交流!) 10w+人浏览 457人参与

在这里插入图片描述

在这里插入图片描述

点击下面查看作者专栏
🔥🔥C语言专栏🔥🔥
🌊🌊编程百度🌊🌊
🌠🌠如何获取自己的代码仓库🌠🌠

一、动态内存管理

C语言的动态内存管理。这可是C语言的“精髓”之一,也是新手(甚至老手)的“噩梦”之源
它就像给了你一个超能力:你可以在程序运行时,向上帝(操作系统)“借”一块内存,用完了再“还”回去
在这里插入图片描述

🧰 1)为什么需要动态内存管理?

我们先看看“不动态”的内存是什么样的:

  • 静态/全局内存 (Static/Global):
int global_var = 10;

这些变量在程序一开始运行时就被分配了,直到程序结束才释放。它们“长命百岁”,但不够灵活。



  • 自动内存 (Automatic / Stack)
void my_func() { int local_var = 5; }

函数里的局部变量,放在“栈”上。它们的好处是“自动”:函数一调用,它们就出生;函数一返回,它们就去世并自动释放



  • 动态内存(Heap / 堆)

从一块叫堆(Heap) 的巨大内存池里按需分配的



🧰 2)核心工具箱:四个关键函数

🌊🌊所有动态内存管理都围绕着 <stdlib.h> 头文件里的四个函数🌊🌊

1)malloc - 内存分配

摆渡游船:点击前往malloc函数官网

  • 原型: void* malloc(size_t size);

  • 作用: 你告诉它你需要多少字节(size),它会去堆里找一块这么大的、连续的空闲内存

  • 返回值:

    • 成功: 返回一个 void* 类型的指针,指向这块内存的起始地址。void* 是“通用指针”,你需要将它强制类型转换为你需要的类型(比如 int*, char* 等)。
    • 失败: 如果系统没钱(内存)了,它会返回NULL
// 申请一块足够存放 100 个整数的内存
int* my_array = (int*)malloc(100 * sizeof(int)); 

// 🚨 **极其重要:永远检查返回值!**
if (my_array == NULL) {
    // 内存分配失败了!
    printf("天呐!内存不足!\n");
    // 此时应该做一些错误处理,比如退出程序
    return 1; 
}

// 现在你可以像使用普通数组一样使用它
my_array[0] = 10;
my_array[99] = 123;


2)calloc - 连续分配

摆渡游船:点击前往calloc函数官网

  • 原型: void* calloc(size_t num_elements, size_t element_size);

  • 作用: 你告诉它你需要多少个元素(num_elements)以及每个元素多大(element_size)。

  • malloc 的区别:

    • 参数更直观(比如 calloc(100, sizeof(int)))。
    • 它会自动把分配的内存全部初始化为0 malloc 不会,malloc 给你的内存里可能是任何“垃圾数据”


3)realloc - 重新分配

摆渡游船:点击前往realloc函数官网

  • 原型void* realloc(void* ptr, size_t new_size);

  • 作用: 调整 ptr 指向的那块内存的大小为 new_size

注意:
1. 如果新大小new_size小于 原大小,它会“截断”多余的内存
2. 如果new_size大于 原大小,它会尝试在原地扩容
3. 如果原地空间不够(这很常见),realloc 会在堆上另找一个足够大的新地方,把旧内存里的数据复制到新地方,然后释放旧的内存,最后返回新地址。

正因如此,你必须用一个新指针接收 realloc 的返回值!

// 之前申请了 100 个整数
int* my_array = (int*)malloc(100 * sizeof(int));
// ... 使用 ...

// 发现不够用,需要 200 个
int* bigger_array = (int*)realloc(my_array, 200 * sizeof(int));

if (bigger_array == NULL) {
    // realloc 失败时,原指针 my_array 仍然有效
    // 可以继续使用原来的内存块
} else {
    // 成功了,现在 my_array 可能已经失效了
    // 应该用 bigger_array 代替
    my_array = bigger_array; 			//realloc成功时,用新指针替换旧指针
}


4)free - 释放

摆渡游船:点击前往free函数官网

  • 原型: void free(void* ptr);

  • 作用: 告诉操作系统,ptr 指向的这块动态内存你不用了,可以回收了

  • 规则:

    • 你只能 free 通过 malloc, calloc, realloc 得到的指针。
    • free(NULL) 是安全的,什么也不做
int* my_array = (int*)malloc(100 * sizeof(int));
// ... 尽情使用 ...

// 好了,用完了,必须释放
free(my_array);


🧰 3)动态内存的注意事项

3.1)动态开辟内存忘记释放(内存泄漏)

  • “只借不还”
    1.你 malloc 了一块内存,但用完后忘记 free。这块内存就“丢失”了。你的程序还在运行,但它既不能使用这块内存(因为你把指向它的指针弄丢了),系统也不能把它分给别人。
    2.如果你的程序(比如服务器)长时间运行,并且不断地泄漏内存,它最终会耗尽系统所有内存,然后崩溃。

在这里插入图片描述



3.2)野指针

  • “你退了房,但还拿着钥匙。”
    free(ptr) 之后,ptr 指针本身的值(那个地址)并没有变,但它指向的内存已经不属于你了。

  • 问题:
    如果你稍后不小心又通过ptr去读写那块内存,你就是在“非法闯入”。你可能会读到垃圾数据,或者(更糟的)你可能会破坏掉其他人(程序里的另一部分)正在使用的数据。

  • 最佳实践:
    free 之后,立即将指针设为 NULL

free(my_array);
my_array = NULL; // 好习惯!

// 之后如果不小心访问
if (my_array != NULL) {
    my_array[0] = 5; // 这段代码就不会执行了
}


3.3)重复释放

  • “同一张账单付了两次钱。”
    free(ptr) 之后,过了一会儿又(不小心) free(ptr) 了一次。这会严重破坏堆的内部管理结构,几乎肯定会导致程序崩溃。

在这里插入图片描述

(这就是为什么 free 后设为 NULL 是个好习惯,因为 free(NULL) 是安全的)。



3.4)访问越界

  • “租了10平米的仓库,却往里塞了11平米的东西。”
    malloc(10 * sizeof(int)),只申请了10个整数的空间,但你却试图访问 my_array[10](这是第11个元素)

在这里插入图片描述



二、数据存储的内存结构

🧠 计算机内存中的“数据存储”是什么?
当你写下:

int x = 10;

这句代码看起来只是一个整数,但程序运行时,它必须存在某个地方才能被 CPU 操作。
这个地方就是 内存(Memory)

  • C 语言的数据存储模型主要描述:
    - 程序中不同数据存放在 哪一块内存区域
    - 这些数据 何时被创建,何时被销毁
    - 谁能访问它们(作用域)

🧮 1)内存区的详细特征


1️⃣ 栈(Stack)
  • 自动分配释放函数调用时开辟,返回时销毁

  • 存放 局部变量函数参数返回地址

  • 空间较小但速度极快(顺序分配)

  • 典型错误:返回栈变量的地址!

int *func() {
    int x = 100;
    return &x; // ❌ x 是栈变量,函数返回后内存已被回收
}

2️⃣ 堆(Heap)
  • 手动分配与释放: malloc / free

  • 生命周期: 程序员决定

  • 灵活但易出错(内存泄漏、悬空指针)

int *p = malloc(sizeof(int));
*p = 42;
free(p);     // ✅ 释放
p = NULL;    // ✅ 置空避免野指针

3️⃣ 全局区(静态区 / Data Segment)
  • 已初始化区:int a = 10;

  • 未初始化区(BSS 段):int b; (默认值为0)

  • 它们在程序启动时由系统分配,程序结束时系统释放


4️⃣ 常量区(Constant Segment)
  • 存放字面常量,如字符串常量 hello

  • 通常只读,不允许修改

char *s = "hello";
s[0] = 'H'; // ❌ 运行时错误(段错误)
  • 注意事项
char s[] = "hello"; // ✅ 拷贝到栈区,可修改
s[0] = 'H';

🧮 2)图片详解

在这里插入图片描述



三、总结

动态内存管理是C语言强大灵活性和性能的基石。它允许你构建真正复杂、高效的数据结构(如链表、树、哈希表)
但是也会导致各种安全漏洞

如果你觉得这篇文章对你有帮助

请给一个三连吧在这里插入图片描述

评论 16
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值