【C语言】动态内存管理及其常见错误

文章目录

  • 1、前言:为什么要有动态内存分布
  • 2、三种动态内存的创建方式及其释放
    • 2.1 malloc
    • 2.2 calloc
    • 2.3 ralloc
    • 2.4 free
  • 3、常⻅的动态内存的错误
    • 3.1 对NULL指针的解引用操作
    • 3.2 对动态开辟空间的越界访问
    • 3.3 对非动态开辟内存使用free释放
    • 3.4 使⽤free释放⼀块动态开辟内存的⼀部分
    • 3.5 对同⼀块动态内存多次释放
    • 3.6 动态开辟内存忘记释放(内存泄漏)
  • 4、小结


1、前言:为什么要有动态内存分布

我们已经掌握的内存开辟方式有:

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

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

  • 空间开辟大小是固定的。
  • 数组在申明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整。

但是对于空间的需求,不仅仅是上述的情况。

有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了C语言引入了动态内存开辟,让程序员自己可以申请和释放空间,就比较灵活了。


2、三种动态内存的创建方式及其释放

2.1 malloc

void* malloc (size_t size);

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

  • 如果开辟成功,则返回一个指向开辟好空间的指针。
  • 如果开辟失败,则返回一个 NULL指针,因此malloc的返回值一定要做检查。
  • 返回值的类型是 void*,所以malloc函数并不知道开辟空间的类型,具体在使用的时- - 候使用者自己来决定。
  • 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。一般是会输出一个空指针。相关文章链接链接: link

2.2 calloc

void* calloc (size_t num, size_t size);

  • 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。
  • 与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0

举个例子

#include <stdio.h>
#include <stdlib.h>
int main() {
    int* p = (int*)calloc(10, sizeof(int));
    if (NULL != p) {
        int i = 0;
        for (i = 0; i < 10; i++) {
            printf("%d ", *(p + i));
        }
    }
    free(p);
    p = NULL;
    return 0;
}

输出结果

  • 1 0 0 0 0 0 0 0 0 0 0

所以如果我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。


2.3 ralloc

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

函数原型如下:

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

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

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

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

在这里插入图片描述

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

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

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

#include <stdio.h>
#include <stdlib.h>
int main() {
    int* ptr = (int*)malloc(100);
    if (ptr != NULL) {
//业务处理
    } else {
        return 1;
    }
//扩展容量
//代码1 - 直接将realloc的返回值放到ptr中
    ptr = (int*)realloc(ptr,
                        1000);//这样可以吗?(如果申请失败会如何?)
//代码2 - 先将realloc函数的返回值放在p中,不为NULL,在放ptr中
    int* p = NULL;
    p = realloc(ptr, 1000);
    if (p != NULL) {
        ptr = p;
    }
//业务处理
    free(ptr);
    return 0;
}

在我们一般的编写过程中是不建议使用代码1的,因为,可能存在上述的情况2,如果剩余内存空间不足了,可能会开辟失败,然后返回一个空指针,这样一来会导致之前地址的值都没了,所以建议是先创建一个新的指针,确保无误后在将这个地址转移。


2.4 free

C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下

void free (void* ptr);

free函数用来释放动态开辟的内存。

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

malloc、calloc、ralloc 和 free 都声明在 stdlib.h 头文件中。

释放动态内存这一举措是很有必要的,因为电脑存储分栈、堆和静态区,而每个区地内存大小是有上限的,而动态开辟的空间的又是储存在堆里,所以如果一直在开辟空间而不释放最终会导致内存泄露,整个堆就会爆满,最终导致程序奔溃。


3、常⻅的动态内存的错误

3.1 对NULL指针的解引用操作

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

这种情况下需要对新开辟的内存空间判断是否为空指针。

3.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.3 对非动态开辟内存使用free释放

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

free的使用对象仅限于动态内存对象,是不能所有内存空间都去释放的
如果释放了非动态内存空间,最终会导致整个编译器卡死,甚至无法调试

3.4 使⽤free释放⼀块动态开辟内存的⼀部分

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

不说了,直接卡死

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

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

