【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】

news/2024/11/25 17:43:56/

797de056b3c241e7ae2b83df9cd61759.jpeg

 

 

 

 

本章重点

  • 什么是动态内存
  • 为什么要有动态内存
  • 什么是野指针
  • 对应到C空间布局, malloc 在哪里申请空间
  • 常见的内存错误和对策
  • C中动态内存“管理”体现在哪

11151d2dc87b47e7ba85044a081988c9.png

什么是动态内存

  • 动态内存是指在程序运行时,根据需要动态分配的内存空间。
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#define N 10
int main()
{int* p = (int*)malloc(sizeof(int) * N); //动态开辟空间40个字节的空间if (NULL == p) { //判断是否成功申请空间perror("malloc\n");//perror函数是标准库函数,其作用是输出上一个系统调用(例如malloc)的错误信息exit(0);//exit函数结束程序运行}for (int i = 0; i < N; i++) {p[i] = i;//赋值0,1,2,3,4,5,6,7,8,9}for (int i = 0; i < N; i++) {printf("%d ", i);//打印}printf("\n");free(p); //开辟完之后,要程序员自主释放p = NULL;return 0;
}

6e748ed4a0e449aa9ffa90b966aad400.png

 

为什么要有动态内存

  • 通常,在编写程序时,我们可以使用静态内存(静态分配内存),也就是在程序编译阶段就确定了内存空间的大小和位置,但是静态内存存在一定的限制和局限性,比如无法在运行时改变分配的内存大小等。
  • 而动态内存则具有更大的灵活性和可变性,可以在程序运行时动态地分配、释放内存,以适应程序的实际需求。

38fd44bc350745e4b2793a5b3f1a3132.png

栈、堆和静态区

C程序动态地址空间分布

ff864c714be04bb095832edbcc38fce5.png

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>int g_val2;//未初始化变量
int g_val1 = 1;//已初始化变量int main()
{printf("code addr: %p\n", main);//代码区const char* str = "hello world";//字符常量区printf("static readonly: %p\n", str);//这里输出的是字符串的首地址printf("init(初始化) global val: %p\n", &g_val1);//已初始化变量区printf("uninit(未初始化) global val: %p\n", &g_val2);//未初始化变量区int* p = (int*)malloc(sizeof(int) * 10);printf("heap(堆) : %p\n", p);//这里输出的是开辟40个字节空间的首地址//输出两个局部指针变量的地址printf("stack(栈) addr: %p\n", &str);printf("stack(栈) addr: %p\n", &p);
}

由于win中有地址随机化保护,我们这里的结果是再Linux中验证的

48de635d6c294556bd459703c8d963f8.png

同时我们也可以发现栈是向下增长的,后定义的变量后入栈,其相应的地址也较小。

再来验证一下堆区的特点。

char* p1 = (char*)malloc(sizeof(char) * 10); 
printf("heap(堆) : %p\n", p1);char* p2 = (char*)malloc(sizeof(char) * 10); 
printf("heap(堆) : %p\n", p2);char* p3 = (char*)malloc(sizeof(char) * 10); 
printf("heap(堆) : %p\n", p3);printf("stack(栈) addr: %p\n", &p1);
printf("stack(栈) addr: %p\n", &p2);
printf("stack(栈) addr: %p\n", &p3);

15a81b2515fd45cf9531bcc1a65ff145.png

堆是符合向上增长的,先开辟的变量,其相应的地址较小。

在C语言中,为何一个临时变量,使用static修饰之后,它的生命周期变成全局的了?

bca1a935851b48cf84cc7a4e952f2013.png

        当在一个函数中将一个局部变量添加了static关键字时,编译器会将其转化为对应的静态变量,这使得该变量的存储位置从栈(stack)转变为全局数据区(data segment)中的静态变量存储区,使得该变量在函数调用结束后不会被自动销毁。

82c24f9ce6b044498159bbdd9dc9c447.png

6537846313224309b5170f6c3efd5599.png

常见的内存错误及对策

ONE:指针没有指向一块合法的内存

1、结构体成员指针未初始化

9b7a2b338cf9420e98d39368acc948f0.png


2、没有为结构体指针分配足够的内存

87bfa12551864a0cb8e1d1291c36c53d.png


3、函数的入口检测

441dc99ed85f4555b6ea14e9cd2be9ba.png


TWO:为指针分配的内存太小

b3634ec44df1432ab4df6807704d272f.png


THREE:内存分配成功,但并未初始化d113ff07962249c8803cdf7e79cc06e8.png

FOUR:内存越界

5045a560a766474f8fedd1ce657751c4.png

FIVE:内存泄漏

  • 申请内存是在哪里申请?- 堆
  • 申请内存是向谁要空间?- 操作系统
  • 如何申请内存? - malloc函数
  • 申请内存是否需要释放?如何释放? - 需要,free函数
  • 申请内存不释放会有什么问题? - 内存泄露

程序退出的时候,曾经的内存泄漏问题还存在吗?

b195e60535894512848e1ce80b00186c.png

内存释放的本质是什么?

4e49cd27ba4e438a8b16dd9791463a07.png

观察free函数的参数,free函数只知道释放空间的起始地址,貌似并不知道要释放多大空间,那如何正确释放呢?

e3d836705a0e43a2ba4425c3f186cb1a.png

