「C语言进阶1」动态内存分配

目录

一、动态内存分配是什么?

二、为什么需要动态内存分配?

三、怎么进行动态内存分配?

1. malloc

2. calloc

3. realloc

a. realloc功能解析

b. 内存泄漏和内存块被截断问题

c. 总结

4. free 

四、使用动态内存分配常见的问题

【面试题】


一、动态内存分配是什么?

        动态内存分配是指在程序执行的过程中,系统根据程序的需要动态地分配或回收存储空间的分配内存的方法。

        这种分配方式不像数组等静态内存分配方法那样,需要预先指定要分配的内存大小,且一经分配后大小无法修改,而是由系统按程序的需要灵活地分配和回收内存空间。


二、为什么需要动态内存分配

        在C99引入变长数组前,定义数组时大小不能是变量,这就导致了开辟数组时必须指定数组的大小,且一经开辟,数组的大小再也无法改变。但实际情况下,如实现一个通讯录时,我们往往不知道需要多大的数组,且经常会遇到数组容量不够的情况,这就是静态内存分配的弊端。

        早期的解决方案就是使用动态内存分配,也就是使用四个库函数:malloc、calloc、 realloc、free对堆区的内存进行更灵活的分配和回收。

使用动态内存分配的优势:

  1. 可以灵活分配、回收内存:
            与大小固定的数组相比,动态内存分配可以使用malloc、calloc申请分配内存,使用relloc对申请好的内存空间大小进行调整,使用free回收内存。
  2. 可以随时回收,多次利用这部分空间:
            静态内存分配的空间,直到整个程序结束才会由系统自动释放。但是动态内存分配的空间需要用户手动释放,所以我们可以在使用完后立马通过free函数空间,使得这块空间能在一个程序中被反复使用,这样也能在一定程度上节省内存空间。
  3. 使用内存中堆区的空间
            动态内存分配是在堆上分配空间的,静态内存分配是在栈上分配空间的,所以了解动态内存分配后就能使用堆上的空间了。

三、怎么进行动态内存分配?

        C语言中动态内存管理方式是通过四个库函数实现的:malloc、calloc、 realloc、free。这四个库函数都在头文件<stdlib.h>

1. malloc

函数声明    |        void*  malloc (size_t  size);
功能      |        在堆上申请一个大小为size字节的空间。(申请分配动态内存)
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。
#include<stdio.h>
#include<stdlib.h>

int main()
{
	// 开辟一个动态内存变量
    //malloc函数的返回值类型为void*所以要强制类型转换
	int* a = (int*)malloc(1 * sizeof(int));
	// 检查动态内存是否开辟失败,开辟失败返回NULL
	if (NULL == a)
	{
		printf("动态内存开辟失败。\n");
		return 0;
	}
	// 使用动态内存
	*a = 1;
	printf("%d\n", *a);
	// 释放动态内存
	free(a);
    a = NULL; //free后指针仍指向原来的空间,所以要置为NULL
}
	// 开辟一个动态内存数组
	int size = 10; // 动态内存数组大小
	int* arr = (int*)malloc(size * sizeof(int));
	// 检查动态内存是否开辟失败
	if (NULL == arr)
	{
		printf("动态内存开辟失败。\n");
		return 0;
	}
	// 使用动态内存数组(和静态内存数组的使用没有差别)
	for (int i = 0; i < size; ++i)
	{
		arr[i] = i;
		printf("%d ", arr[i]);
	}
	// 释放动态内存数组
	free(arr);
    arr = NULL;

2. calloc

函数声明    |        void* calloc (size_t  num, size_t  size);
功能      |        在堆上申请大小为num个size字节的空间,并初始化空间中的内容为0。(分配动态内存)
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。
	// 使用calloc函数开辟一个动态内存变量/数组
	int size = 10; // 动态内存数组大小,size = 1即为变量
	int* arrc = (int*)calloc(size , sizeof(int));// calloc函数的返回值类型为void*
	if (NULL == arrc)// 检查
	{
		printf("动态内存开辟失败。\n");
		return 0;
	}
	for (int i = 0; i < size; ++i)// 使用
	{
		printf("%d ", arrc[i]); //calloc默认初始化内容全为0
	}
	free(arrc);// 释放
    arrc = NULL;

