C语言动态内存管理(重点)

目录

1、为什么要有动态内存分配

2、malloc 和 free

2.1 malloc函数

2.2 free函数

3、calloc 和 realloc

3.1  calloc函数 

3.2  realloc 函数

3.3  realloc 和 malloc 区别

3.4  realloc 函数存在的问题

4、常见的动态内存的错误

5、动态内存经典笔试题分析

6、柔性数组 

6.1 补充 typedef 创建结构体

6.2 柔性数组的特点

6.3 柔性数组的使用

6.4 柔性数组的优势 

7、总结C/C++中程序内存区域划分


1、为什么要有动态内存分配

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

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

2、malloc 和 free

2.1 malloc函数

       malloc 函数是 C 语言中用于动态分配内存的函数之一,其声明在 <stdlib.h> 头文件中。该函数用于在程序运行时从堆(heap)中动态分配指定大小的内存空间,并返回一个指向该内存空间起始地址的指针。

void *malloc(size_t size);
  • size_t size:需要分配的内存空间大小,以字节为单位。

   malloc 函数返回一个 void 类型的指针,需要根据实际情况进行类型转换后使用。如果内存分配成功,则返回指向分配内存起始地址的指针;如果内存分配失败,则返回 NULL

2.2 free函数

   free 函数是 C 语言中用于释放动态分配内存的函数,其声明在 <stdlib.h> 头文件中。当程序不再需要动态分配的内存空间时,应该使用 free 函数将该内存空间释放,以便系统可以重新利用这部分内存。 

void free(void *ptr);
  • void *ptr:指向先前由 malloccalloc 或 realloc 返回的内存空间起始地址的指针。

       使用 free 函数后,内存空间将被标记为可用,并可以被后续的内存分配操作重新使用。但需要注意的是,尝试释放已经释放过的内存空间或者尝试释放静态分配的内存空间会导致未定义的行为,因此务必确保只对动态分配的内存空间使用 free 函数。

#include <stdio.h>
#include <stdlib.h>int main() 
{int *arr;int n = 5;// 动态分配包含5个整数的内存空间arr = (int *)malloc(n * sizeof(int));if (arr == NULL) {perror("malloc");return 1;}// 将数组元素初始化为 0for (int i = 0; i < n; i++) {arr[i] = 0;}// 打印数组元素for (int i = 0; i < n; i++) {printf("%d ", arr[i]);}// 释放动态分配的内存free(arr);arr=NULL;return 0;
}

注:在 free 释放开辟的动态内存后,原来指向该内存首地址的指针仍然指向该地址,为了避免该指针成为野指针,在 free 释放完开辟的动态内存后最好将指向该内存的指针设置为NULL;

    free(arr);arr=NULL;

3、calloc 和 realloc

3.1  calloc函数 

calloc 函数是 C 语言中用于动态分配内存并初始化为零的函数,其声明在 <stdlib.h> 头文件中。与 malloc 函数不同的是,calloc 函数在分配内存空间的同时会将其初始化为零,这是 callocmalloc 的主要区别之一。 

void *calloc(size_t num, size_t size);
  • size_t num:需要分配的元素个数。
  • size_t size:每个元素的大小,以字节为单位。

   calloc 函数会分配 num*size 个字节的内存空间,并将所有位初始化为零。如果内存分配成功,则返回指向分配内存起始地址的指针;如果内存分配失败,则返回 NULL

#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;
}
3.2  realloc 函数

   realloc 函数是 C 语言中用于重新分配动态分配内存空间的函数,其声明在 <stdlib.h> 头文件中。当需要调整先前分配的内存空间的大小时,可以使用 realloc 函数来实现。realloc 函数会尝试扩大或缩小先前分配的内存块,并保留原始内存块中的数据。 

void *realloc(void *ptr, size_t size);
  • void *ptr:指向先前由 malloccalloc 或 realloc 返回的内存空间起始地址的指针。
  • size_t size:重新分配后的内存空间大小,以字节为单位。

            如果重新分配成功,则返回指向新分配内存起始地址的指针;如果重新分配失败,则返回 NULL。值得注意的是,realloc 函数可能会将原内存块移动到新的位置,因此在调用 realloc 后应该谨慎处理原指针。

