【C语言】动态内存管理(malloc,free,calloc,realloc)-- 详解

一、动态内存分配

定义动态内存分配 (Dynamic Memory Allocation) 就是指在程序执行的过程中,动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样,需要预先分配存储空间,而是由系统根据程序的需要即时分配,且分配的大小就是程序要求的大小

目前掌握的两种开辟内存的方式:

// 在栈空间上开辟四个字节
int val = 20; 

// 在栈空间上开辟10个字节的连续空间
char arr[10] = {0}; 
上述的开辟空间的方式有两个特点:

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

存在动态内存开辟的原因:对于空间的需求,不仅仅是上述的情况。有时我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了,这时我们就需要动态内存开辟来解决问题。


二、动态内存函数的介绍

1、malloc 函数

void* malloc(size_t size);
  • malloc 是 C 语言提供的一个动态内存开辟的函数,该函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
  1. 如果开辟成功,则返回一个指向开辟好空间的指针
  2. 如果开辟失败,则返回一个 NULL 指针,因此 malloc 的返回值一定要做检查
  3. 返回值的类型是 void* ,所以 malloc 函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
  4. 如果参数 size 为 0malloc 的行为是标准是未定义的,取决于编译器。

cplusplus.com/reference/cstdlib/malloc/?kw=malloc


2、free 函数

void free (void* ptr);
  • free 函数用来释放动态开辟的内存。
  1. 如果参数 ptr 指向的空间不是动态开辟的,那 free 函数的行为是未定义的。
  2. 如果参数 ptr 是 NULL 指针,则 free 函数将不会执行任何动作。

注意

  1. 使用完之后一定要记得使用 free 函数释放所开辟的内存空间。
  2. 使用指针指向动态开辟的内存,使用完并 free 之后一定要记得将其置为空指针

cplusplus.com/reference/cstdlib/free/


【演示】

#include <stdio.h>
#include <stdlib.h>
 
int main() 
{
    int arr[10]; // 开辟10个整型空间
 
    int* p = (int*)malloc(10*sizeof(int)); // 动态开辟10个大小为int的空间
 
    if (p == NULL) // 判断p指针是否为空
    {
        perror("main"); // main: 错误信息
        return 0;
    }

    for (int i = 0; i < 10; i++)
    {
        *(p + i) = i;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", p[i]);
    }
 
    // 回收空间
    free(p); // 释放p指针所指向的动态内存
    p = NULL; // 需要手动置为空指针
 
    return 0;
}

【1】为什么 malloc 前面要进行强制类型转换呢?

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

为了和 int* p 类型相呼应,所以要进行强制类型转换。如果把强制转换删掉,其实也不会有什么问题。但是因为有些编译器要求强转,所以最好进行一下强转,避免不必要的麻烦。


【2】为什么 free 之后,一定要把 p 置为空指针?

因为 free 之后那块开辟的内存空间已经不存在了,它的功能只是把开辟的空间回收掉,但是 p 仍然还指向那块内存空间的起始位置,这并不合理。所以需要使用 p = NULL 把它置成空指针。


3、calloc 函数

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

cplusplus.com/reference/cstdlib/calloc/

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int* p = (int*)calloc(10, sizeof(int)); // 开辟10个大小为int的空间,40
    if (p == NULL)
    {
        return 1;
    }
    for (int i = 0; i < 10; i++)
    {
        printf("%d ", *(p + i));
    }

    free(p);
    p = NULL;
 
    return 0;
}

 

结论:说明 calloc 会对内存进行初始化,把空间的每个字节初始化为 0 。如果我们对于申请的内存空间的内容,要求其初始化,我们就可以使用 calloc 函数来轻松实现。


4、realloc 函数

void* realloc (void* ptr, size_t size);
  • realloc 函数,让动态内存管理更加灵活。用于重新调整之前调用 malloccalloc 所分配的 ptr 所指向的内存块的大小,可以对动态开辟的内存进行大小的调整

  1. ptr 为指针要调整的内存地址
  2. size 调整之后的新大小
  3. 返回值为调整之后的内存起始位置,请求失败则返回空指针
  4. realloc 函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

  • realloc 在调整内存空间的是存在三种情况:
  1. 原有空间之后有足够大的空间。
  2. 原有空间之后没有足够大的空间。
  3. realloc 有可能找不到合适的空间来调整大小。

