【轻松掌握C语言】动态内存管理

news2024/12/24 10:04:19

目录

          一、为什么存在动态内存分配

          二、动态内存函数

               1、malloc函数

                   (1)函数的用途

                   (2)函数的使用

               2、free函数

                   (1)函数的用途

                   (2)函数的使用

               3、calloc函数

                   (1)函数的用途

                   (2)函数的使用

               4、realloc函数

                   (1)函数的用途

                   (2)函数的使用

          三、常见的动态内存错误

               1、对NULL指针的解引用操作

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

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

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

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

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

          四、经典的笔试题


一、为什么存在动态内存分配

int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

上述述的开辟空间的方式有两个特点:

1、空间开辟大小是固定的。

2、数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。

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

二、动态内存函数

    1、malloc函数

      (1)函数的用途

 这个函数是向内存申请一块连续可用的空间,并返回指向这块空间的指针。申请的时候是按字节申请的,具体用法如下:

 malloc函数使用时,注意

因为malloc函数的返回类型是void* ,但是void* 类型是不可以直接使用的,需要我们强制类型转换成需用的类型才可以使用。

1、如果开辟成功,则返回一个指向开辟好空间的指针。

2、如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

3、返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

4、如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

      (2)函数的使用

int main()
{
	int* p = (int*)malloc(40);//向内存申请了40个字节的空间
	if (p == NULL)
	{
		perror("malloc");//如果是空指针就会报错,给出相应的提示
	}
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(p+i));
	}
	free(p);//释放空间
	p = NULL;
	return 0;
}

malloc函数是要和free函数联合使用。下面就讲一讲free函数。

    2、free函数

      (1)函数的用途

 free函数用来释放动态开辟的内存,是与内存开辟函数配套使用的。具体用法如下:

 注意:

1、如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

2、如果参数 ptr 是NULL指针,则函数什么事都不做。 

      (2)函数的使用

free(ptr);
ptr = NULL;

在前面开辟完空间并使用后,我们要将动态开辟的内存空间给释放掉,我们释放掉之后为什么要在赋值为空指针呢?因为这只是释放了那块空间,而里边的内容依然存在,ptr 指针依然能找到这块空间,为了把内容清除掉,就将 ptr 赋值为空指针 。

    3、calloc函数

      (1)函数的用途

calloc函数和malloc函数都是用来开辟动态空间的,具体用法如下:

注意:

1、函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。

2、与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

int* p = (int*)malloc(10*sizeof(int));
int* p = (int*)calloc(10,sizeof(int));

 malloc函数申请的空间没有被初始化,calloc函数申请的空间都初始化为0。

      (2)函数的使用

int main()
{
	int* p = (int*)calloc(10, sizeof(int));
	if (p == NULL)
	{
		perror("calloc");
	}
	for (int i = 0; i < 10; i++)
	{
		*(p + i) = i;
		printf("%d ", *(p + i));
	}
	free(p);
	p = NULL;
	return 0;
}

    4、realloc函数

      (1)函数的用途

realloc函数可以做到对动态开辟内存大小 的调整,具体用法如下:

 注意:

这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

realloc函数在 调整内存空间的是存在两种情况:

情况1:原有空间之后有足够大的空间;

情况2:原有空间之后没有足够大的空间。

情况1:

当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。

情况2:

当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

      (2)函数的使用

int main()
{
    int* p = malloc(40);
    if (p != NULL)
    {
        //使用
    }
    else
    {
        perror("malloc");
    }
    int* ptr = (int*)realloc(p, 100);
    if (ptr != NULL)
    {
        p = ptr;
    }
    //使用
    free(p);
    p = NULL;
}

三、常见的动态内存错误

    1、对NULL指针的解引用操作

int main()
{
	int* p = (int*)malloc(40);
	*p = 100;//如果p是NULL,就会出现问题
    free(p);
	return 0;
}

上述代码是无法判断我们开辟的空间是否成功,如果没有开辟成功直接使用会出现问题,所以在这里需要提前判断一下,正确写法如下:

int main()
{
	int* p = (int*)malloc(40);
	if (*p == NULL)
	{
		perror("malloc");
        return;
	}
    *p = 100;
	free(p);
	return 0;
}

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

void test()
{
	int i = 0;
	int* p = (int*)malloc(10 * sizeof(int));
	if (NULL == p)
	{
		perror("malloc");
	}
	for (i = 0; i <= 10; i++)
	{
		*(p + i) = i;//当i是10的时候越界访问
	}
	free(p);
}

 改进措施:就是将for循环的第二个条件的等号去掉。

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

void test()
{
	int a = 10;
	int* p = &a;
	free(p);//对非动态开辟内存不能使用free释放
}

free函数是针对于在堆上动态开辟的空间进行释放。

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

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

在free函数执行前,p指向的不再是动态内存空间的起始位置,所以开辟的内存没有完全被释放完。

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

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

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

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

