C语言:动态内存管理

动态内存管理

一、动态分配内存的必要性

普通内存分配

  1. 空间开辟大小是固定的。
  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

动态内存分配

1、空间大小可以更改。

2、空间大小可以在程序执行时再分配。

二、动态内存分配函数

(一)malloc

/ 复制代码
void* malloc (size_t size);

使用规则:

传入字节数,申请成功返回申请空间的首地址,申请失败返回空地址,因此申请空间需要进行检查。

/ 复制代码
int main() {
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL) {             // 进行检查会更安全
		perror("malloc");
		return 1;
	}
	printf("申请成功");
	free(p);                      //后面会有函数用法
	p = NULL;
	return 0;
}

(二)calloc

/ 复制代码
void* calloc (size_t num, size_t size);

使用规则:

传入申请类型个数以及一个类型的字节数,申请成功返回申请空间的首地址,申请失败返回空地址,申请空间需要进行检查。

与 malloc 不同的是calloc 会初始化每个字节为0;

/ 复制代码
int main() {
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL) {
		perror("calloc");
		return 1;
	}
	printf("申请成功");
	free(p);
	p = NULL;
	return 0;
}

(三)realloc

/ 复制代码
void* realloc (void* ptr, size_t size);

使用规则:

如果原来的内存块后面的空间足够开辟空间,那么直接在内存块后面扩展内存。否则 realloc 会另外寻找一片内存进行开辟,返回找到的地址,如果实在找不到这么大的内存,就会返回NULL。

/ 复制代码
int main() {
	int* arr = (int*)malloc(sizeof(int) * 10);
	if (arr == NULL) {
		perror("malloc\n");
		return 1;
	}
	int* p = (int*)realloc(arr, sizeof(int) * 20);
	if (p == NULL) {
		perror("realloc\n");
		return 1;
	}
	arr = p;
	free(arr);
	arr = NULL;
	return 0;
}

如果 ptr 传入NULL,他的效果就相当于 malloc。

/ 复制代码
int main() {
	int* p = (int*)realloc(NULL, sizeof(int) * 20);
	if (p == NULL) {
		perror("realloc\n");
	}
	free(p);
	p = NULL;
	return 0;
}

(四)free

/ 复制代码
void free (void* ptr);

上面的函数 malloc calloc realloc 分配了空间之后,也需要进行释放。如果不释放,可能导致内存泄漏。如果不置空,可能导致非法访问。

如果传入NULL,就不进行操作。

三、常见的错误

(一)对空指针进行解引用操作

如果分配空间时,内存不足以开辟返回NULL;不进行检查就解引用可能导致程序崩溃。

(二)对动态分配空间越界访问

顾名思义,就是不可以越界访问。

(三)free释放动态分配空间的一部分

如下,p++ 改变了 p 指向的内存,不再是首地址,就好像是释放一部分空间,这种情况程序会崩溃。

/ 复制代码
int main() {
	int* p = (int*)malloc(10*sizeof(int));
	if (p == NULL) {
		perror("malloc");
		return 1;
	}
	for (int i = 0; i < 5; i++) {
		*(p++) = i;
	}
	free(p);
	p = NULL;
	return 0;
}

(四)动态开辟内存忘记释放

下面代码中,如果开辟成功,malloc 开辟的内存没有释放,但是 p 的地址我们在函数外面就再也找不到了,这就是内存泄漏。

/ 复制代码
void test()
{
	int flag = 1;
	int*p = (int*)malloc(100);
	if (p == NULL)
	{
		return;
	}
	if (flag)
		return;
	free(p);
	p = NULL;
}

int main()
{
	test();
	return 0;
}

四、柔性数组

(一)柔性数组的特点

1、结构中的柔性数组成员前面必须至少一个其他成员。

2、sizeof 返回的这种结构大小不包括柔性数组的内存。

3、包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

/ 复制代码
struct S {
	int n; 
	int arr[];
};