第三种情况,如果 realloc 找不到合适的空间,就会返回空指针。如果想让它增容,它却存在返回空指针的危险,怎么办?

不要拿指针直接接收 realloc,可以使用临时指针判断一下。

#include <stdio.h>
#include <stdlib.h>
 
int main() 
{
    int* p = (int*)calloc(10, sizeof(int));
    if (p == NULL)
    {
        perror("main");
        return 1;
    }
    for (int i = 0; i < 10; i++)
    {
        *(p + i)  = 5;
    }

    // 此时,这里需要 p 指向的空间更大,需要 20 个int的空间
    int* ptmp = (int*)realloc(p, 20*sizeof(int));

    // 如果ptmp不等于空指针,再把p交付给它
    if (ptmp != NULL)
    {
        p = ptmp;
    }
 
    free(p);
    p = NULL;
}

当要调整的内存地址为 NULL 时,realloc 的功能相当于 malloc。 

int* p = (int*)realloc(NULL, 40); // 这里功能类似于malloc,就是直接在堆区开辟40个字节

三、常见的动态内存错误

1、NULL指针的解引用操作

// error - 错误演示
#include <stdlib.h>
#include <stdio.h>

void test()
{
    int* p = (int*)malloc(9999999999);
    *p = 20; // 对空指针进行解引用操作,非法访问内存
    free(p);
    return 0;
}
// 正确代码
#include <stdlib.h>
#include <stdio.h>
 
int main()
{
    int* p = (int*)malloc(9999999999);

    if (p == NULL) // 对malloc函数的返回值做判空处理
    {
        perror("main")
        return 1;
    }
    for (int i = 0; i < 10; i++)
    {
        *(p + i) = i; // 对空指针进行解引用操作,非法访问内存
    }
 
    return 0;
}

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

堆上开辟的空间是有范围的。

// error
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int* p = (int*)malloc(10*sizeof(int)); // 申请10个整型的空间
    if (p == NULL)
    {
        perror("main");
        return 1;
    }
    for (int i = 0; i < 40; i++) // 越界访问 - 指针p只管理10个整型的空间,根本无法访问40个
    {
        *(p + i) = i;
    }
 
    free(p);
    p = NULL;
 
    return 0;
}

注意:为了防止越界访问,使用空间时一定要注意开辟的空间大小。 


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

#include <stdio.h>
#include <stdlib.h>
 
int main()
{
    int arr[10] = {0};
    int* p = arr;
 
    free(p); // 使用free释放非动态开辟的空间 - error
    p = NULL;
 
    return 0;   
}

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

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

        此时 free(p) 就会出问题,释放的是后面的空间。不能从一块动态开辟的内存空间的某一部分释放,必须从头开始释放!

        这么写会导致 p 只释放了后面的空间,并不知道这块空间的起始位置,这样会存在内存泄露的风险。

注意释放内存空间的时候一定要从头开始释放。


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

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

应该在第一次释放后紧接着将 p 置为空指针。


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

#include <stdio.h>
#include <stdlib.h>
 
void test()
{
    int* p = (int*)malloc(100);
    if (p == NULL)
    {
        return;
    }
    // 这里忘记释放了
}
 
int main()
{
    test();
    
    free(p); // 此时释放不了了,并不知道这块空间的起始位置在哪
    p = NULL;
}
忘记释放不再使用的动态开辟的空间会造成内存泄漏。
注意 动态开辟的空间一定要释放,并且正确释放。

C / C++ 中程序内存区域划分

C/C++程序内存分配的几个区域:
  1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。
  2. 堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由 OS 回收 。分配方式类似于链表。
  3. 数据段(静态区)(static):存放全局变量静态数据。程序结束后由系统释放。
  4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。

实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。但是被 static 修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以 生命周期变长

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

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

相关文章

十、pikachu之php反序列化

