动态内存管理:学习笔记9

news2024/11/20 14:39:12

目录

一.前言

二.动态内存函数

1.malloc和free

2.calloc函数

3. realloc函数(动态内存空间调整函数)

情形一:扩容时,原内存地址处可以容纳调整后的动态内存

情形二:扩容时,原内存地址无法容纳调整后的动态内存

三.C/C++程序的内存开辟

四.柔性数组

柔性数组的优点:

代码段1(柔性数组实现动态数组) 

代码段2 (利用结构成员指针实现变长数组)

五.关于结构体成员访问的知识补充


一.前言

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

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

但是上述的开辟空间的方式有两个特点:
1. 开辟的空间大小是固定的
2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配,也就是说程序运行时,内存的分配已经完成了
然而,有时候我们需要申请的对象的内存空间的大小要在程序运行的时候才能确定下来,那上述编译时开辟空间的方式就不能满足我们的使用需求了。
于是,C语言提供了动态内存管理函数。

二.动态内存函数

1.malloc和free

malloc函数首部:void* malloc (size_t size);

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


1.如果内存开辟失败,则返回一个NULL指针因此malloc的返回值一定要做检查
函数返回值的类型是 void* ,函数调用者可以根据实际需求将返回值转换转换成某种类型的地址并将其存放到一个对应类型的指针变量中。(该地址在其指向的内存空间被释放前不能丢失,否则会造成内存泄漏)

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

free函数首部:void free (void* ptr);
free函数用来释放动态开辟的内存(释放掉ptr指向的地址处后一段连续的动态内存)。(不能用于释放非动态开辟的内存)
1.如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的
2.如果参数 ptr 是NULL指针,则函数什么事都不做。
3.malloc和free都声明在 stdlib.h 头文件中。

4.malloc,calloc,realloc开辟的空间都由free函数来释放

基本应用示例:


    int num = 0;
    scanf("%d", &num);
    int* ptr = NULL;
    ptr = (int*)malloc(num*sizeof(int));
    if(NULL != ptr)  //判断ptr指针是否为空
    {
        //使用该空间
     }
    free(ptr);       //释放ptr所指向的动态内存
    ptr = NULL;      
    return 0;

注意:

1.指针变量接收malloc的返回值后一定要判断一下其是否为空指针以确定堆区内存是否申请成功。

2.堆区内存使用完毕后一定要用free函数对其进行释放,并将指向该内存的指针变量赋空

3.ptr的值在后续使用中不要被修改,不然动态内存的维护和释放会变得很麻烦(ptr相当于是堆区数组的标识名一样的存在)

2.calloc函数

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

举个例子:

int *p = (int*)calloc(10, sizeof(int));
if(NULL != p)
{
    //使用空间
}
free(p);
p = NULL;

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

该函数的使用注意事项与malloc基本相同。 

3. realloc函数(动态内存空间调整函数)

函数首部:void* realloc (void* ptr, size_t size);

函数功能:

1.ptr 是要调整的内存地址(必须指向动态申请的堆区空间)
2.size 调整之后新大小
3.返回值为调整之后的内存起始位置。
4.这个函数在调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

5.realloc在调整内存空间(增大调整)的时候存在两种情况:

情形一:扩容时,原内存地址处可以容纳调整后的动态内存

该情形下要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化

情形二:扩容时,原内存地址无法容纳调整后的动态内存

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

基于上述两种可能情况的分析可以得知:realloc函数使用时,其返回值可能有三种情况

1.内存空间调整失败:返回原空间地址(原空间大小和内容不变)

2.内存空间调整成功:返回原空间地址

3.内存空间调整成功:返回新空间地址

函数调用示例:


    int *ptr = (int*)malloc(100);
    if(ptr != NULL)
    {
        //业务处理
    }
    else
    {
        exit(EXIT_FAILURE);
    }
//扩展容量

    int*p = NULL;
    p = realloc(ptr, 1000);
    if(p != NULL)
    {
        ptr = p;
    }
    //业务处理
    free(ptr);
    return 0;

注意:

