【C语言】零基础教程——动态内存管理

本文详细介绍了动态内存管理,包括malloc, free, calloc和realloc函数的使用,解析了它们的特点和应用场景。同时,讨论了动态内存分配中可能出现的错误,如NULL指针解引用、越界访问、非法释放和内存泄漏等。强调了释放内存后将指针置为NULL的重要性。

目录

1.为什么存在动态内存分配

        1.1我们熟知的开辟内存方式有

         1.2已知的开辟方式有以下两个特点

        1.3原因

 2. 动态内存函数的介绍

        2.1 malloc

        malloc函数的特点

        malloc函数具体使用

                那如果malloc函数开辟失败会怎么样呢?

                在C语言中有这么一个定义好的值:INT_MAX

        什么是栈区,堆区?

2.2 free

        先来看看free函数的特点吧~

        具体应用&&带入刚刚的例子

        和free前有什么一样的,有什么不一样的?

2.2 calloc

        calloc函数有什么特点呢?和malloc函数有什么区别呢?

2.3 realloc

        realloc函数的特点 

        例子:在malloc开辟的40个字节的空间扩容到80个字节的空间

        举个例子:

3. 常见的动态内存错误

        对NULL指针的解引用操作

        对动态开辟空间的越界访问

        对非动态开辟内存使用free释放

        使用free释放一块动态开辟内存的一部分

        对同一块动态内存多次释放

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



1.为什么存在动态内存分配

1.1我们熟知的开辟内存方式有

        在栈区上开辟4个字节的空间

int i = 0

        在栈区上开辟16个字节的连续空间

int i[4] = { 0 };

 1.2已知的开辟方式有以下两个特点

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

1.3原因

        但是,有些时候,我们要通过运行程序才能知道数组需要的空间大小,亦或是在程序运行期间,需要增大数组的空间,这又该如何去做呢?

         这就不得不引入动态内存开辟的概念了~


 2. 动态内存函数的介绍

2.1 malloc

        C语言提供了来自stdlib.h头文件中动态内存开辟的函数

void* malloc (size_t size);

此函数会向 堆区 申请一段连续可用空间,开辟成功则返回开辟空间的起始位置的地址 

malloc函数的特点

        开辟成功,返回这块空间的指针;

        开辟失败,返回一个NULL指针,所以每一次开辟空间一定要检查;

        返回类型为void* ,所以需要我们根据自己的需求,将返回的指针强制类型转化成我们需要的指针;

        若size的单位是字节,若参数为0,malloc是未定义的,取决于编译器;

malloc函数具体使用

举个例子:开辟10个整形的空间,并分别赋值0~9的数字

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));//若开辟失败,打印错误信息
		return 1;//约定俗成:返回0表示正常,返回1表示不正常
	}
	int i = 0;
	//赋值
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*(p + i));
	}
	return 0;
}

        这时候可能有同学可能会问了:

        那如果malloc函数开辟失败会怎么样呢?

演示一波~

        在C语言中有这么一个定义好的值:INT_MAX

 这是什么东西呢?

        叫整形最大值,有多大呢?

转到定义来看看:

 不得了,是一个21亿多的一个数字,这时候,让malloc函数开辟这么打一个空间试试?

嗯...好吧没想到电脑内存还蛮大的,开辟出来了...

 这样,咱来个INT_MAX*INT_MAX

走~

 还没运行,就已经提醒你了哈哈哈。

一个开辟空间失败的错误示范;

还有一个点:

什么是栈区,堆区?

        内存里面分为三个区,就是栈区,堆区,静态区(实际上还有很多其他区,主要讲这几点),栈区是用来存放临时变量的,变量空间定义好就不能再变了,而malloc、calloc、realloc这几个函数就会在堆区上开辟空间,定义好后根据需求,可以继续改变变量所占空间的大小,静态区用来存放全局变量;(如下图)