文章目录 1、php反序列化概述2、实战3、关于Magic function4、__wakeup()和destruct() 1、php反序列化概述 在理解这个漏洞前&#xff0c;首先搞清楚php中serialize()&#xff0c;unserialize()这两个函数。 &#xff08;1&#xff09;序列化serialize()&#xff1a;就是把一个…

基于Jenkins+Git+Ansible 发布PHP 项目-------从小白到大神之路之学习运维第88天

第四阶段提升 时 间&#xff1a;2023年8月25日 参加人&#xff1a;全班人员 内 容&#xff1a; 基于JenkinsGitAnsible 发布PHP 项目 目录 基于JenkinsGitAnsible 发布PHP 项目 一、部署PHP 运行环境 二、主机环境配置 三、Tomcat主机操作&#xff1a; 四、Jenkins主…

【golang】panic函数、recover函数以及defer语句

从panic被引发到程序终止运行的大致过程是什么&#xff1f; 大致过程&#xff1a; 某个函数中的某行代码有意无意地引发了一个panic。这时&#xff0c;初始的panic详情会被建立起来&#xff0c;并且该程序的控制权会立即从从行代码转移至调用其所属函数的那行代码上&#xff…

CentOS系统环境搭建(十七)——elasticsearch设置密码

centos系统环境搭建专栏&#x1f517;点击跳转 elasticsearch设置密码 没有密码是很不安全的一件事&#x1f62d; 文章目录 elasticsearch设置密码1.设置密码2.登录elasticsearch3.登录kibana4.登录elasticsearch-head 1.设置密码 关于Elasticsearch的安装请看CentOS系统环境搭…

SpringBootWeb案例 Part 4

3. 修改员工 需求&#xff1a;修改员工信息 在进行修改员工信息的时候&#xff0c;我们首先先要根据员工的ID查询员工的信息用于页面回显展示&#xff0c;然后用户修改员工数据之后&#xff0c;点击保存按钮&#xff0c;就可以将修改的数据提交到服务端&#xff0c;保存到数据…

【校招VIP】产品思维分析之面试新的功能点设计

考点介绍&#xff1a; 这种题型是面试里出现频度最高&#xff0c;也是难度最大的一种&#xff0c;需要面试者对产品本身的功能、扩展性以及行业都有一定的了解。而且分析时间较短&#xff0c;需要一定的产品能力和回答技巧。 『产品思维分析之面试新的功能点设计』相关题目及解…

java+springboot+vue儿童慈善捐赠管理系统的设计与实现8n9e4

针对用户需求开发与设计&#xff0c;该技术尤其在各行业领域发挥了巨大的作用&#xff0c;有效地促进了“爱相连”儿童慈善管理的发展。然而&#xff0c;由于用户量和需求量的增加&#xff0c;信息过载等问题暴露出来&#xff0c;为改善传统线下管理中的不足&#xff0c;本文将…

Docker搭建LNMP----(超详细)

目录 ​编辑 一、项目环境 1.1 所有安装包下载&#xff1a; 1.3 服务器环境 1.4任务需求 二、Ngin 2.1、建立工作目录 2.2 编写 Dockerfile 脚本 2.3准备 nginx.conf 配置文件 2.4生成镜像 2.5创建自定义网络 2.6启动镜像容器 2.7验证 nginx、 三、Mysql 3.1建立…

传智教育广州校区又又又举行校内招聘会,多名学员被广东民生在线教育招入麾下

数字经济的高速发展以及经济形势的逐渐回暖&#xff0c;带动了企业对数字人才的用人需求增加&#xff0c;近日&#xff0c;传智教育旗下高端IT教育品牌黑马程序员多个校区接到了企业上门招聘的需求&#xff0c;各分校区通过举行校内招聘会&#xff0c;为用人企业和学员搭建了人…

一文速学-让神经网络不再神秘,一天速学神经网络基础-激活函数(二)

前言 思索了很久到底要不要出深度学习内容&#xff0c;毕竟在数学建模专栏里边的机器学习内容还有一大半算法没有更新&#xff0c;很多坑都没有填满&#xff0c;而且现在深度学习的文章和学习课程都十分的多&#xff0c;我考虑了很久决定还是得出神经网络系列文章&#xff0c;…