#include <stdio.h>
#include <stdlib.h>int main() 
{int *arr;int n = 5;// 动态分配包含5个整数的内存空间arr = (int *)malloc(n * sizeof(int));if (arr == NULL) {printf("内存分配失败\n");return 1;}// 重新分配为包含10个整数的内存空间arr = (int *)realloc(arr, 10 * sizeof(int));if (arr == NULL) {printf("内存重新分配失败\n");return 1;}// 打印数组元素for (int i = 0; i < 10; i++) {printf("%d ", arr[i]);}// 释放动态分配的内存free(arr);arr=NULL;return 0;
}
3.3  realloc 和 malloc 区别

        malloc返回类型是 void 型指针,再根据实际需求进行强制类型转换,那么根据两个函数的定义就可以发现,当 realloc 函数第一个参数是空指针时那么就和 malloc 函数功能相同:

int* p = (int*)realloc(NULL,40);
3.4  realloc 函数存在的问题

        3.2 部分的示例代码首先使用 malloc 分配了包含5个整数的内存空间,然后使用 realloc 函数将内存空间重新分配为包含10个整数的空间。但是存在一个问题: 若 realloc 函数重新分配失败怎么办?会返回NULL,那么想重新分配的指针 arr = NULL,其原来指向的内存也不存在了。 

realloc 函数在重新分配内存空间时可能会返回 NULL,主要是由于以下几种情况导致的:

  1. 内存分配失败:当系统无法满足请求的内存空间大小时,realloc 函数会失败并返回 NULL。这通常发生在内存不足或者系统资源受限的情况下。

  2. 内存空间连续性不足:如果原内存块之后没有足够的连续空间来扩展到请求的大小,realloc 也会失败并返回 NULL。这种情况下,系统无法在原地扩展内存空间,需要将原内存块移动到新的位置,如果新位置无法提供足够的连续空间也会导致失败。

  3. 其他系统限制:某些系统可能会施加其他限制,例如内存碎片化程度过高、操作系统限制等,都有可能导致 realloc 返回 NULL

情况1:

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

情况2: 

        realloc函数会在内存的堆区重新找一个空间(满足新的空间大小需求的),同时会把旧的数据拷贝到新的空间,然后释放旧的空间,同时返回新的空间的起始地址。

由于上述的两种情况,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;}else{perror("realloc");return 1;}free(ptr);return 0;
}

4、常见的动态内存的错误

(一) 对NULL指针的解引用操作

//可以先进行判断
#include<stdio.h>
#include<stdlib.h>
int main()
{int* p = (int*)malloc(100);if (p == NULL){perror("malloc");return 1;}*p = 20;free(p);return 0;
}

(二) 对动态开辟空间的越界访问

(三)对非动态开辟内存使用free释放

(四)使用free释放一块动态开辟内存的一部分

使用 free 释放空间时给的不是起始地址。

(五)对同一块动态内存多次释放

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

        如下情况,在 test 函数中申请了100个字节的空间,并定义了p指向了这个空间。当函数调用结束后,函数定义的局部变量 p 随之销毁,同时也没有变量保存这块空间的地址,申请的这块内存空间后续将无法使用,因为不知道这块空间的地址。

#include<stdio.h>
#include<stdlib.h>
void test()
{int* p = (int*)malloc(100);if (NULL != p){*p = 20;}
}
int main()
{test();while (1);
}

5、动态内存经典笔试题分析

题目一: 

#include<stdio.h>
#include<stdlib.h>
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;
}

        分析上述代码,当将一个指针作为参数传递给函数时,实际上传递的是指针变量所存储的地址值的副本,而不是实际指针变量本身。这意味着在函数内部对传递进来的指针进行修改时,只会影响到形式参数的副本,而不会影响到原始指针变量的值。                                                                      在 GetMemory 函数中,参数 p 被传递为指针的副本,而不是指针本身。所以函数内部对 p 进行的内存分配不会影响到 Test 函数中原始指针 str 的值。当 GetMemory 函数返回时,分配的内存空间将丢失,因为只是修改了传递进来的副本指针 p 的值,而原始指针 str 仍然是 NULL。因此,strcpyprintf 函数中使用的 str 指针仍然是 NULL,对空指针解引用就会导致程序的崩溃。