3. realloc

函数声明    |        void* realloc (void*  mem_address, unsigned int  newsize);
功能      |        见下方解析。(对申请好的动态内存空间大小进行调整,可以扩展或缩小动态内存。
返回值     |        若申请成功,返回一个指向该空间开头的指针;若失败,则返回空指针。

a. realloc功能解析

        realloc函数用于对申请好的动态内存空间大小进行调整。它接受两个参数:一个是已分配的动态内存的指针,另一个是要重新分配的大小(单位字节)。

        realloc函数首先检查传入的指针是否为空指针。如果是空指针,则等同于调用malloc函数,直接分配指定大小的动态内存,并返回指向该动态内存的指针。
        如果传入的指针不是空指针,则realloc函数会尝试重新分配内存块的大小。它会根据传入的新内存块大小newsize和原有内存块的大小oldsize来判断如何重新分配内存:

  • newsize = 0,释放原有的内存块,并返回空指针。
  • newsize > oldsize,则realloc函数会尝试在指针参数指向原有内存块的上进行扩容:
    如果原有内存块有足够的连续空间,直接扩容,并且将传入的指针返回;
    如果没有足够的连续空间,先按照指定的大小分配一个新的内存块,然后将原有数据拷贝到新内存块中,而后由系统自动释放原来的内存块,并返回新内存块起始位置的指针;
    如果扩容失败,则返回空指针,并且原有内存块的内容保持不变。
  • newsize < oldsize,则realloc函数会尝试缩小内存块的大小:
    它可能会直接使用缩小后的原内存块;
    也可能将原内存块中的数据复制到新内存块中,并返回指向新内存块的指针。内容不变,但是多余的数据可能会被截断。

b. 内存泄漏和内存块被截断问题

使用realloc函数时需要小心内存泄漏和内存块被截断的问题:

  • 如果realloc函数返回空指针,则说明内存分配失败,原内存块没有被释放,这可能会导致内存泄漏;(realloc失败不会释放原空间)
  • realloc函数可能会因为原来位置的内存大小不够,将原有内存块移到新的位置。但是,指向原有内存块的指针仍然指向原内存块,这可能会导致内存泄漏。所以realloc后要将原指针置为NULL。
  • 如果新的内存块大小小于原有内存块的大小,则可能会导致多余的数据被截断。

c. 总结

        realloc函数用于重新分配内存块的大小,可以扩展或缩小内存块。但是使用时需要注意内存泄漏和数据截断的问题。

	// 使用realloc函数调整动态内存的大小
	// 1.如果传入的是空指针,则相当于使用malloc开辟新空间:
	int* p1 = (int*)realloc(NULL, 1 * sizeof(int));
	if (NULL == p1)
	{
		printf("动态内存开辟失败。\n");
		return 0;
	}
	*p1 = 1;
	printf("%d\n", *p1);
	free(p1);
		printf("p1所指向的空间:%d\n", sizeof p1);
	// 2.如果新空间需要的大小newsize = 0
	//则释放原有的内存块,并返回空指针。
	int* p2 = (int*)malloc(1 * sizeof(int));
	int* p3 = (int*)realloc(p2, 0);
	if (NULL == p3)
		printf("p2, p3所指向的空间将会由系统在合适的时间释放。\n");

	// 3.指针指向的旧空间大小oldsize < 新空间需要的大小newsize
	// 可能在原空间上扩容,也可能找一个新空间扩容
	int* p4 = (int*)calloc(10, sizeof(int));
	int* p5 = (int*)realloc(p4, 12 * sizeof(int));
	int* p6 = (int*)realloc(p5, 1000 * sizeof(int));
	if (p4 == p5)
		printf("在原空间上扩容。\n");
	if (p5 != p6)
		printf("找一个新空间扩容。\n");

	// 4.oldsize > newsize
	// 可能使用原空间,也可能找一个新空间
	int* p7 = (int*)calloc(10, sizeof(int));
	int* p8 = (int*)realloc(p7, 9 * sizeof(int));
	int* p9 = (int*)realloc(p8, 2 * sizeof(int));
	if (p7 == p8)
		printf("使用原空间。\n");
	if (p8 != p9)
		printf("找一个新空间。\n");

4. free 

函数声明    |        void free (void*  ptr);
        |        释放ptr指向的空间,如果ptr是空指针则不做处理;如果ptr指向的空间不是动态内存或是
功能      |        已经被释放的动态内存,则free函数的结果是未知的——可能导致程序崩溃,也可能导致
        |        内存泄漏。(回收动态内存)
返回值     |        无。

        释放动态内存,并不意味着执行到free函数时立马让该空间的内存全变为0,也不会让指针指向NULL,这只是告诉操作系统可以重新使用该内存块,具体的释放时间由操作系统决定。所以free后要手动将指针置为NULL。这也意味着无法通过指针是否指向NULL来判断空间是否已经被释放。

        注意,只有动态申请的堆区内存需要我们主动释放,其它区都由系统管理,强行free可能导致程序崩溃:


四、使用动态内存分配常见的问题

3.1 不检查动态内存是否开辟成功

如果开辟失败,返回值为NULL,不检查就可能会对NULL进行解引用操作。
    int *p = (int *)malloc(INT_MAX/4);
    *p = 20;//如果p的值是NULL,就会有问题
    free(p);

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

和静态数组一样,因忘记数组大小,导致越界访问。
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	for (int i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);

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

强行free非动态内存可能导致程序崩溃
	int t = 1;
	int* tp = &t;
	free(tp);
	return 0;

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

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

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

一样可能导致程序崩溃。
	int* p = (int*)calloc(10, sizeof(int));
	if (NULL == p)
	{
		exit(EXIT_FAILURE);
	}
	free(p);
	free(p);
	return 0;

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

        最常见的问题:忘记使用free函数释放动态内存空间,如果是一个长期运行的程序,这部分内存空间会因未释放而一直被占用,这就导致程序消耗的内存越来越多。切记: 动态开辟的空间一定要释放,并且正确释放 。

3.7 使用realloc和free后,未将原指针置为NULL

        realloc函数可能原位置空间不足,而使用新内存块移到新的位置。但是不会让指向原有内存块的指针不会指向NULL。free函数也不会让指针指向NULL。这可能会导致内存泄漏。所以realloc和free后要将原指针置为NULL。


【面试题】

题目1

void Test()
{
    int* p1 = (int*)malloc(sizeof(int));
    free(p1);
    // 1.malloc/calloc/realloc的区别是什么?
    int* p2 = (int*)calloc(4, sizeof(int));
    int* p3 = (int*)realloc(p2, sizeof(int) * 10);
    // 2.这里需要free(p2)吗?
    free(p3);
}


1. malloc/calloc/realloc的区别?

        从参数和函数功能方面进行回答。


2.这里需要free(p2)吗?

        答:不需要,使用realloc扩容时,自动释放了p2之前的空间。

题目2

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


1. 请问运行Test 函数会有什么样的结果?

        答:运行Test函数会导致程序崩溃,因为形参只是实参的拷贝,形参的改变并不影响实参,所以str仍指向NULL,这会导致空指针异常。

题目3 

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


1. 请问运行Test 函数会有什么样的结果?

        答:不确定,因为在函数GetMemory执行完后,所处空间被系统销毁,p指向的空间也被销毁了,此时str因函数返回值指向了p指向的这片被消毁的空间。所以运行Test函数会产生不确定的结果。

题目4:

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


1. 请问运行Test 函数会有什么样的结果?

        答: 可能会导致程序崩溃,也可能会在显示屏上打印world。因为不是执行free函数就立即销毁指针指向的空间,指针也不会被free函数被置为NULL,所以指针仍指向原空间,如果系统还没销毁该空间,那么会在显示屏上打印world,否则会导致程序崩溃。


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/404902.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

线代:认识行列式、矩阵和向量

本文主要参考的视频教程如下&#xff1a; 8小时学完线代【中国大学MOOC*小元老师】线性代数速学_哔哩哔哩_bilibili 另外这个视频可以作为补充&#xff1a; 【考研数学 线性代数 基础课】—全集_哔哩哔哩_bilibili 行列式的概念和定义 一般会由方程组来引出行列式 比如一个二阶…

IO进程线程的通信操作

1.编程实现互斥机制 程序代码&#xff1a; 1 #include<myhead.h>2 int num520;//临界资源3 //1.创建一个互斥锁变量4 pthread_mutex_t mutex;//定义一个pthread_mutex_t类型的变量5 //定义任务1函数6 void *task1(void *arg)7 {8 printf("不畏过去\n");9 …

java面试题之mybatis篇

什么是ORM&#xff1f; ORM&#xff08;Object/Relational Mapping&#xff09;即对象关系映射&#xff0c;是一种数据持久化技术。它在对象模型和关系型数据库直接建立起对应关系&#xff0c;并且提供一种机制&#xff0c;通过JavaBean对象去操作数据库表的数据。 MyBatis通过…

驾校预约|驾校预约小程序|基于微信小程序的驾校预约平台设计与实现(源码+数据库+文档)

驾校预约小程序目录 目录 基于微信小程序的驾校预约平台设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户​微信端功能模块​ 2、管理员服务端功能模块 &#xff08;1&#xff09;学员信息管理 &#xff08;2&#xff09; 教练信息管理 &#xff08;3&…

Django学习笔记-HTML实现服务器图片的下载

1.index编写代码,跳转下载页面 2.创建download界面 3.编写download路由 4.创建download函数 1).如果请求的方法是GET&#xff0c;imglist变量存储从models.imgModel模型中获取的所有对象,创建字典ctx,使用render函数来渲染download.htm 2).如果请求的方法是POST,获取要下载的文…