实验二 tftp 服务器环境搭建

tftp 服务器环境搭建 tftp&#xff08;Trivial File Transfer Protocol&#xff09;即简单文件传输协议是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议&#xff0c;提供不复杂、开销不大的文件传输服务。端口号为69 【实验目的】 掌握 tftp 环境搭…

【目标检测】“复制-粘贴 copy-paste” 数据增强实现

文章目录 前言1. 效果展示代码说明3. 参考文档4. 不合适点 前言 本文来源论文《Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation》&#xff08;CVPR2020&#xff09;&#xff0c;对其数据增强方式进行实现。 论文地址&#xff1a;https:/…

MediaPlayer音频与视频的播放介绍

作者&#xff1a;向阳逐梦 Android多媒体中的——MediaPlayer&#xff0c;我们可以通过这个API来播放音频和视频该类是Androd多媒体框架中的一个重要组件&#xff0c;通过该类&#xff0c;我们可以以最小的步骤来获取&#xff0c;解码和播放音视频。 它支持三种不同的媒体来源…

Talk | 上海交通大学官同坤:识别任意文本,隐式注意力与字符间蒸馏在文本识别中的应用

本期为TechBeat人工智能社区第525期线上Talk&#xff01; 北京时间8月23日(周三)20:00&#xff0c;上海交通大学博士生—官同坤的Talk已准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “隐式注意力与字符间蒸馏在文本识别中的应用”&#xff0c;分享了识别…

使用docker-maven-plugin插件构建镜像并推送至私服Harbor

前言 如下所示&#xff0c;建议使用 Dockerfile Maven 插件&#xff0c;但该插件也停止维护更新了。因此先暂时使用docker-maven-plugin插件。 一、开启Docker服务器的远程访问 1.1 开启2375远程访问 默认的dokcer是不支持远程访问的&#xff0c;需要加点配置&#xff0c;开…

bh002- Blazor hybrid / Maui 保存设置快速教程

1. 建立工程 bh002_ORM 源码 2. 添加 nuget 包 <PackageReference Include"BootstrapBlazor.WebAPI" Version"7.*" /> <PackageReference Include"FreeSql" Version"*" /> <PackageReference Include"FreeSql.…

MyBatis分页插件PageHelper的使用及特殊字符的处理

目录 一、PageHelper简介 1.什么是分页 2.PageHelper是什么 3.使用PageHelper的优点 二、PageHelper插件的使用 原生limit查询 1. 导入pom依赖 2. Mybatis.cfg.xml 配置拦截器 3. 使用PageHelper进行分页 三、特殊字符的处理 1.SQL注入&#xff1a; 2.XML转义&#…

【Linux】【驱动】第一个相对完整的驱动编写

【Linux】【驱动】第一个相对完整的驱动编写 续1.驱动部分的代码2 app 代码3 操作相关的代码 续 这个章节会讲述去直接控制一个GPIO&#xff0c;高低电平。 因为linux不允许直接去操作寄存器&#xff0c;所以在操作寄存器的时候就需要使用到函数&#xff1a;ioremap 和iounma…

线性代数的学习和整理10:各种特殊类型的矩阵(草稿-----未完成 建设ing)

目录 1 图形化分类 1.1对称矩阵 1.2 梯形矩阵 1.3 三角矩阵 1.3.1 上三角矩阵 1.4 对角线矩阵 2 按各自功能分 2.1 等价矩阵 2.2 增广矩阵 2.3 伴随矩阵 2.4 正交矩阵 2.5 正交矩阵 2.6 相似矩阵 1 图形化分类 1.1对称矩阵 1.2 梯形矩阵 1.3 三角矩阵 1.3.1 上…

【ARM-Linux】项目,语音刷抖音项目

文章目录 所需器材装备操作SU-03T语音模块配置代码&#xff08;没有用wiring库&#xff0c;自己实现串口通信&#xff09;结束 所需器材 可以百度了解以下器材 orangepi-zero2全志开发板 su-03T语音识别模块 USB-TTL模块 一个安卓手机 一根可以传输的数据线 装备操作 安…