要记住:动态开辟内存空间一定释放,否则会存在内存泄漏的问题。 

四、经典的笔试题

  1、运行下面Test函数会怎么样?

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

解析:

1、无法对空指针进行解引用;

在Test函数中,我们调用了getmemory函数,这时候是将str这个指针传入,而是将str的数据,即NULL进行了传入,所以此时p接收的是NULL。

2、存在内存泄漏。

正确写法为:

void GetMemory(char** p)
{
	*p = (char*)malloc(100);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str);
	strcpy(str, "hello world");
	printf(str);
	free(str);
	str = NULL;
}
int main()
{
	Test();
	return 0;
}

     2、运行下面Test函数会怎么样?

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

解析:

返回栈空间地址问题。

因为这里的p是一个局部变量,出了作用域就销毁了,所以存储的字符串也会被释放掉。当这段空间被释放掉后,这段空间依然存在,但是已经没有了使用权限,虽然没有使用权限我们还可以通过一个相同德 地址找到这块空间。但是这块空间中就存的不是我们想要的内容了。

    3、运行下面Test函数会怎么样?

void GetMemory(char** p, int num)
{
	*p = (char*)malloc(num);
}
void Test(void)
{
	char* str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}
int main()
{
	Test();
	return 0;
}

解析:

没有free,出现了内存泄漏问题。

   4、运行下面Test函数会怎么样?

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

解析:

非法访问,这里的str是野指针。

这里释放完str后,一定要将指向这段空间的指针置为空,否则就出现了野指针的问题。


本文要是有不足的地方,欢迎大家在下面评论,我会在第一时间更正。

老铁们,记着点赞加关注哦!!! 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【小f的刷题笔记】(JS)链表 - 单链表的倒数第 k 个节点 LeetCode19 单链表的中点 LeetCode876

【链表】 一、单链表的倒数第 k 个节点&#xff1a; ✔ 要求&#xff1a;只遍历一遍&#xff0c;链表有多长未知 LeetCode19 链接&#xff1a; 19.删除链表的倒数第N个结点 题目&#xff1a; 思路&#xff1a; 因为没有给头结点&#xff0c;我们就先定义一个哑结点&#…

从对称加密和非对称加密讲解HTTP到HTTPS的发展思路

一、传统的HTTP协议 传统的http在进行网络数据传输时&#xff0c;数据信息都是明文的&#xff0c;因此就很容易出现数据在网络的传输过程&#xff08;中间路由过程&#xff09;数据被监听或者窃取、替换的危险。因此http是一种不安全的传输协议。 那么就需要对数据进行加密。…

网络编程与通信原理

总感觉这个概念&#xff0c;和研发有点脱节&#xff1b; 一、基础概念 不同设备之间通过网络进行数据传输&#xff0c;并且基于通用的网络协议作为多种设备的兼容标准&#xff0c;称为网络通信&#xff1b; 以C/S架构来看&#xff0c;在一次请求当中&#xff0c;客户端和服务端…

物联网开发笔记(59)- 使用Micropython开发ESP32开发板之控制合宙4g Air724U模块

一、目的 这一节我们学习如何使用我们的ESP32开发板来控制合宙4g Air724U模块。 二、环境 ESP32 合宙4g Air724U模块 Thonny IDE 几根杜邦线 接线方法&#xff1a; 注意连接方式&#xff1a; ESP32的RX2----->4G模块的TX ESP32的TX2----->4G模块的RX 三、介绍 1&…

JSP ssh机房学生上机管理系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

一、源码特点 JSP SSH机房学生上机管理系统是一套完善的web设计系统&#xff08;系统采用ssh框架进行设计开发&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采 用B/S模式开发。开发环境为TOMCA…

SpringBoot 注解方式快速整合Mybatis

序言&#xff1a;使用MyBatis3提供的注解可以逐步取代XML&#xff0c;例如使用Select注解直接编写SQL完成数据查询&#xff0c;使用SelectProvider高级注解还可以编写动态SQL&#xff0c;以应对复杂的业务需求。 一. 基础注解 MyBatis 主要提供了以下CRUD注解&#xff1a; Se…

Spring Cloud Alibaba Nacos Config - - - >@RefreshScope动态获取刷新后的配置内容

初学者不知道有没有这个疑惑&#xff1a;我明明已经在 SpringBoot 应用的 bootstrap.yml 配置文件中&#xff0c;通过 spring.cloud.nacos.config.refresh-enabledtrue 开启配置文件动态刷新了&#xff0c;为什么在 Controller 控制类中使用 Value 注解无法获取到配置文件修改后…

猿如意中的【取色器】效率工具详情介绍

目录 一、工具名称 二、下载安装渠道 2.1 什么是猿如意&#xff1f; 2.2 如何下载猿如意&#xff1f; 2.3 如何在猿如意中下载取色器&#xff1f; 三、取色器介绍 四、软件安装过程 五、软件界面 六、取色器功能特点介绍 七、取色器使用/体验感受 一、工具名称…