修改后代码如下:

 题目二:

        分析上述代码。在 GetMemory 函数中,它尝试返回一个指向局部变量 p 的指针。然而,一旦 GetMemory 函数执行完毕并返回,p 所在的内存空间将被释放,因为它是一个自动变量(在栈上分配)。因此,当 Test 函数尝试使用从 GetMemory 返回的指针时,实际上它指向的是一个无效的内存位置,这可能导致未定义的行为。

可以在GetMemory函数中定义char p[ ]前加static,这样当执行完函数后这块空间仍然不会释放:

6、柔性数组 

6.1 补充 typedef 创建结构体

        使用 typedef 可以将复杂的数据类型简化为一个更容易记忆和理解的别名,使得代码更加清晰,减少出错的可能性。在 C 语言中,typedef 经常与结构体、枚举等复杂数据类型一起使用,方便在程序中定义新的类型名称。

        在如下代码中,通过 typedefstruct st_type 定义的结构体类型命名为 type_a,这样以后可以直接使用 type_a 来声明结构体变量,而不需要每次都写完整的 struct st_type。这种方式使得代码更加简洁明了,提高了代码的可读性和可维护性。

#include <stdio.h>// 定义结构体 st_type
struct st_type 
{int i;char c;
};// 使用 typedef 创建结构体别名 type_a
typedef struct st_type type_a;int main() 
{// 声明一个结构体变量并赋初值type_a my_struct;my_struct.i = 10;my_struct.c = 'A';// 打印结构体变量的成员值printf("i: %d\n", my_struct.i);printf("c: %c\n", my_struct.c);return 0;
}
6.2 柔性数组的特点

        在 C 语言中,柔性数组(Flexible Array Member)是一种特殊的结构体成员,它允许在结构体的末尾定义一个长度不确定的数组。柔性数组通常用于动态分配内存,使得结构体可以容纳可变长度的数据。(1)在结构体中;(2)最后一个成员;(3)未知大小的数组。例如:

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

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

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

(1)结构中的柔性数组成员前⾯必须至少⼀个其他成员。
(2)sizeof 返回的这种结构大小不包括柔性数组的内存。
(3)包含柔性数组成员的结构用 malloc() 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

6.3 柔性数组的使用
#include <stdio.h>
#include <stdlib.h>// 定义包含柔性数组的结构体
struct FlexArray 
{int length;int data[]; // 柔性数组,实际长度在运行时确定
};int main() 
{int n = 5;// 计算结构体大小并分配内存,前面的sizeof(struct FlexArray)分配给柔性数组前面的变量,后面的 n * sizeof(int)为柔性数组的大小struct FlexArray* flex = (struct FlexArray*)malloc(sizeof(struct FlexArray) + n * sizeof(int));if (flex == NULL) {printf("Memory allocation failed\n");return 1;}flex->length = n;// 使用柔性数组存储数据for (int i = 0; i < n; i++) {flex->data[i] = i * 2;}// 打印柔性数组中的数据printf("Data stored in flexible array:\n");for (int i = 0; i < flex->length; i++) {printf("%d ", flex->data[i]);}// 释放内存free(flex);return 0;
}
6.4 柔性数组的优势 
#include <stdio.h>
#include <stdlib.h>
typedef struct st_type
{int i;int* p_a;
}type_a;
int main()
{int i = 0;type_a* p = (type_a*)malloc(sizeof(type_a));p->i = 100;p->p_a = (int*)malloc(p->i * sizeof(int));//处理for (i = 0; i < 100; i++){p->p_a[i] = i;}for (int i = 0; i < 100; i++) {printf("%d ", p->p_a[i]);}//释放空间free(p->p_a);p->p_a = NULL;free(p);p = NULL;return 0;
}

假如将6.3中的柔性数组换成 int*的指针,同样可以实现相同的功能 ,但是柔性数组更有优势:

拓展:C语言结构体里的数组和指针 