realloc的返回值一定要先用一个中间变量来接收,判断该变量不为空指针后再将新地址赋给维护动态内存的ptr指针.如果realloc的返回值直接赋给ptr,那么realloc执行失败时返回空指针会导致原动态内存丢失泄漏。

动态开辟的空间使用完后一定要释放,并且正确释放,忘记释放不再使用的动态开辟的空间会造成内存泄漏.(内存泄漏指向操作系统申请的动态内存不再使用(或因地址丢失而无法使用),而不释放归还给操作系统,导致该段内存不被占有程序使用的同时也无法被其他程序使用)

三.C/C++程序的内存开辟

C/C++程序内存分配的几个区域:
1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等栈区的特点是在上面创建的变量出了作用域就销毁(作用域用代码段的花括号表示)
2. 堆区(heap):动态内存函数申请的就是堆区上的内存。
3. 数据段(静态区)(static)存放全局变量、静态数据。数据段的特点是在上面创建的变量,直到程序结束才销毁
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码 

四.柔性数组

 柔性数组是定义在结构体中的数组,其大小由动态内存分配来决定

typedef struct st_type
{
    int i;
    int a[0];//柔性数组成员
}type_a;

 有些编译器会报错无法编译可以改成:

typedef struct st_type
{
    int i;
    int a[];//柔性数组成员
}type_a;

关键点:

1.结构中的柔性数组成员前面必须至少一个其他成员(这些其他成员决定其类型大小)
2.sizeof(type_a)返回值是除柔性数组外其他成员所占的内存(考虑内存对齐)
3.包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于
sizeof(type_a),以适应柔性数组的预期大小.

实例:

typedef struct st_type
{
    int i;
    int a[0];                  //柔性数组成员
}type_a;

printf("%d\n", sizeof(type_a));//输出的是4


int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理

free(p);



含柔性数组的结构体的成员内存分布(包括柔性数组成员)遵循内存对齐规则。

柔性数组的优点:

代码段1(柔性数组实现动态数组) 

typedef struct st_type
{
    int i;
    int a[0];//柔性数组成员
}type_a;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int))
free(p);
p=NULL;

 

代码段2 (利用结构成员指针实现变长数组)

typedef struct st_type
{
    int i;
    int *p_a;
}type_a;
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));      堆区上的数组用堆区上的结构体来维护
free(p->p_a);                                  释放时必须先释放数组再释放结构体,否则会导致数 
                                               组的地址丢失而造成内存泄漏
p->p_a = NULL;                                            
free(p); 
p = NULL;

 

上述两种动态数组的定义方式的主要差别在于:

1.代码段1的方法只需要一次动态内存分配而代码段2的方法则需要两次动态内存分配。多次动态内存分配会导致堆区上的被占用空间的分布变得零散内存碎片增多,内存利用率降低。而且不连续的内存分布会让寻址效率有所降低。

 2.代码段2中的方法在使用完数组后要进行两次动态内存释放,并且结构体和其维护的数组两者必须先释放数组再释放结构体,因此代码段2的方法编码隐患更多。

五.关于结构体成员访问的知识补充

 内容参考来自大神陈晧的一篇博客C语言结构体里的成员数组和指针 | 酷 壳 - CoolShell

阅读小笔记:

所谓变量,其实是内存地址的一个抽像名字罢了在静态编译的程序中,所有的变量名都会在编译时被转成内存地址.

现给出一个代码段:

struct test
{
    int i;
    short c;
    char *p;
};
int main()
{
    struct test *pt=NULL;
    return 0;
}

启动调试运行代码:

调试过程中我们在监视窗口观察一下&(pt->i),&(pt->c),&(pt->p)

可以看到(64位机器下)

&(pt->i)=0x0000000000000000;

&(pt->c)=0x0000000000000004;

&(pt->p)=0x0000000000000008;

如果把&(pt->i),&(pt->c),&(pt->p)换种写法,写成&((*pt).i),&((*pt).c),&((*pt).p)结果也是一样的:

&((*pt).i)=0x0000000000000000;

