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;

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

六、结束语

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

相关推荐
福大大架构师每日一题6 分钟前
文心一言 VS 讯飞星火 VS chatgpt (396)-- 算法导论25.2 1题
算法·文心一言
EterNity_TiMe_21 分钟前
【论文复现】(CLIP)文本也能和图像配对
python·学习·算法·性能优化·数据分析·clip
陌小呆^O^26 分钟前
Cmakelist.txt之win-c-udp-client
c语言·开发语言·udp
机器学习之心31 分钟前
一区北方苍鹰算法优化+创新改进Transformer!NGO-Transformer-LSTM多变量回归预测
算法·lstm·transformer·北方苍鹰算法优化·多变量回归预测·ngo-transformer
yyt_cdeyyds42 分钟前
FIFO和LRU算法实现操作系统中主存管理
算法
alphaTao1 小时前
LeetCode 每日一题 2024/11/18-2024/11/24
算法·leetcode
kitesxian1 小时前
Leetcode448. 找到所有数组中消失的数字(HOT100)+Leetcode139. 单词拆分(HOT100)
数据结构·算法·leetcode
VertexGeek2 小时前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
石小石Orz2 小时前
Three.js + AI:AI 算法生成 3D 萤火虫飞舞效果~
javascript·人工智能·算法
jiao_mrswang3 小时前
leetcode-18-四数之和
算法·leetcode·职场和发展