不行,直接卡死,但是如果在第二次free前将p定义为空指针就不会出错。

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

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

一开始不会有什么问题,但是时间一长,当堆全部使用完后,就会导致程序奔溃,无法继续运行


4、小结

动态内存的开辟为程序员的代码编写增添了很大一片发挥空间,但是,这里面需要注意的不仅仅是动态内存的释放等常见问题,还有一些细小的问题需要不断注意,不然极有可能会发生内存泄漏等很难发现的细小问题,一旦代码过于庞大,调查起来就会十分的繁琐、困难,需要加以注意

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

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

相关文章

Linux网络协议栈从应用层到内核层②

文章目录 1、bind 源码剖析2、listen 源码剖析3、accept 源码剖析4、connect 源码剖析客户端调用connect成功&#xff0c;但三次握手并未完成&#xff0c;进程是如何阻塞自己客户端在connect时&#xff0c;如何选择源端口客户发送syn封包以及重传服务端收到syn封包&#xff0c;…

echarts 折线图 数据点过密,显示重叠该如何解决

echarts 折线图 数据点过密&#xff0c;显示重叠该如何解决 有这样一个图表&#xff0c;显示的数据比较多&#xff0c; 当把 label 显示出来的时候&#xff0c;这些 label 重叠了&#xff0c;我想让它间隔一下。 结果是这样的&#xff1a; 只需要在 label.formatter 上处理 …

Linux课程____Samba文件共享服务

一、 Samba服务基础 SMB协议&#xff0c;服务消息块 CIFS协议&#xff0c;通用互联网文件系统 1.Samba 服务器的主要程序 smbd:提供对服务器中文件、打印资源的共享访问 nmbd:提供基于 NetBlOS 主机名称的解析 2.目录文件 /etc/samba/smb.conf 检查工具&#xff1a;test…

408学习笔记-17-C-C/C++中程序内存区域划分

C/C中程序内存区域划分 C/C程序内存分配的几个区域&#xff1a; 1、栈区(stack)&#xff1a;在执行函数时&#xff0c;函数内局部变量的存储单元都可以在栈上创建&#xff0c;函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中&#xff0c;效率很高…

前端学习笔记 | Node.js

一、Node.js入门 1、什么是Node.js 定义&#xff1a;是跨平台JS运行环境&#xff08;可以独立执行JS的环境&#xff09;作用&#xff1a; 编写数据接口&#xff0c;提供网页资源功能等等前端工程化&#xff1a;为后续学Vue和React等框架做铺垫 2、Node.js为何能执行JS&#xff…

【Java反序列化】CommonsCollections-CC1链分析

前言 好几天没发博文了&#xff0c;偷偷憋了个大的——CC1链分析&#xff0c;手撸了一遍代码。虽然说&#xff0c;这个链很老了&#xff0c;但还是花费了我一段时间去消化吸收&#xff0c;那么接下来&#xff0c;我会简洁的介绍下整个链的利用过程&#xff0c;还有哪些不理解的…

2核4G服务器阿里云性能测评和优惠价格表

阿里云2核4G服务器租用优惠价格&#xff0c;轻量2核4G服务器165元一年、u1服务器2核4G5M带宽199元一年、云服务器e实例30元3个月&#xff0c;活动链接 aliyunfuwuqi.com/go/aliyun 活动链接如下图&#xff1a; 阿里云2核4G服务器优惠价格 轻量应用服务器2核2G4M带宽、60GB高效…

HTTP --- 上

目录 1. HTTP协议 2. 认识URL 2.1. URL中的四个主要字段 2.2. URL Encode && URL Decode 3. HTTP 协议格式 3.1. 快速构建 HTTP 请求和响应的报文格式 3.1.1. HTTP 请求格式 3.1.2. HTTP 响应格式 3.1.3. 关于 HTTP 请求 && 响应的宏观理解 3.2. 实现…

SpringBoot+Vue项目(后端项目搭建 + 添加家居)

