深入了解C语言的动态内存管理
作者:熬夜磕代码丶 发布时间:2023-09-19 23:46:11
一、为什么会存在动态内存
int data=20;//在栈空间上开辟4个字节空间
char ch[5]={0};//在栈开辟5个字节连续空间
上面展示的即为我们正常开辟固定的内存空间,它有两个方面的特点
1.内存空间所占大小是固定的,不能改变的。
2.创建数组时,必须指明长度大小,在编译时内存进行分配。
很显然静态分配内存分配在一些场景,就暴露出它的弊端。如果在开发之前,我们不知道空间的需求,我们有时只有在程序运行的时候才能知道自己所需要空间大小,这时候我们只能使用动态分配内存了。
二、动态内存函数
1.malloc和free
malloc函数的参数只有一个size_t size,向内存申请一块连续可用的空间,有几点需要注意
1.如果开辟成功的话,返回指向开辟好空间的指针
2.如果开辟失败的话,则返回NULL,因此每次开辟空间之后,都要进行检查
3.malloc函数未定义返回类型,一切由使用者自己使用
4.需引用stdlib.h头文件
free函数是和malloc配套使用的,每次在堆开辟动态空间后,程序结束之前,必须进行空间释放,不然会出现动态空间泄露,在使用free时,仍需要注意几点
1.如果指针指向的空间不是动态开辟的,不能用free进行释放
2.如果指针指向的是null指针,则free函数什么事都不做
3.free不能多次使用
4.需引用stdlib.h头文件
代码如下(示例):
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* src = NULL;
src = (int*)malloc(40);//开辟40字节动态内存
if (src == NULL)
{
printf("%s", strerror(errno));
return 1;
}
free(src);//进行动态内存释放
src = NULL;
return 0;
}
相信有人会问,不是已经对动态内存进行释放,为什么还要令指针等于NULL,我们调试一把。
这里我们可以发现,虽然动态内存进行free释放,但指针仍然指向被释放的动态内存的地址,如果不置空,就会造成野指针,非法访问的问题。
2.calloc
calloc和malloc最大的区别就是,malloc只负责对内存进行动态开辟,但calloc不仅开辟,还进行初始化。
代码如下(示例):
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* src = (int*)calloc(10, sizeof(int));
if (src == NULL)
{
printf("%s", strerror(errno));
return 1;
}
free(src);//进行动态内存释放
src = NULL;
return 0;
}
我们调试一把可以发现,calloc在开辟空间时同时进行了初始化。所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。
3.realloc
当我们一次开辟动态内存不够大的时候,realloc让动态内存更加的灵活。realloc几个参数:
1.第一个参数为要调整内存的地址
2.调整后大小
3.调整后内存的起始位置
为什么还要返回调整后内存的地址,不是直接就开辟好了吗?其实reallloc函数在开辟时有以下两种情况:
1.原来的内存之后空间是足够的,则直接开辟
2.原来的内存之后空间不够用。
我们画图刨析一下
情况1:直接追加空间,原来数据不变
情况2:没有足够的空间,在堆上找一个大小合适的连续空间。所以函数返回的是一个新的内存地址。
代码如下(示例)
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
int* src = NULL;
src = (int*)malloc(40);//开辟40字节动态内存
if (src == NULL)
{
printf("%s", strerror(errno));
return 1;
}
src = realloc(src, 80);
if (src == NULL)
{
printf("%s", strerror(errno));
return 1;
}
free(src);//进行动态内存释放
src = NULL;
return 0;
}
三、动态内存函数常见错误
1.动态内存越界访问
void test1()
{
int* src = (int*)malloc(20);
if (NULL == src)
{
return 1;
}
int i = 0;
for (i = 0; i < 6; i++)
{
(*src+i)=i
}
free(src);
src = NULL;
}
在这里我们malloc只开辟了20个字节,但(*src+5)造成了越界访问
2.对NULL指针进行解引用操作
void test2()
{
int* src = (int*)malloc(INT_MAX);//此处INT_MAX为int的最大值
*src = 10;//如果src是NULL时,无法解引用
free(src);
return 0;
}
这里未对开辟的动态内存空间进行是否为空的判断,当为空时,解引用就会出现错误。
3.使用free释放一块动态开辟内存的一部分
void test3()
{
int* src = (int*)malloc(40);
int i=0;
for(i=0;i<6;i++)
{
*(src+i)=i;
src++;
}
free(src);//此时src不指向起始位置
}
因为指针指向的地址发生变化,不在指向起始未知,进行free释放是非常危险的。
4.对静态内存进行free释放
void test4()
{
int a = 20;
int* src = &a;
free(src);
}
5.对同一内存空间多次释放
void test5()
{
int* src = (int*)malloc(40);
free(src);
free(src);//多次释放
}
第一个free已经将堆空间的动态内存进行释放,此时src已经是一个野指针,在进行释放是十分危险的。
6.动态开辟空间忘记释放
void test6()
{
int* src = (int*)malloc(40);
if (src != NULL)
{
}
while (1);
}
在开辟动态内存之后,一直进行while循环,为进行free释放,会造成内存泄漏。
四、经典笔试题
1.笔试1
void test(char* src)
{
src = (char*)malloc(30);
}
int main()
{
char* src = NULL;
test(src);
strcpy(src, "wo yao jin da chang");
printf(src);
free(src);
}
这里会输出wo yao jin da chang 吗?
这里很明显,src仍然是NULL,所以无法输出wo yao jin da chang
2.笔试2
char* test()
{
char arr[] = "wo yao jin da chang";
return arr;
}
int main()
{
char* src = NULL;
src = tset();
printf(src);
return 0;
}
这里会输出wo yao jin da chang 吗?
这里test函数确实把字符串地址传给了src,但是字符串是局部变量,当函数执行完之后,就销毁了,所以src输出的内容是随机的。
3.笔试3
void test()
{
char* src = (char*)malloc(50);
if (src != NULL)
{
strcpy(src, "wo yao jin da chang");
}
free(src);
if (src != NULL)
{
strcpy(src, "taijuanlebujinle");
printf(src);
}
}
这里会输出taijuanlebujinle 吗?
这里对动态内存释放后,继续进行赋值,对野指针进行了访问是错误的。
来源:https://blog.csdn.net/buhuisuanfa/article/details/125768729