7、总结C/C++中程序内存区域划分

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

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

相关文章

Vue.js前端开发零基础教学(一)

目录 第一章 初识Vue.js 前言 开发的好处 一.前端技术的发展 什么是单页Web应用&#xff1f; 二. Vue的简介 三. Vue的特性 四. Vue的版本 五.常见的包管理 六.安装node环境 第一章 初识Vue.js 学习目标&#xff1a; 了解前端技术的发展 了解什么是Vue掌握使用方…

Oracle19C图形界面安装教程

文章目录 一、安装前的准备1、安装Linux操作系统2、配置网络源或者本地源3、hosts文件配置 二、Oracle19c安装过程1、安装相关软件&#xff1a;2、用户与组&#xff1a;3、修改内核参数&#xff1a;4、资源限制&#xff1a;5、配置用户环境变量&#xff1a;6、创建相关文件目录…

如何理解 Linux 命令行参数与环境变量7

一、命令行参数 1.1参数介绍 在写C语言程序时&#xff0c;main函数是否可以带参数呢&#xff1f;------ 是可以的 int argc: 命令行参数的个数char *argv[ ]: 字符指针数组&#xff08;指向各个命令行参数的字符指针所构成的数组&#xff09; 我们写一段代码来打印一下看这…

基于单片机的事务管理系统

基于单片机的事务管理系统 摘 要 所谓事务管理系统就是主要用来做提醒&#xff0c;辅助以计时、秒表等的一个小系统。利用MCS51单片机即可完成系统硬件需要&#xff0c;成本低廉&#xff0c;程序简单&#xff0c;功能丰富实用&#xff0c;使用率广。根据题目的设计要求&#…

HW中常见的面试题

1.说说你在工作中或者SRC中挖到的比较典型的漏洞? 2.HW中如果已经发现红方IP地址&#xff0c;该如何溯源? 通过蜜罐系统或者安全设备锁定红方MAC&#xff0c;然后通过IP地址对&#xff0c;whois查询到该IP注册人以及注册邮箱&#xff0c;如果是发现邮箱是某厂商注册则可利…

SpringBoot-03 | SpringBoot自动配置

SpringBoot-03 | SpringBoot自动配置 原理分析代码示例源码剖析SpringBootConfiguration&#xff1a;组合注解&#xff0c;标记当前类为配置类ComponentScanEnableAutoConfigurationImport加载spring.factoriesrun初始化加载spring.factoriesspring.factories中的钩子类 网上盗…

部署DiffSynth-Studio实现视频风格转换

DiffSynth 是一个新的 Diffusion 引擎&#xff0c;可以实现图片和视频的风格转换。 拉取源码 git clone https://github.com/Artiprocher/DiffSynth-Studio/ 创建环境 conda env create -f environment.yml conda activate DiffSynthStudio 下载模型 将Stable Diffusion模…

组织学习的革命:打破常规,引领未来

组织学习的革命&#xff1a;打破常规&#xff0c;引领未来 一、组织学习的重塑&#xff1a;从传统到现代的转变 在知识经济的时代背景下&#xff0c;组织学习已经成为企业持续发展和竞争优势的关键。传统的组织学习方式&#xff0c;如培训、研讨会等&#xff0c;虽然在一定程…

NeRF——基于神经辐射场的三维场景重建和理解

概述 三维重建是一种将物理世界中的实体转换为数字模型的计算机技术。其基本概念是通过对物理世界中的物体或场景进行扫描或拍摄&#xff0c;并使用计算机算法将其转换为三维数字模型。抽象意义上的三维模型指的是&#xff1a;形状和外观的组合&#xff0c;并且可以渲染成不同…

阿里云服务器2核4G服务器收费价格表,1个月和一年报价

阿里云2核4G服务器多少钱一年&#xff1f;2核4G服务器1个月费用多少&#xff1f;2核4G服务器30元3个月、85元一年&#xff0c;轻量应用服务器2核4G4M带宽165元一年&#xff0c;企业用户2核4G5M带宽199元一年。本文阿里云服务器网整理的2核4G参加活动的主机是ECS经济型e实例和u1…

PyTorch学习笔记之激活函数篇(三)

