C语言【动态内存管理 前篇】

这篇具有很好参考价值的文章主要介绍了C语言【动态内存管理 前篇】。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🫅 1. 为什么存在动态内存管理

C语言中的数据结构通常是固定大小的。例如,一旦程序完成编译,数组元素的数量就是固定的。
说到这里,有人就要说:变长数组呢?在C99中,变长数组的长度在运行时确定,但在数组的生命周期内仍然是固定的,因为在编写程序时强制选择了大小,所以固定大小的数据结构可能会有问题。也就是说,在不修改程序并且再次编译程序的情况下无法改变数据结构的大小

对于空间的需求,有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。
这时候就只能试试动态存开辟了。

🫅2. 动态内存函数

🤦‍♂️(1)空指针

在调用内存分配函数时,总存在这样的可能性:找不到我们需要的足够大的内存块。那这样的话,函数就会返回空指针(NULL)

  1. 空指针就是“不指向任何地方的指针”
  2. 区别于所有有效指针的特殊值
  3. 名为NULL的有定义的头文件:<locale.h>、<stddef.h>、<stdlib.h>、<string.h>、<time.h>、<wchar.h>(C99)
  4. 数测试真假的方法:0为假,非0为真;指针测试真假的方法:空指针为假,非空为真

注意:
1. 程序员的任务是测试任意内存分配函数的返回值,并且要在返回值为空指针时采取适当措施
2. 通过空指针访问内存的行为是未定义的,程序可能会出现崩溃

🤦‍♂️(2)malloc

在介绍具体的内存分配函数之前,先了解一下他们的功能:

  • malloc:分配内存块,但是不对内存块进行初始化
  • calloc:分配内存块,并且对内存块进行清零
  • realloc:调整先前分配的内存块大小
  • free :释放内存块
  • 所有函数的头文件都是<stdlib.h>
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

🌰

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

int main()
{
	//申请10个整形类型
	int* p = (int*)malloc(10 * sizeof(int));
	//int* p=(int*)malloc(40);

	//判断是否申请空间成功
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}

	//使用   存放1-10
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i + 1;
	}

	//打印
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}

	//释放申请的空间
	free(p);
	p = NULL;

	return 0;
}

在这里解释一下:

  • malloc函数申请空间:
  1. 如果开辟成功,则返回一个指向开辟好空间的指针
  2. 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
  • void* :是通用型指针类型:
  1. 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定
  2. 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器
  • free函数:
  1. free函数是释放动态开辟的空间(堆区上的空间),否则是未定义的行为。如果参数是空指针,那么free函数什么事都不用做
  2. malloc函数和free函数搭配使用
  • 判断是否申请空间成功:
  1. 程序员的任务是测试任意内存分配函数的返回值,并且要在返回值为空指针时采取适当措施
  2. 通过空指针访问内存的行为是未定义的,程序可能会出现崩溃
  • 一旦p指向动态分配的内存块,就可以忽略p是指针的事实,可以把它看成数组的名字。上面代码的使用和打印部分就是利用了这一特点

🐉🐉🐉🐉🐉
当然了,有人还有疑问:为什么一定要释放申请的内存空间?释放了空间之后,为什么要置为空指针?
在这里,我来单独解释:

  1. 向堆区申请了空间,当然要还了 (还给操作系统)
  2. free函数的作用是切断操作系统与该变量之间的联系
  3. 所以,光是将空间还给操作系统是不行的,因为该变量还保留着起始位置的地址,需要我们手动置空
    🐉🐉🐉🐉🐉
    有人又有疑问了:博主,你申请空间的时候,怎么写了两种方法?那种更推荐?
    其实,个人而言,我更推荐第一种,原因是:
    计算变量类型所需要的空间数量时,始终要使用sizeof运算符,如果不能分配足够的空间,将会产生严重的后果。比如,不同的机器上,int类型的大小就不一样,有的是2字节,有的是4字节…
    假如:你写的是第二种 p = (int*) malloc(38); 你所用的机器上int是4个字节,这种情况下,就会产生分配错误的结果。

🤦‍♂️(3)calloc

void* calloc (size_t num, size_t size);

🌰

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

int main()
{
	int* p = calloc(10, sizeof(int));

	if (p == NULL)
	{
		perror(p);
		return 1;
	}

	for (int i = 0; i < 10; i++)
	{
		printf("%d ", p[i]);
	}

	free(p);
	p = NULL;

	return 0;
}

//运行结果:
******
0 0 0 0 0 0 0 0 0 0 
*****