Typescript学习(第三弹)

泛型 定义 不预先确定的数据类型&#xff0c;具体的类型在使用的时候才确定&#xff0c;把泛型理解为代表类型的参数 泛型函数 泛型函数类型 泛型接口 引用泛型接口要指定一个类型&#xff0c;否则会报错 或者在泛型接口里指定一个默认类型 泛型类 泛型放在类的后面这样…

项目上线后我是如何通过慢查询和索引让系统快起来的

1、前言 最近对mysql的操作比较多一些&#xff0c;主要是项目上线以后&#xff0c;难免会有一些数据上的问题。开始的时候还主要由后端来处理&#xff0c;后面数据问题确实比较多&#xff0c;于是我就找后端要来服务器的账号密码&#xff0c;连上数据库顺便来看看数据的问题。…

C语言小项目-----员工管理系统

目录 项目要求&#xff1a; 考虑点&#xff1a; 实现过程 所用知识点 最终效果如下&#xff1a; 项目要求&#xff1a; 考虑点&#xff1a; 服务器端用select监听多个客户端&#xff0c;考虑点在于&#xff0c;公司内部的系统管理系统&#xff0c;不会有太多人每天都登陆&a…

【web前端开发】CSS的元素显示模式

前言 元素的显示模式可以更好的帮助我们布局页面,了解元素的显示模式,可以让我们布局页面时更加简单清晰 什么是元素显示模式 元素显示模式就是元素(标签)以什么样的方式进行显示 HTML元素一般分为块元素和行内元素两种类型 以下是块级元素和行内元素在网页中的显示: 块元…

3.神经网络-深度学习入门

3.神经网络 深度学习入门 本文的文件和代码链接&#xff1a;github地址 1.激活函数 sigmoid h(x)11e−xh(x) \frac{1}{1 e^{-x}} h(x)1e−x1​ def sigmod(x):return 1 / (1 np.exp(-1 * x))ReLU h(x){x:x>00:x≤0h(x) \left\{ \begin{array}{lr} x & : x > …

CMake静态库和动态库构建实例

任务 建⽴⼀个静态库和动态库&#xff0c;提供 HelloFunc 函数供其他程序编程使⽤&#xff0c;HelloFunc 向终端输出 Hello World 字 符串。安装头⽂件与共享库。 构建过程 构建动态库 目录结构 jyhlinuxubuntu:~/share/makefile_cmake/cmake01$ tree . ├── build #在…

m基于多用户MIMO系统的分布式可重构注水算法的matlab仿真

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 在单用户MIMO场景中&#xff0c;空间复用技术能够带来高数据速率的传输&#xff0c;但是也需要一些前提条件&#xff0c;比如发射端的预编码或者接收端的信道估计与信号检测。然而&#xff0c;在…

java项目-第169期ssm二手交易平台网站-ssm毕业设计_计算机毕业设计

java项目-第169期ssm二手交易平台网站-ssm毕业设计 【源码请到下载专栏下载】 《ssm二手交易平台网站》 该项目分为3个角色&#xff0c;管理员、用户、商家。 用户可以浏览前台商品并且进行购买。在个人后台可以看到自己的商品。 商家可以对商品进行商品分类管理、商品信息管理…

React 学习笔记:组件通信

组件通信 组件为什么需要通信呢&#xff1f;这是因为组件是独立且封闭的单元&#xff0c;默认情况下&#xff0c;组件只能使用自己的数据&#xff0c;但是多个组件之间不可避免的要共享某些数据&#xff0c;为了实现这些功能&#xff0c;就需要打破组件的独立封闭性&#xff0…

深度学习入门(五十九)循环神经网络——通过时间反向传播

深度学习入门&#xff08;五十九&#xff09;循环神经网络——通过时间反向传播前言循环神经网络——通过时间反向传播教材1 循环神经网络的梯度分析1.1 完全计算1.2 截断时间步1.3 随机截断1.4 比较策略2 通过时间反向传播的细节3 小结前言 核心内容来自博客链接1博客连接2希…

基于java+springboot+vue+mysql的甜品蛋糕销售商城网站

项目介绍 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。人们生活水平的不断提高&#xff0c;日常生活中用户对网上蛋糕商城方面的要求也在不断提高&#xff0c;网上蛋糕商城得到广大用户的青睐&#xff0c;使得网上蛋糕商城的开发成为必需而且紧迫的事情。本系…

Docker笔记--使用数据卷实现容器与宿主机的数据交互

1--数据卷的介绍和作用 在 Docker 架构中&#xff0c;宿主机系统和容器之间不能直接传递数据&#xff0c;同时当容器被删除时&#xff0c;容器所有的数据都会被清除&#xff1b; 数据卷能够在宿主机与容器、容器与容器之间搭建数据传输和共享的通道&#xff0c;当容器内的目录与…