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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第1张图片

 

 

 

 

本章重点

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第2张图片

什么是动态内存

  • 动态内存是指在程序运行时,根据需要动态分配的内存空间。
#include 
#include 
#include 
#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;
}

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第3张图片

 

为什么要有动态内存

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第4张图片

栈、堆和静态区

C程序动态地址空间分布

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第5张图片

#include 
#include 
#include 

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中验证的

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第6张图片

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

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

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);

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第7张图片

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

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第8张图片

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第9张图片

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第10张图片

常见的内存错误及对策

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

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第11张图片


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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第12张图片


3、函数的入口检测

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第13张图片


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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第14张图片


THREE:内存分配成功,但并未初始化【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第15张图片

FOUR:内存越界

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第16张图片

FIVE:内存泄漏

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

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第17张图片

内存释放的本质是什么?

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第18张图片

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第19张图片

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

#include 
#include 
#include 
#include 
#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;
}

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第20张图片

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第21张图片

 

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

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第22张图片

【内存管理大猫腻:从“越界”到“内存泄漏”应有尽有】_第23张图片

 

 

你可能感兴趣的:(深度理解C语言,c语言)