malloc 和 calloc

  • 相同:
  1. 堆区上申请空间
  2. 返回起始地址
  3. 由于是申请空间,不用的话,要释放内存并置空
  • 不同:
  1. malloc一个参数,calloc两个参数,相当于 40(总字节大小) = 10(变量个数) * 4(每个变量的大小)
  2. malloc没有初始化,calloc会把空间初始化为0 - 故malloc比calloc的效率高

🤦‍♂️(4)realloc

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整

void* realloc (void*ptr, size_t size)
  • ptr是要调整的内存地址
  • size是调整之后的大小
  • 返回值是调整之后的内存起始地址
  • realloc在调整内存空间是存在两种情况:
  1. 情况1:原有空间之后有足够大的空间
    要扩展内存就直接在原有内存之后追加空间,原来空间的数据不发生变化,并返回旧空间的起始地址
  2. 情况2:原有空间之后没有足够的空间
    在堆空间上另外找一个大小合适的连续空间来使用,将原有数据拷贝到这个新空间上,释放掉就空间,并返回新空间的起始地址
#include<stdio.h>
#include<stdlib.h>

int main()
{
	int* p = malloc(10 * sizeof(int));
	//判断malloc函数是否开辟成功
	if (p == NULL)
	{
		perror(p);
		return 1;
	}

	int* ptr = realloc(p, 5 * sizeof(int));
	//判断realloc函数是否开辟成功
	if (ptr != NULL)
	{
		p = ptr;
	}

	for (int i = 0; i < 5; i++)
	{
		printf("%d ", p[i]);
	}

	free(p);
	p = NULL;

	return 0;
}

//运行结果:
*****
5个随机数
*****

🐉🐉🐉🐉🐉
看到这里,有人就有疑问了:realloc函数如果开辟失败,原有内存中的数据会怎么变化?为什么不是直接让p来接收realloc的结果?
解释:

  • 如果realloc函数不能按照要求来进行扩展,那么就会返回NULL,原有数据都不会发生变化
  • realloc的结果一定要让一个新指针来接收。因为:
  1. realloc函数可能将内存块移动到堆区上的其他位置
  2. 如果开辟失败返回NULL,那么以后进行的操作就会导致系统崩溃

🐉🐉🐉🐉🐉
此外,realloc函数还需要注意:

  • 传给realloc函数的第一个指针必须来自于先前malloc、calloc 或 realloc的调用,如果不是这样的指针,程序导致崩溃
  • realloc函数不会对添加进内存块的函数进行初始化
  • 第一个参数如果是NULL,那么相当于调用malloc函数
  • 第二个参数是0,将会释放掉该内存块

🤦‍♂️(5)free

void free (void* ptr);
  • free函数是专门来释放动态开辟的内存
  • 如果参数ptr不是指向动态开辟来的空间,将会导致程序崩溃
  • 如果参数ptr是NULL,函数将什么事都不会做

🫅3. 常见的动态内存错误

🤦‍♂️(1)对NULL指针的解引用操作

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

int main()
{

		int* p = (int*)malloc(INT_MAX );
		*p = 20;//如果p的值是NULL,就会有问题
		free(p);

	return 0;
}

补充一下:
INT_MAX:2147483647

🤦‍♂️(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;//当i是10的时候越界访问
	}
	free(p);
}

申请空间的单位一般都是字节

🤦‍♂️(3)对非动态开辟内存使用free释放

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

free函数是专门来释放动态开辟的内存空间的

🤦‍♂️(4)使用free释放动态开辟内存的一部分

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

free函数的参数是要释放动态内存空间的起始地址

🤦‍♂️(5)对同一块动态内存多次释放

void test()
{
   int *p = (int *)malloc(100);
   free(p);
   free(p);//重复释放
}

对同一个空间释放两次了 - 因为p一直保留着起始位置的地址

void test()
{
   int *p = (int *)malloc(100);
   free(p);
   p=NULL;
   free(p);//重复释放
}

这种不是对同一个空间释放空间两次 - 因为p已经置为空了,对空指针进行free操作是没有意义的

🤦‍♂️(6)动态开辟内存忘记释放(内存泄漏)

void test()
{
	int* p = (int*)malloc(100);
	p++;
	free(p);//p不再指向动态内存的起始位置
}

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

动态开辟的空间一定要释放,并且要正确释放,否则会造成内存泄漏

C语言【动态内存管理 前篇】,C语言,c语言,开发语言,数据结构