Spring学习上下文【ConfigurableApplicationContext】

话不多说&#xff0c;先上图&#xff1a; ConfigurableApplicationContext是Spring框架中的一个接口&#xff0c;它继承了ApplicationContext接口&#xff0c;并扩展了一些额外的方法&#xff0c;用于允许应用程序在运行时动态地修改和管理应用上下文。ConfigurableApplicati…

【js】无限虚拟列表的原理及实现

什么是虚拟列表 虚拟列表是长列表按需显示思路的一种实现&#xff0c;即虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术。 简而言之&#xff0c;虚拟列表指的就是「可视区域渲染」的列表。有三个概念需要了解一下&#xff1a; 视口容器元…

KDD 2023 图神经网络方向论文总结

ACM SIGKDD&#xff08;国际数据挖掘与知识发现大会&#xff0c;KDD&#xff09;是数据挖掘领域历史最悠久、规模最大的国际顶级学术会议&#xff0c;也是首个引入大数据、数据科学、预测分析、众包等概念的会议。今年&#xff0c;第29届 KDD 大会在美国加州长滩举行&#xff0…

冒泡排序法的名字由来,排序步骤是什么,最坏情况下的排序次数如何计算得来的呢?

问题描述&#xff1a;冒泡排序法的名字由来&#xff0c;排序步骤是什么&#xff0c;最坏情况下的排序次数如何计算得来的呢&#xff1f; 问题解答&#xff1a; 冒泡排序法的名字来源于排序过程中较大的元素会像气泡一样逐渐“冒”到序列的顶端&#xff0c;而较小的元素则会逐…