int main() {
	struct S* p = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 10);
	if (p == NULL) {
		perror("malloc");
	}
	struct S* temp = (struct S*)realloc(p, sizeof(struct S) + sizeof(int) * 20);
	if (temp == NULL) {
		perror("realloc");
	}
	p = temp;
	return 0;
}

(二)柔性数组的优势

相比于上面的代码,肯定也会有疑惑,为什么不在结果体中设置一个指针变量进行动态内存开辟空间。

那我们就进行探究

/ 复制代码
struct S
{
	int n;
	int* arr;
};

int main()
{
	struct S* ps = (struct S*)malloc(sizeof(struct S));
	if (ps == NULL)
	{
		perror("malloc");
		return 1;
	}
	int*tmp = (int*)malloc(20*sizeof(int));
	if (tmp != NULL)
	{
		ps->arr = tmp;
	}
	else
	{
		return 1;
	}
	tmp = (int*)realloc(ps->arr, 40*sizeof(int));
	if (tmp != NULL)
	{
		ps->arr = tmp;
	}
	else
	{
		perror("realloc");
		return 1;
	}
	free(ps->arr);
	ps->arr = NULL;
	free(ps);
	ps = NULL;
	return 0;
}

我们发现柔性数组的优势:
1、提高访问速度:

连续的内存有益于提高访问速度,也有益于减少内存碎片
2、方便内存释放:

如果用动态内存开辟的方式,我们要释放两次才能释放掉内存空间,因此柔性数组更优秀。

五、有趣的题目

1、求下面代码的输出结果

/ 复制代码
void GetMemory(char* p)
{
    p = (char*)malloc(100);
}

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

这个代码有两个问题:

1、 传入函数中的为NULL,动态分配空间GetMemory 函数结束后就无法访问到了,造成内存泄漏。

2、strcpy 函数中,如果对空指针进行解引用操作,导致程序崩溃。

可查看strcpy详解

2、求下面代码的输出结果

/ 复制代码
int main() {
	char* p = (char*)malloc(sizeof(int) * 10);
	strcpy(p, "hello");
	free(p);         //这里没有及时的将 p 置空造成了野指针
	if (p) {         //发生非法访问
		strcpy(p, "what can i see!");
	}
	return 0;

根据代码中分析,这段代码在运行时也会导致程序崩溃。

六、结束语

小伙伴看了这篇文章感觉如何呢,如果能有收获是值得高兴的,如果觉得有帮助的化,别忘记点赞支持支持哦!

相关推荐
_feivirus_37 分钟前
神经网络_使用TensorFlow预测气温
人工智能·神经网络·算法·tensorflow·预测气温
大柏怎么被偷了1 小时前
【C++算法】位运算
开发语言·c++·算法
程序猿方梓燚1 小时前
C/C++实现植物大战僵尸(PVZ)(打地鼠版)
c语言·开发语言·c++·算法·游戏
CPP_ZhouXuyang1 小时前
C语言——模拟实现strcpy
c语言·开发语言·数据结构·算法·程序员创富
闻缺陷则喜何志丹1 小时前
【C++前后缀分解 动态规划】2100. 适合野炊的日子|1702
c++·算法·动态规划·力扣·前后缀分解·日子·适合
逝去的秋风1 小时前
【代码随想录训练营第42期 Day57打卡 - 图论Part7 - Prim算法与Kruskal算法
算法·图论·prim算法
QXH2000001 小时前
数据结构—双向链表
c语言·数据结构·算法·链表
旺小仔.2 小时前
【数据结构篇】~排序(1)之插入排序
c语言·数据结构·算法·链表·性能优化·排序算法
绎岚科技2 小时前
深度学习自编码器 - 随机编码器和解码器篇
人工智能·深度学习·算法·机器学习
jingling5552 小时前
后端开发刷题 | 数字字符串转化成IP地址
java·开发语言·javascript·算法