我们这里写一个单链表代码来演示动态开辟内存

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <windows.h>
#define N 10
typedef struct _Node {int data;struct _Node* next;
}node_t;static node_t* AllocNode(int x)
{node_t* n = (node_t*)malloc(sizeof(node_t));if (NULL == n) {exit(EXIT_FAILURE);}n->data = x;n->next = NULL;return n;
}void InsertList(node_t* head, int x)
{node_t* end = head;while (end->next) {end = end->next;
}
node_t* n = AllocNode(x);
end->next = n;
}void ShowList(node_t* head)
{node_t* p = head->next;while (p) {printf("%d ", p->data);p = p->next;}printf("\n");
}void DeleteList(node_t* head)
{node_t* n = head->next;if (n != NULL) {head->next = n->next;free(n);}
}int main()
{node_t* head = AllocNode(0); //方便操作,使用带头结点的单链表printf("插入演示...\n");Sleep(10000);for (int i = 1; i <= N; i++) {InsertList(head, i); //插入一个节点,尾插方案ShowList(head); //显示整张链表Sleep(1000);}printf("删除演示...\n");for (int i = 1; i <= N; i++) {DeleteList(head); //删除一个节点,头删方案ShowList(head); //显示整张链表Sleep(1000);}free(head); //释放头结点head = NULL;return 0;
}

efdd4d630dfa475b9b223cb8ad1fc218.gif

SIX:内存已经被释放了,但是继续通过指针来试用

2600e6a166944b70b8b47cb3d2a9350e.png

 

C中动态内存“管理”体现在哪

c641d7734e3741f6a2a2ce05371c9a33.png

cfce00bdbeef49ac91199777cb98a447.png

 

 


http://www.ppmy.cn/news/231345.html

相关文章

微信机器人

通过自己编译rom实现的微信机器人,rom自带v2ray透明代理, 没有任何三方框架, 安全系数极高, 稳定性好, 不会被封号 支持的功能 发送消息(文字,语音,红包,图片, 视频,小程序, 图文连接…)消息拦截发送朋友圈视频号相关功能修改个人资料群管理相关加好友相关 目前市面上搭建微…

【华为OD机试】斗地主之顺子【2023 B卷|100分】

【华为OD机试】-真题 !!点这里!! 【华为OD机试】真题考点分类 !!点这里 !! 题目描述 在斗地主扑克牌游戏中, 扑克牌由小到大的顺序为:3,4,5,6,7,8,9,10,J,Q,K,A,2, 玩家可以出的扑克牌阵型有:单张、对子、顺子、飞机、炸弹等。 其中顺子的出牌规则为:由至少5张由小到…

戴森Cyclone V10无绳吸尘器上市 凭借全新马达再度成为爆款

戴森无绳吸尘器的备受追捧&#xff0c;更像是人们对于工匠精神的致敬。 从工业社会到信息社会再到智能社会&#xff0c;企业的商业模式和我们的生活方式都在不断迭代。但总有一些是没有变的&#xff0c;对于企业来说&#xff0c;很多时候&#xff0c;正是选择了坚守&#xff0c…

惠普 暗影精灵5(i7-9750H+gtx1660ti),ubuntu16.04 无法安装显卡驱动

在ubuntu16.04上安装GTX1660ti显卡时&#xff0c;一定要将安全模式禁用&#xff0c;使用传统模式&#xff0c;不然显卡驱动无法安装

计算机boot进入u盘启动,暗影精灵5怎么设置u盘启动 暗影精灵5设置u盘启动方法...

最近有位电脑用户想要使用u盘启动盘重装系统&#xff0c;但是却不知道应该怎么使用bios设置u盘启动&#xff0c;为此非常苦恼&#xff0c;那么惠普暗影精灵5 omen 15-dc1068tx笔记本怎么使用bios设置u盘启动呢?下面为大家介绍惠普暗影精灵5 omen 15-dc1068tx笔记本使用bios设置…

暗影精灵5怎么调风扇转速_网传惠普暗影精灵5散热性很差 在实际测评中的表现怎么样的...

惠普暗影精灵系列的游戏本实际表现力其实都非常不错&#xff0c;但是在很多人的观念里&#xff0c;惠普的电脑散热性都不太好&#xff0c;之前也有盛传说最新惠普暗影精灵5散热表现不佳&#xff0c;那么事实究竟如何&#xff0c;我们来看看实测。 散热能力及温度表现 影响玩家游…

入手评测 惠普暗影精灵 7怎么样

普暗夜精灵7将配备16.1英寸2K分辨率电竞屏&#xff0c;超窄边框&#xff0c;尺寸比例为16:10&#xff0c;刷新率高达165Hz&#xff0c;还支持100%sRGB色域。 惠普暗影精灵 7怎么样这些点很重要看过你就懂了http://www.adiannao.cn/dy 性能方面&#xff0c;暗夜精灵7搭载Intel…

惠普暗影精灵 9 旗舰版 评测

设计方面&#xff0c;暗影精灵 9 旗舰版采用金属机架机箱&#xff0c;搭配全侧透钢化玻璃设计与钢化玻璃前面板&#xff0c;结合 ARGB 灯效&#xff0c;呈现出更好的视觉效果。 配置方面&#xff0c;暗影精灵 9 旗舰版搭载 i9-13900K 处理器&#xff0c;配备第二代 Omen Cyro …