文章目录 3、ReLU3.1 公式3.2 对应的图像3.3 对应的图像的代码3.4 优点与不足3.5 torch.relu()函数 3、ReLU 3.1 公式 ReLU函数的公式&#xff1a; f ( x ) { x , x > 0 0 , x < 0 f(x) \begin{cases} x&,x>0 \\ 0&,x<0 \end{cases} f(x){x0​,x>…

Vue 3响应式系统详解:ref、toRefs、reactive及更多

&#x1f31f; 前言 欢迎来到我的技术小宇宙&#xff01;&#x1f30c; 这里不仅是我记录技术点滴的后花园&#xff0c;也是我分享学习心得和项目经验的乐园。&#x1f4da; 无论你是技术小白还是资深大牛&#xff0c;这里总有一些内容能触动你的好奇心。&#x1f50d; &#x…

为什么延迟删除可以保证MYSQL 与redis的一致性?

看过很多保持MYSQL 与redis保持一致性的文章都提到了延迟删除&#xff0c;其实脱离任何业务场景的设计都是不切实际的&#xff0c;所以我会本着一个通用的读写场景去分析为什么延迟删除大概率可以保证MYSQL与redis的最终一致。 通常的读写场景 通常在使用redis作为读写缓存时…

无人机/飞控--ArduPilot、PX4学习记录(2)

这是一篇碎碎念&#xff0c;零零碎碎的记录了环境配置过程&#xff0c;仅供本人记录学习历程和参考。(记录的挺乱的&#xff0c;但是文章链接里的博客写的是真好) 本章主要完成的目标&#xff1a; 安装PX4 并 成功运行出3D无人机界面。 参考文章&#xff1a; 搭建PX4环境&…

不同的Git仓库单独设置用户名和邮件地址

最近使用公司电脑将自己的一个私人项目推送到远程仓库&#xff0c;仓库显示的公司邮箱地址。因为设置了全局的username和usermail&#xff0c;这样就比较尴尬了。但是又不能频繁来回改用户信息&#xff0c;那么请看下面如何单独设置仓库的用户信息&#xff0c;让不同的仓库展示…

走上管理岗才发现:所谓工作能力强,就一点

走上管理岗才发现&#xff1a;所谓工作能力强&#xff0c;就一点 建立SOP‼️ - 我二本工科&#xff0c;电力专业&#xff0c;从现场工程师到新能源公司核心部门经理&#xff0c;入职公司三个月直接升职加薪。 - 我刚从工程师升至经理带团队的时候&#xff0c;经常靠加班续命&a…

zookeeper底层细节

zk 临时节点和watch机制实现注册中心自动注册和发现&#xff0c;数据都在内存&#xff0c;nio 多线程模型&#xff1b; cp注重一致性&#xff0c;数据不一致时集群不可用 事务请求处理方式 1.all事务由唯一服务器处理 2.将客户端事务请求转成proposal分发follower 3.等待半…

部署单节点k8s并允许master节点调度pod

安装k8s 需要注意的是k8s1.24 已经弃用dockershim&#xff0c;现在使用docker需要cri-docker插件作为垫片&#xff0c;对接k8s的CRI。 硬件环境&#xff1a; 2c2g 主机环境&#xff1a; CentOS Linux release 7.9.2009 (Core) IP地址&#xff1a; 192.168.44.161 一、 主机配…

【spring】@ConditionalOnResource注解学习

ConditionalOnResource 介绍 ConditionalOnResource 是Spring框架中的一个条件化注解&#xff0c;它允许你根据类路径中是否存在指定的资源来决定是否加载特定的Bean定义或配置类。这个注解可以用于类级别或方法级别。 具体Conditional使用请看这篇文章【spring】Conditional…

停车管理系统asp.net+sqlserver

停车管理系统asp.netsqlserver 说明文档 运行前附加数据库.mdf&#xff08;或sql生成数据库&#xff09; 主要技术&#xff1a; 基于asp.net架构和sql server数据库&#xff0c; 功能模块&#xff1a; 停车管理系统asp.net sqlserver 用户功能有菜单列表 我的停车记录 专…