码文不易,各位看官一键三连哦 💕💕💕
各位的鼓励与支持是我前进最大的动力
文章来源地址https://www.toymoban.com/news/detail-783786.html

到了这里,关于C语言【动态内存管理 前篇】的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 「探索C语言内存:动态内存管理解析」

    🌠先赞后看,不足指正!🌠 🎈这将对我有很大的帮助!🎈 📝所属专栏:C语言知识 📝阿哇旭的主页:Awas-Home page 目录   引言 1. 静态内存 2. 动态内存 2.1 动态内存开辟函数 2.1.1 malloc函数 2.1.2 calloc函数 2.1.3 realloc函数 2.2 动态内存释放函数 2.2.1 free函数 3. 动态内存的常见

    2024年04月28日
    浏览(27)
  • c语言-动态内存管理

    1.我们一般的开辟空间方式: 2.这样开辟空间的特点 (1)申请的空间大小是固定的 (2)像数组那样一开始就要确定大小,一旦确定大小就不能改变了 3.动态内存 对于程序来说上述的内存申请是不能满足 因此为了能够对内存进行调整,C语言引入了动态内存开辟,让程序员自

    2024年02月04日
    浏览(27)
  • 动态内存管理(C语言)

    我们已经掌握的内存开辟方式有 int val = 20;//在栈空间上开辟四个字节 char arr[10] = {0};//在栈空间上开辟10个字节的连续空间 但上述开辟内存的方式有两个特点 空间开辟大小是固定的 数组在声明时必须指定数组的长度,他所需要的内存在编译时分配 但是对于空间的需求,不仅

    2024年02月16日
    浏览(35)
  • C语言:动态内存管理

    先点赞再观看哦! 学习数据结构之前,一定要对指针、结构体、动态内存管理进行深入学习! 小伙伴们可以看看博主之前的文章! 今天重点介绍动态内存开辟!十分重要哈! 我们已知的内存开辟方式有什么呢?? 但是上述开辟的空间有三个特点: 1、空间开辟的大小是固定

    2024年01月22日
    浏览(30)
  • 【C语言:动态内存管理】

    文章的标题是动态内存管理,那什么是动态内存管理?为什么有动态内存管理呢? 回顾一下以前学的知识,我们已经掌握的开辟内存的方式有以下几种: 上述开辟内存的方式有几个弊端: 开辟空间的大小是固定的 数组在声明的时候,必须指定数组的长度,数组空间⼀旦确定

    2024年02月03日
    浏览(29)
  • 动态内存管理 --- C语言

    目录 1.为什么存在动态内存管理 2.动态内存函数的介绍 2.1 malloc 与 free  2.2 calloc 2.3 realloc 3.常见的动态内存错误 4.几个经典笔试题 6.柔性数组 我们已经掌握的内存开辟方式有: 但是上述的开辟空间的方式有两个特点: 空间开辟的 大小是固定的 。 数组在声明的时候, 必须指

    2024年02月11日
    浏览(76)
  • 【C语言】动态内存管理

    大家好,我是苏貝,本篇博客带大家了解动态内存管理,如果你觉得我写的还不错的话,可以给我一个赞👍吗,感谢❤️ 我们已经掌握的内存开辟方式有: int val = 20; 在栈空间上开辟四个字节 char arr[10] = {0}; 在栈空间上开辟10个字节的连续空间 但是上述的开辟空间的方式有

    2024年01月16日
    浏览(28)
  • <C语言> 动态内存管理

    为什么存在动态内存分配? 上述的开辟空间的方式有两个特点: 空间开辟大小是固定的。 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配。 但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组

    2024年02月15日
    浏览(33)
  • 【C语言】进阶——动态内存管理

    我们已经掌握的内存开辟方式有: 但上述开辟空间的方式有两个 特点: 空间开辟 大小 是固定的 数组在声明的时候,必须指定 数组的长度 ,他需要内存在 编译时分配 对于空间的需求,不仅仅是上述的情况,有时候我们需要的空间大小在程序运行的时候才知道,那数组的编

    2024年02月06日
    浏览(38)
  • 【进阶C语言】动态内存管理

    前言 📕作者简介: 热爱跑步的恒川 ,致力于 C/C++、Java、Python 等多编程语言,热爱跑步,喜爱音乐的一位博主。 📗本文收录于 C语言进阶 系列,本专栏主要内容为数据的存储、指针的进阶、字符串和内存函数的介绍、自定义类型结构、动态内存管理、文件操作等,持续更

    2023年04月18日
    浏览(27)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包