猜你喜欢
- 1.Spring bean组件 ”默认为单例模式scope=“singleton, 运行JavaApplication容器启动时自动创建对象
- 本文实例讲述了Android编程开发中ListView的常见用法。分享给大家供大家参考,具体如下:一、ListView的使用步骤ListVi
- 比如我创建一个Kotlin Object类:ObjectMethodpackage com.baichuan.example.unit_te
- 一、线程的状态NEW: 安排了工作, 还未开始行动RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.BLOCKED: 这几
- 前言基于安卓平台的连续滚动图像组件ContinuousScrollableImageView(https://github.com/Cutt
- mybatis的环境搭建:1、创建maven工程并且导入坐标:即我们需要在pop.xml文件中添加我们需要的依赖具体方法:搜索maven中央
- 简要介绍Retrofit是当前应用非常广泛的网络请求框架,通常结合RxJava来进行网络请求,本文将展示一个采用RxJava+Retrofi
- 1. 栈1.1 概念栈:是一种特殊的线性表,其只允许在固定的一端进行插入和删除元素操作。特点:栈中的数据元素遵循先进后出的原则,但要注意进的
- 1.过滤器 (Filter)过滤器的配置比较简单,直接实现Filter 接口即可,也可以通过@WebFilter注解实现对特定URL拦截,看
- SpringBoot默认的存放静态资源文件的位置是:注:SpringBoot中的src/main/resources/资源文件夹对应clas
- 【开发环境】物理机版本:Win7旗舰版(64位)Android Studio版本:2.1正式版【Android Studio的优势】•基于G
- 本文实例讲述了WinForm判断关闭事件来源于用户点击右上角“关闭”按钮的方法。分享给大家供大家参考。具体如下:protected over
- 详解房卡麻将分析系列 "牌局回放" 之 播放处理 昨天红孩儿给大伙讲了讲”牌局回放“的数据记录处
- 前言Java中URL传中文时乱码的问题相信不少朋友都遇到过,最近就遇到一个问题,就是在Action当中把一条中文信息绑定在URL的后面,Ac
- 一. 思路今天接到个小任务,让把json文件转换成excel文件,按照列展开.思路:既然json已经都已经是现成的,那直接将jso
- 本文实例为大家分享了Java实现串口通信的具体代码,供大家参考,具体内容如下1.介绍使用Java实现的串口通信程序,支持十六进制数据的发送与
- jdk中自带了很多工具可以用于性能分析,位于jdk的bin目录下,jvisualvm工具可以以图形化的方式更加直观的监控本地以及远程的jav
- 什么是静态单例模式?静态单例模式(Static Singleton Pattern)是我在实践中总结的模式,主要解决的问题是在预先知道某依赖
- Service概念及用途:Android中的服务,它与Activity不同,它是不能与用户交互的,不能自己启动的,运行在后台的程序,如果我们
- 通过spring注解开发,测试单例和多例区别1.注解和配置两种用法形式配置版:注解版:2.在spring框架中,scope作用域默认是单例的