代码随想录算法训练营第四十天 343. 整数拆分、 96.不同的二叉搜索树

代码随想录算法训练营第四十天 | 343. 整数拆分、 96.不同的二叉搜索树 343. 整数拆分 题目链接&#xff1a;343. 整数拆分 - 力扣&#xff08;LeetCode&#xff09; 例如 n 10, 可以拆分为 3 * dp[7] 。因为dp[7]之前已经计算过最大 3 * 4&#xff0c; 所以dp[10] 3 * 3 …

Microsoft 365自定义安装软件

如图&#xff0c;在安装类型的步骤的时候&#xff0c;可以勾选自己想要的软件&#xff08;而非一股脑儿的安装一大堆自己不需要的&#xff09;。

AI绘画巅峰对决:Stable Diffusion 3与DALL·E 3原理深度比较

最近&#xff0c;Stable Diffusion 3 的预览版已经亮相啦&#xff01; 虽然这个AI绘画模型还没全面上线&#xff0c;但官方已经开启预览申请通道了。 https://stability.ai/stablediffusion3 而且好消息是&#xff0c;后面还会推出开源版本哦&#xff01; 这个模型套件真的…

五种多目标优化算法(MOAHA、MOGWO、NSWOA、MOPSO、NSGA2)性能对比(提供MATLAB代码)