文章目录 1.使用版本控制管理该项目1.创建远程仓库2.克隆到本地 2.后端项目环境搭建1.创建一个maven项目2.删除不必要的文件夹3.pom.xml文件引入依赖4.application.yml 配置数据源&#xff08;注意&#xff0c;数据库名还没写&#xff09;5.com/sun/furn/Application.java 编写…

2024年起重机司机(限桥式起重机)证考试题库及起重机司机(限桥式起重机)试题解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年起重机司机(限桥式起重机)证考试题库及起重机司机(限桥式起重机)试题解析是安全生产模拟考试一点通结合&#xff08;安监局&#xff09;特种作业人员操作证考试大纲和&#xff08;质检局&#xff09;特种设备作…

【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器

【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器 文章目录 【OpenSSH】Windows系统使用OpenSSH搭建SFTP服务器一、环境说明二、安装配置步骤1.下载完成后&#xff0c;传至服务器或者本机并解压至C:/Program Files/目录下2.打开PowerShell终端3.进入到包含ssh可执行exe文件的文…

每日五道java面试题之springboot篇(一)

目录&#xff1a; 第一题. 什么是 Spring Boot&#xff1f;第二题. Spring Boot 有哪些优点&#xff1f;第三题. Spring Boot 的核心注解是哪个&#xff1f;它主要由哪几个注解组成的&#xff1f;第四题. 什么是 JavaConfig&#xff1f;第五题. Spring Boot 自动配置原理是什么…

全流程ArcGIS Pro技术应用

GIS是利用电子计算机及其外部设备&#xff0c;采集、存储、分析和描述整个或部分地球表面与空间信息系统。简单地讲&#xff0c;它是在一定的地域内&#xff0c;将地理空间信息和 一些与该地域地理信息相关的属性信息结合起来&#xff0c;达到对地理和属性信息的综合管理。GIS的…

小本创业项目哪个比较赚钱?户用光伏加盟如何

随着不可再生能源的不断消耗和环保问题的日益重视&#xff0c;世界各国对新能源的需求都迫在眉睫。而光伏发电在新能源又有独特的优势。而光伏既改变了人们的生活方式也推动了社会的进步和市场经济的发展。那想做小本游戏创业的话可以选择户用光伏加盟吗&#xff1f; 首先要确认…

【微服务】Feign远程调用

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;微服务 ⛺️稳中求进&#xff0c;晒太阳 先来看我们以前利用RestTemplate发起远程调用的代码&#xff1a; 存在下面的问题&#xff1a;代码可读性差&#xff0c;编程体验不统一参数复杂URL…

332. 重新安排行程(力扣LeetCode)

文章目录 332. 重新安排行程题目描述思路如何理解死循环该记录映射关系 dfs&#xff08;回溯法&#xff09;代码 332. 重新安排行程 题目描述 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排…

6.2 感知器

感知器的概念由 罗森布拉特弗兰克 在1957年提出&#xff0c;它是一种监督训练的二元分类器。 一、单层感知器 考虑一个只包含一个神经元的神经网络。 这个神经元有两个输入x1&#xff0c;x2&#xff0c;权值为w1&#xff0c;w2。其激活函数为符号函数&#xff1a; 根据感知器…

Git浅谈配置文件和免密登录

一、文章内容 简述git三种配置ssh免密登录以及遇见的问题git可忽略文件git remote 相关操作 二、Git三种配置 项目配置文件(局部)&#xff1a;项目路径/.git/config 文件 git config --local user.name name git config --local user.email 123qq.cc全局配置文(所有用户): …

C++位运算符(<<,>>,|,^,)

简介 位运算符作用于整数类型的运算对象&#xff0c;并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能&#xff0c;一种名为bitset的标准库类型也可以表示任意大小的二进制集合&#xff0c;所以位运算符同样可以用于bitset类型。 如果运算对象是“小…

使用echart绘制拓扑图,树类型,自定义tooltip和label样式,可收缩

效果如图&#xff1a; 鼠标移上显示 vue3 - ts文件 “echarts”: “^5.4.3”, import { EChartsOption } from echarts import * as echarts from echarts/core import { TooltipComponent } from echarts/components import { TreeChart } from echarts/charts import { C…
最新文章