C语言之动态内存管理

       在C语言中我们在栈上开辟的空间是固定的,一旦确定好大小就不能随意改变,就想你创建了

int i = 10;
int arr[10] = {0};
int i 一旦确定下来就是四个字节,arr一旦确定好大小在重新运行时也是不能改变的。

为此C语言引入了动态内存空间开辟,这是给程序员自主开辟好空间,并且这个空间是在上开辟的。

动态内存函数

头文件是stdlib.h

malloc

malloc 可以向堆上申请 size 个字节的空间大小,如果开辟成功就会返回相应的指针,并且对于开辟的空间是不会进行初始化赋值,里面都是随机值!
如果开辟空间失败就会返回NULL!
如果开辟0个字节的空间,返回值取决于特定的库实现,可能为NULL也可能不是.
要注意了返回的指针类型是 void*,所以我们需要强制类型转换为我们需要的。

由于可能会返回NULL,一旦被解引用就是野指针的非法访问。
所以我们要对返回的指针做检查

下面我们来实践一下:

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

int main()
{
	int* p = (int*)malloc(sizeof(int) * 5);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	// 使用

	return 0;
}

free

介绍完malloc 函数后,我们来介绍一下如何释放空间,由于堆的空间是有限的,如果一直开辟不去释放空间,那就会一直占用堆上的空间,导致运行时发生内存泄漏。所以我们需要学会释放空间!

free只能对malloc,calloc和realloc 这些动态内存开辟函数开辟的空间进行释放空间,如果释放的不是这些空间那这个行为就是未定义的行为(也就是错误的)
如果你传过去的指针为NULL的话,那这个函数不会做任何事情
free函数不会改变你传过去的指针的值,所以在释放空件后,可以将指针置为NULL,避免野指针的使用

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

int main()
{
	int* p = (int*)malloc(sizeof(int) * 5);
	if (p == NULL)
	{
		perror("malloc");
		return 1;
	}
	// 使用

	free(p);
	p = NULL;

	return 0;
}

calloc

这个函数也是申请 num 个 size 大小的字节空间,唯一和malloc不同的是,calloc会将空间全部初始化为0

realloc

这个函数就是扩大空间的,如果你觉得malloc或者calloc开辟的空间小了,就传入一个指针,输入你想要扩大的字节数,就可以了。

如果你传的是空指针,那realloc就会像malloc一样,为你开辟一块空间并返回新空间的地址。
如果开辟失败就会返回空指针

这里的开辟方式分两种:

第一种是就地扩容,返回原来的指针
第二种是异地扩容,将原先的空间的值全部拷贝到新空间,并释放就空间,最后返回新空间的地址

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

int main()
{
	int* p = (int*)calloc(5, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
		return 1;
	}
	// 使用

	int* ptr = (int*)realloc(p, 50);
	if (ptr == NULL)
	{
		perror("realloc");
		return 1;
	}

	p = ptr;
	//使用

	free(p);
	p = NULL;

	return 0;
}

练习

题目1

void test()
{
	int* p = (int*)malloc(INT_MAX / 4);
	*p = 20;
	free(p);
}

如果p的值是NULL,就是对空指针解引用,发生错误

题目2

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;
	}
	free(p);
}

当i是10的时候就会越界访问

题目3

void test()
{
	int a = 10;
	int* p = &a;
	free(p);
}

对非动态内存开辟的空间不能使用free

题目4

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);
}

由于p已经不是原先的起始位置,释放的空间不彻底,导致内存泄漏

题目5

void test()
{
	int* p = (int*)malloc(100);
	free(p);
	free(p);
}

对一块动态开辟的内存多次释放是错误的,在第一次释放的时候,空间以及还给操作系统了,free是无权访问操作系统的空间,所以在使用free后,我们可以将指针置空,避免多次释放。

题目6

void test()
{
	int* p = (int*)malloc(100);
	if (NULL != p)
	{
		*p = 20;
	}
}
int main()
{
	test();
	while (1);
}

这个就是忘记对开辟的内存进行释放,导致内存泄漏。

笔试题

题目1

void GetMemory(char* p)
{
	p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

首先str还是NULL,因为形参的改变不会影响实参,如果要改变str需要传入str的地址,对NULL解引用程序发生崩溃,并且对于开辟的内存空间要记得释放

题目2

char* GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char* str = NULL;
	str = GetMemory();
	printf(str);
}

临时变量p一旦出了GetMemory 函数就被销毁了,str非法访问内存空间。

题目3

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

忘记释放内存,发生内存泄漏

题目4

void Test(void)
{
	char* str = (char*)malloc(100);
	strcpy(str, "hello");
	free(str);
	if (str != NULL)
	{
		strcpy(str, "world");
		printf(str);
	}
}

free已经释放了str所指向的内存空间,但是free不会将str置空,后面的代码段内存空间进行了非法访问!

柔性数组

       柔性数组就是数组的大小是未定义的,这种一般出现在结构体上,如果不知道结构体的大小的话,可以看我上上篇的文章《结构体详解》。

柔性数组的创建:

struct S
{
	int i;
	char arr[];
};

特点

柔性数组前面必须要有一个成员
使用sizeof去计算这个结构体的时候,是没有包含柔性数组的大小(因为他还没大小)
包含柔性数组成员的结构⽤malloc ()函数进行内存的动态分配,并且分配的内存应该⼤于结构的大小,以适应柔性数组的预期大小。

使用

当我们要使用结构体是,就需要对这个柔性数组开辟空间:

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

struct S
{
	int i;
	char arr[];
};

int main()
{
	struct S* s1;
	s1 = (struct S*)malloc(sizeof(int) + 10 * sizeof(char));
	return 0;
}

这样子我们就获得了10个字节的arr数组的空间了。

优点

我们在学习链表的时候会定义下面的结构体:

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

struct S
{
	int data;
	struct S* next;
};

int main()
{
	struct S* p;
	p = (struct S*)malloc(sizeof(struct S));
	if (p == NULL)
	{
		perror("malloc p");
		return 1;
	}

	p->next = (struct S*)malloc(sizeof(struct S));
	if (p->next == NULL)
	{
		perror("malloc next");
		return 1;
	}
	//使用

	free(p->next);
	p->next = NULL;
	free(p);
	p = NULL;
	return 0;
}

当我们使用这个结构体的时候需要对内存进行两次释放,如果使用柔性数组就可以一次释放。
柔性数组的使用能让内存空间更加连续,减少内存碎片化。

版权声明:本文为博主作者:熵减玩家原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/liwuqianhzc/article/details/137019139

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2024年4月10日
下一篇 2024年4月10日

相关推荐