一、5种多目标优化算法简介 多目标优化算法是用于解决具有多个目标函数的优化问题的一类算法。其求解流程通常包括以下几个步骤&#xff1a; 1. 定义问题&#xff1a;首先需要明确问题的目标函数和约束条件。多目标优化问题通常涉及多个目标函数&#xff0c;这些目标函数可能…

基于SpringBoot的产业园区智慧公寓管理系统

文章目录 项目介绍主要功能截图&#xff1a;部分代码展示设计总结项目获取方式 &#x1f345; 作者主页&#xff1a;超级无敌暴龙战士塔塔开 &#x1f345; 简介&#xff1a;Java领域优质创作者&#x1f3c6;、 简历模板、学习资料、面试题库【关注我&#xff0c;都给你】 &…

这才开工没几天收到Offer了,简历改的好,找工作没烦恼。

喜报喜报 这才开工没几天&#xff0c;就收到了喜报&#xff01; 就像上面截图中所说的一样&#xff1a;简历改了真的有用。 我也和大家分享一下优化简历的技巧&#xff0c;希望对大家有帮助&#xff0c;把握住金三银四的机会&#xff0c;都能顺利上岸&#xff0c;升职加薪&am…

多数pythoneer只知有列表list却不知道python也有array数组

数组和列表 Python中数组和列表是不同的&#xff0c;我敢断言大多数的pythoneer只知道有列表list&#xff0c;却不知道python也有array数组。列表是一个包含不同数据类型的元素集合&#xff0c;而数组是一个只能含相同数据类型的元素集合。 Python的array库是一个提供数组操作…

Android 广播的基本概念

一.广播简介 Broadcast是安卓四大组件之一。安卓为了方便进行系统级别的消息通知&#xff0c;引入了一套广播消息机制。打个比方&#xff0c;记得原来在上课的时候&#xff0c;每个班级的教室里都会装有一个喇叭&#xff0c;这些喇叭都是接入到学校的广播室的&#xff0c;一旦…

【初始RabbitMQ】交换机的实现

交换机概念 RabbitMQ消息传递模型的核心思想就是&#xff1a;生产者生产的消息从不会直接发送到队列。实际上&#xff0c;通常生产者不知道这些消息会传递到那些队列中 相反&#xff0c;生产者只能将消息发送到交换机&#xff0c;交换机的工作内容也很简单&#xff0c;一方面…

网络安全8-11天笔记

内容安全&#xff1a; 攻击可能只是一个点&#xff0c;防御需要全方面进行。 IAE引擎&#xff1a; DFI和DPI技术&#xff1a;深度检测技术 DPI——深度包检测技术&#xff1a;主要针对完整的数据包&#xff08;数据包分片&#xff0c;分段需要重组&#xff09;&#xff0c;之…

Linux--自定义shell

shell shell就是操作系统提供给用户与操作系统进行交互的命令行界面。它可以理解为一个用户与操作系统之间的接口&#xff0c;用户可以通过输入命令来执行各种操作&#xff0c;如文件管理、进程控制、软件安装等。Shell还可以通过脚本编程实现自动化任务。 常见的Unix系统中使…