&((*pt).c)=0x0000000000000004;

&((*pt).p)=0x0000000000000008;

那么上面的0x00,0x04和0x08是怎么产生的呢?


这就涉及到对结构体成员访问操作符的更深层次的理解了:

当我们创建一个结构类型时,C的编译器会保存该结构类型中各成员相对于结构体实例(假设存在)首地址的偏移量(遵循内存对齐原则)当我们访问该结构体某成员时其实就是对 (该结构体首地址 + 某成员偏移量)这个地址值进行解引用。


在上述案例中pt中存放了NULL(也就是0)

当我们使用结构体成员访问操作符时:

比如 pt->c 这句代码实质上相当于 *(pt + c的成员偏移量)

那么 &(pt->c)实质上就相当于(pt+c的成员偏移量),而pt为0,c的成员偏移量为4

这就是&((*pt).c)=0x0000000000000004结果的来源,另外两个也是同理(i的成员偏移量为0,p的成员偏移量为8)

该知识点来整理自大神陈晧的一篇博客C语言结构体里的成员数组和指针 | 酷 壳 - CoolShell

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

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

相关文章

MATLAB实现费诺编码的计算与分析

一、实验目的 1、理解霍费诺编码的原理。 2、掌握费诺编码的方法和步骤。 3、熟悉费诺编码的效率。 4、本实验用Matlab语言编程实现费诺(Fano)编码。 二、实验环境 windows XP,MATLAB 7 三、实验原理 费诺编码算法如下:在信源…

构建前端项目

1.使用vite构建vue项目 vite构建vue项目,输入以下命令: npm init vitelatest接着按照提示的命令选择项目的名称、框架、语言。接着项目就构建完成了。 接着将构建好的项目:vite-demo拖入vsCode里面,在package.json中可以看到项…

redis基础命令使用

目录 Redis redis存储结构(KV) String string类型介绍 string类型数据的基础操作 string类型数据的扩展操作 List list类型介绍 list类型数据基本操作 list类型数据扩展操作 hash hash类型介绍 hash类型数据的基本操作 hash类型数据扩展操…

傅里叶变换

傅里叶变换 傅里叶变换常用的三个函数 函数一: numpy.fft.fft2: 复数数组 函数二: numpy.fft.fftshift: 将零频率分量移动到频谱中心 函数三: 20*np.log(np.abs(fshift)) 设置频谱的范围 import cv2 import numpy as np import matplotlib.pyplot as pltdef test_1():img cv2…

再说多线程(三)——Mutex类

1.引子在前面2节,我们已经讨论了Lock语句和Monitor类,Locks 和 Monitors 确保 InProcess 线程的线程安全,即由应用程序本身生成的线程,即内部线程。但是,如果线程来自 OutProcess,即来自外部应用程序&#…

Java 诊断利器 Arthas monitor/watch/trace命令

一、监控相关命令介绍 二、监控相关命令 2.1、运行Demo 2.2、monitor 命令 2.2.1、方法监控 2.3、watch 命令 (重要) 2.3.1、观察函数调用返回时的参数、this 对象和返回值 2.3.2、查看函数调用的入参和返回值 2.3.3、深度遍历 x 说明 2.3.4、查…

检验仪器控制怎么停止的

之前介绍仪器控制启动是按维护的调用M和仪器ID组串直接j启动进程,进程在调用Start启动TCP。 组装执行M串用j启动进程 启动TCP通道,成功之后就到f的死循环了 死循环这里容易有个误解,以为Stop是停止仪器接口的。其实这个Stop是判断要不要…

一灯大师,基于imx6ull点亮LED灯

一.imx6ull GPIO原理1. STM32 GPIO回顾我们一般拿到一款全新的芯片,第一个要做的事情的就是驱动其 GPIO,控制其 GPIO 输出高低电平,我们学习 I.MX6U 也一样的,先来学习一下 I.MX6U 的 GPIO。在学习 I.MX6U的 GPIO 之前&#xff0c…

Spark WordCount 案例