细致一点就如下图:

 

        有没有思考过这样一个问题?

       即使一个空间用完了,退出程序,系统会自动回收空间, 但如果一个空间用完了,不想再用了,程序还在需要继续运行,继续开辟空间,那岂不是会造成内存泄露?

2.2 free

        不用担心~😶‍🌫️

        C语言专门提供了一个函数free,用来解决内存泄漏,回收空间的功能!原型如下:

void free (void* ptr);

先来看看free函数的特点吧~

1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数 ptr 是NULL指针,则函数什么事都不做。

3.来源于stdlib.h

具体应用&&带入刚刚的例子

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s", strerror(errno));//若开辟失败,打印错误信息
		return 1;//约定俗成:返回0表示正常,返回1表示不正常
	}
	int i = 0;
	//赋值
	for (i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	//打印
	for (i = 0; i < 10; i++)
	{
		printf("%d ",*(p + i));
	}
    free(p);
    p = NULL;
	return 0;
}

这里又是一个细节~

p = NULL?

 想象以下,释放了内存,可以不管这个指针p了吗?

        经过调试可以看到:

free前:

 注        意:p,10可以展示出10个元素,若只是p,只会显示一个值;

free后:

和free前有什么一样的,有什么不一样的?

        free后,内存还给操作系统,动态开辟的空间里赋的值全部变为随机数,但是,p指针指向的地址依旧没变(x64环境)。

        这样会使得指针p十分危险(野指针),即使空间还给了操作系统,但是p地址依旧不变,导致如果以后不小心误用了指针p会导致内存的非法访问!

所以一定要注意,每次释放完动态内存开辟的空间后,请将指针置为NULL;

2.2 calloc

        C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配

void* calloc (size_t num, size_t size);

calloc函数有什么特点呢?和malloc函数有什么区别呢?

calloc函数的特点是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0;

区别在于: malloc会将创建好的空间赋上随机值,而calloc 会在返回地址之前把申请的空间的每个字节初始化为全0;

举个例子:

 可以从内存看出calloc函数开辟好空间,内存就已经全部初始化好成全零了

calloc貌似就等于malloc+memset(初始化全零)

所以如果在需要对开辟好的空间初始化成全零,calloc无疑是个很好的选择!

2.3 realloc

        此函数的出现让动态内存开辟更加灵活!实现了真正意义上的 “动态”~

🤔写代码时,我们常常会遇到这样一个场景,开辟了一个40个字节的空间,但是后来实际操作中,又感觉不够用了,或者是感觉开辟大了,怎么办呢?

        那 realloc 函数就可以做到对动态开辟内存大小的调整。

函数原型:

void* realloc (void* ptr, size_t size);

realloc函数的特点 

1.ptr 是要调整的内存地址
2.size 调整之后新大小
3.返回值为调整之后的内存起始位置。
4.这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

具体来讲realloc在调整内存空间的是存在两种情况:(如下图解)

例子:在malloc开辟的40个字节的空间扩容到80个字节的空间

情况一:

 情况二:

由于上述的两种情况,realloc函数的使用就要注意一些。

举个例子:

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	p = (int*)realloc(p, 80);//注意这里!这样写可以吗?
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	free(p);
	p = NULL;
	return 0;
}

观察代码中的思考问题,这样写合理吗?

不合理!

        想象以下,如果realloc空间开辟失败,就会传给p一个空指针,但是p本身是有指向malloc开辟的那块地址啊,一旦接收空指针,原来的那块空间不就废掉了吗?

        所以可以考虑用个临时指针来接收,如果realloc开辟成功,再将临时指针赋值给p,就可以有效避免内存泄漏问题 ;

代码实现如下:

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

