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

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

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值