文章目录Spark WordCount 案例1、程序连接 Spark2、WordCount 案例示例3、复杂版 WordCount4、Spark 框架WordcountSpark WordCount 案例 1、程序连接 Spark 首先这个Scala spark程序和spark的链接,跟sql编程类似。首先new 一个新的val context SparkContext()对…

谷粒商城-高级篇-Day10-ElasticSearch

初步检索 1、_cat GET /_cat/nodes:查看所有节点 GET/_cat/health:查看es健康状况 GET/_cat/master:查看主节点 GET/_cat/indices:查看所有索引–相当于查询所有数据库 2、索引一个文档 put:http://192.168.205.128:9200/customer/external/1 {"name&qu…

Qt之加载百度离线地图(WebKit和WebEngine)

最近翻看进年前写了一篇关于百度离线地图的博客:Qt加载百度离线地图,发现存在很多问题,比如不能加载折线等图形覆盖物;只支持QtWebKit,不支持QtWebEngine。 之前做项目需要在百度离线地图上绘制Mesh拓扑图,必须添加折线覆盖物,使用的是百度离线地图API V2.1,满足需求。…

Java注解详解

什么是注解 ​ 用一个词就可以描述注解,那就是元数据,即一种描述数据的数据。所以,可以说注解就是源代码的元数据 元注解 JDK1.5之后内部提供的注解: Deprecated 意思是“废弃的,过时的”Override 意思是“重写、覆…

算法训练营 day18 二叉树 找树左下角的值 路径总和 从中序与后序遍历构建二叉树

算法训练营 day18 二叉树 找树左下角的值 路径总和 从中序与后序遍历构建二叉树 找树的左下角 513. 找树左下角的值 - 力扣(LeetCode) 给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至少有一个节…

Java --- JUC之原子类

目录​​​​​​​ 一、基本类型原子类 二、数组类型原子类 三、引用类型原子类 四、对象的属性修改类型原子类 五、原子操作增强类 5.1、高性能热点商品应用 5.2、LongAdder架构图 5.3、源码分析 一、基本类型原子类 public class AtomicTest1 {public static final…

canvas:基础知识【直线和矩形】

canvas,就是画布,是HTML5和核心技术之一,结合JavaScript,可以绘制各种各样的图形,比如矩形、曲线、圆形等等。另外,canvas可以绘制图表、动画效果、游戏开发。 基本图形汇中有直线和曲线。常见的直线图形是…

arduino rc522模块使用

rfid IC卡 先了解IC卡一些前置知识。 首先我们会有一张ic卡(M1类型IC卡,一般买到的都是1K存储空间),在rc522代码中会出现这个,就是对IC卡进行检查PICC_TYPE_MIFARE_4K和PICC_TYPE_MIFARE_1K就是一种卡片类型不同大小…

零基础学MySQL(二)-- 表的创建,修改,删除

文章目录🎈一、创建表1️⃣基本语法2️⃣入门案例🎆二、MySQL常用数据类型1️⃣数值型(整型)默认有符号2️⃣数值型(bit)3️⃣数值型(浮点型)默认有符号4️⃣字符串的基本使用5️⃣字…

1584_AURIX_TC275_SMU的调试以及部分寄存器

全部学习汇总: GreyZhang/g_TC275: happy hacking for TC275! (github.com) 前面学习的过程中,突然间减速了不少。但是为了保证学习的推进,还是得有每天的稳定输出。我的策略是多看,多处理,之后每天整理10页标注的文档…

设计模式相关内容介绍

1.学习设计模式好处 提高编程能力、思维能力、设计能力程序设计更加标准化、代码编制更加工程化,软件开发效率大大提高,缩短项目周期设计的代码可重用性高、可读性强、可靠性高、 灵活性好、可维护性强 2.设计模式分类 创建型模式 提供创建对象的机制…

一文读懂工业级交换机的规范使用

工业交换机具备电信级特性特点,可承受严苛的工作环境,产品种类丰富多彩,交换机配置灵便,可以满足各类工业应用的应用标准。那么,大家使用工业级交换机的过程当中应该如何规范使用呢? 工业级交换机其实质运…