int main()
{
	int* p = (int*)malloc(40);
	if (p == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	else
	{
		//处理
	}
	int* tmp_p = (int*)realloc(p, 80);
	if (tmp_p != NULL)
	{
		p = tmp_p;
		//处理
	}
	else
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	free(p);
	p = NULL;
	return 0;
}

3. 常见的动态内存错误

对NULL指针的解引用操作

直接上代码:

int main()
{
	int* ptr = (int*)malloc(INT_MAX * INT_MAX);
	*ptr = 10;
	return 0;
}

        想象以下如果malloc开辟失败会发生什么?

        malloc开辟失败返回一个NULL,赋值给ptr

        造成对NULL指针的解引用操作!

对动态开辟空间的越界访问

        看看以下代码哪里出问题了?

int main()
{
	int* ptr = (int*)malloc(40);
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//赋值
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		*(ptr + i) = i;
	}
    free(ptr);
    ptr = NULL;
	return 0;
}

很明显,当赋值10的时候发生了越界访问;

对非动态开辟内存使用free释放

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

int main()
{
	int* ptr = (int*)malloc(40);
	if (ptr == NULL)
	{
		printf("%s\n", strerror(errno));
		return 1;
	}
	//赋值
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		*ptr = i;
		ptr++;
	}

	free(ptr);
	ptr = NULL;

	return 0;
}

 这样是否可行?

        ptr一旦++,导致ptr指针不在指向动态开辟的起始位置,这时候free会发生什么呢?(如下图)

这时候有人可能会说:那用const修饰以下不就很安全吗?

        是啊,安全是安全了,但是ptr = NULL;不也不行了吗?从而产生野指针问题,所以一定要注意避免!

 

使用free释放一块动态开辟内存的一部分

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

对同一块动态内存多次释放

int main()
{
    int* ptr = (int*)malloc(40);
    if(ptr == NULL)
    {
        return 1;
    }
    else
    {
        //处理
    }
    free(ptr);

    //处理

    free(ptr);//重复释放
    return 0;
}

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

int main()
{
    int* ptr = (int*)malloc(40);
    if(ptr == NULL)
    {
        return 1;
    }
    else
    {
        //处理
    }
    return 0;//忘记free
}

注        意:动态开辟的空间一定要释放,并且正确释放!

 

码字不易~

Delphi 12.3 作为一款面向 Windows 平台的集成开发环境,由 Embarcadero Technologies 负责其持续演进。该环境以 Object Pascal 语言为核心,并依托 Visual Component Library(VCL)框架,广泛应用于各类桌面软件、数据库系统及企业级解决方案的开发。在此生态中,Excel4Delphi 作为一个重要的社区开源项目,致力于搭建 Delphi 与 Microsoft Excel 之间的高效桥梁,使开发者能够在自研程序中直接调用 Excel 的文档处理、工作表管理、单元格操作及宏执行等功能。 该项目以库文件与组件包的形式提供,开发者将其集成至 Delphi 工程后,即可通过封装良好的接口实现对 Excel 的编程控制。具体功能涵盖创建与编辑工作簿、格式化单元格、批量导入导出数据,乃至执行内置公式与宏指令等高级操作。这一机制显著降低了在财务分析、报表自动生成、数据整理等场景中实现 Excel 功能集成的技术门槛,使开发者无需深入掌握 COM 编程或 Excel 底层 API 即可完成复杂任务。 使用 Excel4Delphi 需具备基础的 Delphi 编程知识,并对 Excel 对象模型有一定理解。实践中需注意不同 Excel 版本间的兼容性,并严格遵循项目文档进行环境配置与依赖部署。此外,操作过程中应遵循文件访问的最佳实践,例如确保目标文件未被独占锁定,并实施完整的异常处理机制,以防数据损毁或程序意外中断。 该项目的持续维护依赖于 Delphi 开发者社区的集体贡献,通过定期更新以适配新版开发环境与 Office 套件,并修复已发现的问题。对于需要深度融合 Excel 功能的 Delphi 应用而言,Excel4Delphi 提供了经过充分测试的可靠代码基础,使开发团队能更专注于业务逻辑与用户体验的优化,从而提升整体开发效率与软件质量。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陈亦康

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值