初识C语言·动态内存开辟
1 为什么要有动态内存开辟
int a = 10;
int arr[10] = { 0 };
上述定义了一个整型,开辟了4个字节,定义了一个整型数组,开辟了40个字节,但是是固定开辟的,面对灵活多变的实际问题的时候可能就有点鸡肋,这种开辟空间的特点是:
i) 开辟好空间之后不能改变
ii) 开辟的空间大小是固定的
那么为了解决实际问题就引入了动态内存开辟,可以根据实际需要进行内存开辟。
那么引用了4个函数,分别是malloc calloc free realloc,而动态开辟函数开辟的空间都是在堆区开辟的,不是栈区!
2 malloc函数的使用
void* malloc (size_t size);
这是malloc函数的原型,需要引用的头文件是stdlib,返回的是void*指针,返回void*指针的原因是因为实际工作中开辟的空间类型是根据实际需要确定的,所以开辟好空间之后需要进行强制类型转化,开辟空间的单位是字节,参数表示的是开辟多少个字节,最后返回的地址是开辟的字节的首地址。
那么开辟空间的话也是分为是否开辟成功的,如果开辟成功了,返回的就是那块空间的首地址,如果开辟失败了,返回的就是空指针,比如我开辟几百亿个字节,一般情况下返回的就是NULL,所以我们用指针接收了地址后第一件事就是判断一下是不是空指针,不然是会有警告的。
这是正常使用的情况,那如果size_ t size是0呢?这时候malloc的行为标准是未定义的,操作就取决于编译器了。
代码1:
int main()
{int* p = (int*)malloc(40);assert(p);for (int i = 0; i < 10; i++){*p = i;p++;} for (int i = 0; i < 10; i++){printf("%d ", *(p + i));}return 0;
}
问运行结果是什么?
答案是报错,你可以把内存开辟的返回的地址理解成数组名,数组名哪里能自增自减呢?理解成数组名的缘由是因为free,动态内存函数开辟了空间之后,使用完空间是要被释放的,而free的参数是开辟的空间的首地址,所以p不能自增自减。防止后面释放空间释放错了。
3 free函数的使用
上面提到了返回的地址不能自增自减,因为free( 头文件依然是stdlib)要出场了,free,免费,释放,在C语言里面就是专门用来释放动态内存开辟的空间的,当使用完之后都是要free的。
int main()
{int* p = (int*)malloc(40);assert(p);for (int i = 0; i < 10; i++){*(p + i) = i;} for (int i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}
free的参数是动态内存开辟的地址,可能让人疑问的点就是为什么最后要给一个NULL,这是因为我们传的是值,不是地址,free的形参是值,所以释放空间的时候,那块空间确实是释放了没错,但是p还仍然保留着原来的数据,所以需要手动置为空指针。
使用free的时候需要注意的:
·free的参数一定是动态开辟的地址,如果不是,那么free的行为是未定义的
·free的参数如果是NULL,那么该函数什么事都不做
4 calloc函数的使用
void* calloc (size_t num, size_t size);
calloc函数的原型如上,头文件依然是stdlib,作用是开辟num个大小为size字节的空间,它和malloc是极其相似的,唯一的不同就是calloc会自动初始化空间为0,malloc函数则不会初始化, 举个例子:
int main()
{int* p = (int*)calloc(10, sizeof(int));assert(p);for (int i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}
最后的运行结果应该全是0。
5 realloc函数的使用
realloc函数才是动态内存开辟函数的老大,因为点啥呢,因为它可以扩大空间,比如你写代码到一半发现空间不够了,这时候就需要realloc函数来操作了,它可以扩大空间
void* realloc (void* ptr, size_t size);
realloc函数的原型如上,头文件依然是stdlib,第一个参数一般都是动态开辟的地址,第二个是表示扩大到多少字节,所以一般情况下,使用该函数之前一般都是已经动态开辟了空间的,那么什么是特殊情况呢?
如下:
int main()
{int* p = (int*)realloc(NULL, 40);assert(p);for (int i = 0; i < 10; i++) {*(p + i) = i;}for (int i = 0; i < 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}
这种情况realloc函数就是malloc函数,也就是说,如果realloc函数的第一个参数是空指针的话,那么就是从内存中随机开辟空间,此时的realloc函数就是malloc函数。
那么realloc函数一般用法就是用来扩大空间的,如果你想实验一下缩短空间也是可以试试的,只不过这个时候realloc函数的行为是未定义的。
realloc函数扩大空间有两种情况:
1 原地址的后面有足够的空间用于扩容
2 原地址的后面没有足够的空间用于扩容
第一种情况没什么好说的,原有空间变大而已,第二种情况,realloc会在堆区重新找一个符合需要的空间,如果没有找到就会返回空指针,如果找到了,那么原有的数据会赋值到新空间,且原有空间会被释放,所以realloc函数开辟完空间之后,如果是重新找空间开辟的,就会释放原来的空间,那么实际写代码的时候,我们就会重新用一个指针来接收新空间,判断完是不是空指针后,再决定要不要赋给原来的地址,
realloc函数和其他函数最不一样的地方就是在于它会自己释放空间,这点需要注意,realloc函数举个例子:
int main()
{int* p = (int*)calloc(25, sizeof(int));assert(p);int* pa = realloc(p, 20 * sizeof(int));//两种情况 所以用另一个指针接收assert(pa);if (pa != NULL){p = pa;}free(*p);free(*pa);p = NULL;pa = NULL;return 0;
}
Tips:所有动态内存开辟的空间是不会自己释放的,释放的空间都是需要自己释放的,要么就是程序结束由操作系统来释放。
int* Test()
{int* p = *(int*)malloc(40);return p;
}
int main()
{//操作return 0;
}
这样的p只要不释放,空间都是可以使用的。
6 常见的动态内存开辟的错误
1)对空指针的解引用
//对空指针的解引用
int main()
{int* p = (int*)malloc(INT_MAX * INT_MAX);;*p = 20;free(p);p = NULL;return 0;
}
这里内存是开辟不了这么多的空间的,所以p是空指针,那么解引用之后,自然就会报错
2)对动态内存开辟空间的越界访问
//对动态内存开辟空间的越界访问
int main()
{int* p = (int*)calloc(10, sizeof(int));assert(p);for (int i = 0; i <= 10; i++){*(p + i) = i;} for (int i = 0; i <= 10; i++){printf("%d ", *(p + i));}free(p);p = NULL;return 0;
}
我们使用了calloc函数开辟了10个整型空间,使用的时候循环次数是11次,那么最后一次循环就会越界访问到未开辟的空间,系统就会报错。
3) 对非动态内存开辟的空间释放
//对非动态开辟的空间进行释放
int main()
{int a = 10;int* pa = &a;free(pa);pa = NULL;return 0;
}
前面提到free函数只能释放动态内存开辟的空间,因为局部变量 全局变量是在栈区 静态区的,而free适用于堆区的动态内存开辟,所以使用free释放非动态内存开辟的空间的时候系统就会报错。
4)free释放一部分动态内存开辟的空间
//使用free释放一部分动态内存开辟的空间
int main()
{int* p = (int*)malloc(10 * sizeof(int));assert(p);p++;free(p);p = NULL;return 0;
}
前面提及我们可以把动态内存开辟返回的地址当作数组名,这样可以避免我们给该地址自增自减,因为free释放都是释放的一整块空间,那么自增之后,free的参数不是起始地址,就会导致释放过多,也会导致越界访问,系统就会报错。
5)对同一块空间多次释放
//对同一块空间多次释放
int main()
{int* p = (int*)calloc(10, sizeof(int));assert(p);/* 操作*/free(p);//p = NULL;/*操作*/free(p);p = NULL;return 0;
}
对同一块空间多次释放后,free再去访问那个地址, 自然就会报错,但是如果前面释放了空间之后,并且置于0,是没有问题的,因为free的参数如果是空指针的话,就不会有任何行为。
6)动态内存开辟的空间未释放
//动态内存开辟的空间未释放
int main()
{int* p = (int*)malloc(40);/*操作*/return 0;
}
如果开辟的空间没有进行释放,那么内存中这块空间的状态就是一直被占用的情况,就会导致内存泄露,假如这种情况多了的话,说不定某一天你的系统内存就被占满了,然后你一重启,就会发现,欸对了,所以动态内存开辟的空间一定要正确释放。
7 动态内存开辟函数题目解析
代码1:
void GetMemory(char* p)
{p = (char*)malloc(100);
}
int main()
{char* str = NULL;Getmemory(str);strcpy(str, "hello world");printf(str);return 0;
}
程序运行到strcpy的时候就会报错,最开始str是空指针,那么这里的传参方式是传值调用,所以即使p的地址已经指向了malloc开辟的100字节,str仍然是空指针,还有一个问题是出了函数GeiMemory的时候,p就会被销毁了,也会导致内存泄漏,并且打印出错
Tips:printf这里是没有问题的,可以自行实验一下,比如pirntf("abcdefg\n"); 实际上传的也是该字符串的地址。
void GetMemory(char** p)
{*p = (char*)malloc(100);
}
int main()
{char* str = NULL;GetMemory(&str);strcpy(str, "hello world");printf(str);free(str);str = NULL;return 0;
}
这是修正后的代码。
代码2:
char* GetMemory()
{char p[] = "Hello world";return p;
}
int main()
{char* str = NULL;str = GetMemory();printf(str);return 0;
}
如果经过调试,我们会发现str指向的确实是常量字符串的地址,实际打印的效果却是一串乱码,其实这是因为出了Getmemory函数的作用域,导致常量字符串被销毁,但是返回的地址是没问题的,是数据没了,此时的p就是野指针了。
修改的方式也很简单,只需要加一个static就行了。
char* GetMemory()
{static char p[] = "Hello world";return p;
}
int main()
{char* str = NULL;str = GetMemory();printf(str);return 0;
}
代码3:
void Getmemory(char** p, size_t num)
{*p = (char*)malloc(num);
}
int main()
{char* str = NULL;Getmemory(&str, 100);strcpy(str, "hello");printf(str);return 0;
}
程序看起来是没有问题的,确实是打印了hello,但是还是存在问题,内存泄漏,没有释放空间。
代码4:
int main()
{char* str = (char*)malloc(100);assert(str);strcpy(str, "hello");free(str);if (str != NULL);{strcpy(str, "world");printf(str);}return 0;
}
这串代码的运行结果是打印world,虽然free了动态开辟的空间,但是str仍然指向了那块空间,str先是赋值了hello,然后判断不是空指针,再次复制了world,所以最后的结果是world,这个str就是野指针了,虽然运行结果是对的,但是程序仍然是有问题的。
8 柔性数组
在C99标准中,允许柔性数组的存在,比如:
struct St
{int i;int arr[0];
};
其中arr就是柔性数组,但有的编译器可能无法通过,所以有时候0是没有加的。
柔性数组的特点是:
i)柔性数组一定是最后一个成员
ii)sizeof计算大小的时候不包括柔性数组的大小
iii)使用malloc的时候开辟的空间应该大于结构体前面成员大小的总和,以此来符合预期
int main()
{struct St{char i;int j;int arr[0];};printf("%zd\n",sizeof(struct St));return 0;
}
结合内存对齐,柔性数组的特点,最后的结果是8。
柔性数组的使用:
struct St
{char i;int j;int arr[0];
};
int main()
{struct St* ps = (struct St*)malloc(sizeof(struct St) + 10 * sizeof(int));assert(ps);ps->i = 'w';ps->j = 520;//使用柔性数组for (int m = 0; m < 10; m++){ps->arr[m] = m;}//空间不够struct St* pt = (struct St*)realloc(ps,sizeof(struct St) + 15 * sizeof(int));assert(pt);ps = pt;printf("%c\n", ps->i);printf("%d\n", ps->j);for (int n = 10; n < 15; n++){ps->arr[n] = n;}for (int m = 0; m < 15; m++){printf("%d ", ps->arr[m]);}free(pt);ps = NULL;pt = NULL;return 0;
}
常规结构体在栈区开辟的空间,有了柔性数组,我们就需要用到malloc函数,那么结构体就是在堆区开辟的空间,在使用的时候需要注意最后释放只需要释放一个指针,因为realloc函数是会自己释放上一块空间的,在开辟空间的时候:
struct St* ps = (struct St*)malloc(sizeof(struct St) + 10 * sizeof(int));
这种写法是为了更直观的看到开辟的空间比整个结构体都大,这样柔性数组才有自己的空间
那么还有一种模拟柔性数组的写法,如下:
struct St
{char i;int j;int* arr;
};
int main()
{struct St* ps = (struct St*)malloc(sizeof(struct St));assert(ps);ps->i = 'w';ps->j = 520;ps->arr = (int*)malloc(10*sizeof(int));assert(ps->arr);//使用柔性数组for (int i = 0; i < 10; i++){*(ps->arr + i) = i;}//空间不够int* pt = (int*)realloc(ps->arr,15 * sizeof(int));assert(pt);ps->arr = pt;printf("%c\n", ps->i);printf("%d\n", ps->j);for (int n = 10; n < 15; n++){*(ps->arr + n) = n;}for (int m = 0,n = 0; m < 15; m++,n++){printf("%d ",*(ps->arr + m));}free(pt);free(ps);ps->arr = NULL;pt = NULL;ps = NULL;return 0;
}
结构体的最后一个成员是int*,也可以是其他类型的指针,这种写法是先为结构体的其他成员开辟空间,再给int*开一个单间,需要用的时候给个malloc,空间不够给个realloc,这样的话,也是类似于柔性数组的,它与上面的写法不同的是多次开辟空间,多次释放,略显繁琐,但是访问速度快点,上面的优点就是内存方便释放,两种写法各有优势。
感谢阅读!
相关文章:

初识C语言·动态内存开辟
1 为什么要有动态内存开辟 int a 10; int arr[10] { 0 }; 上述定义了一个整型,开辟了4个字节,定义了一个整型数组,开辟了40个字节,但是是固定开辟的,面对灵活多变的实际问题的时候可能就有点鸡肋,这种开…...

机器学习 | 利用Pandas进入高级数据分析领域
目录 初识Pandas Pandas数据结构 基本数据操作 DataFrame运算 文件读取与存储 高级数据处理 初识Pandas Pandas是2008年WesMcKinney开发出的库,专门用于数据挖掘的开源python库,以Numpy为基础,借力Numpy模块在计算方面性能高的优势&am…...
三、计算机理论-计算机网络-物理层,数据通信的理论基础,物理传输媒体、编码与传输技术及传输系统
物理层概述 物理层为数据链路层提供了一条在物理的传输媒体上传送和接受比特流的能力。物理层提供信道的物理连接,主要任务可以描述为确定与传输媒体的接口有关的一些特性:机械特性、电气特性、功能特性、过程特性 数据通信的理论基础 数据通信的意义 主…...

ERROR Failed to get response from https://registry.npm.taobao.org/ 错误的解决
这个问题最近才出现的。可能跟淘宝镜像的证书到期有关。 解决方式一:更新淘宝镜像(本人测试无效,但建议尝试) 虽然无效,但感觉是有很大关系的。还是设置一下比较好。 淘宝镜像的地址(registry.npm.taobao…...
overflow产生的滚动条样式设置
修改overflow产生的滚动条样式,主要可以通过如下三个伪元素设置: 1)-webkit-scrollbar:设置水平滚动条的高度,垂直滚动的宽度 2)-webkit-scrollbar-thumb:设置滚动条里面的滑块样式 3)-webkit-scrollbar-track&…...
Ubuntu环境vscode配置Log4cplus库
1、下载源码 http://sourceforge.net/projects/log4cplus/ 2、安装 例如我下载的是2.0.8版本压缩包,需要解压缩 log4cplus-2.0.8.7z安装解压工具: apt install p7zip-full解压: 7z x log4cplus-2.0.8.7z -r -o/home/配置及编译安装&#x…...
vue中,使用file-saver导出文件,下载Excel文件、下载图片、下载文本
vue中,使用file-saver导出文件,下载Excel文件、下载图片、下载文本 1、基本介绍 npm地址:file-saver - npm 2、安装 # Basic Node.JS installation npm install file-saver --save bower install file-saver# Additional typescript defin…...
【VUE】v-if 和 v-show 大详解(多角度分析+面试简答版)
多角度分析+面试简答版 一、`v-if` 和 `v-show` 的区别之多角度分析控制手段:编译过程:编译条件:性能消耗:总结使用场景二、 `v-if`、`v-show`、`display:none` 和`visibility: hidden` 的区别三、简洁版回答:`v-show` 与 `v-if` 比较一、v-if 和 v-show 的区别之多角度分…...
mac intel jdk安装与配置
jdk地址下载 https://www.oracle.com/java/technologies/downloads/ https://repo.huaweicloud.com/java/jdk/8u201-b09/ 安装后 下载完成之后打开终端 注意如果是第一次配置环境变量需要创建.bash_profile文件。(注意:touch后面有空格) to…...

Backtrader 文档学习-Bracket Orders
Backtrader 文档学习-Bracket Orders 1. 概述 组合订单类型是一个非常宽泛的订单类别,只要brokder支持的订单类型都可以, 包括(Market, Limit, Close, Stop, StopLimit, StopTrail, StopTrailLimit, OCO)。 该功能用于回测,交互broker Brac…...

Python编程 从入门到实践(项目二:数据可视化)
本篇为实践项目二:数据可视化。 配合文章python编程入门学习,代码附文末。 项目二:数据可视化 1.生成数据1.1 安装Matplotlib1.2 绘制简单的折线图1.2.1 修改标签文字和线条粗细1.2.2 校正图形1.2.3 使用内置样式1.2.4 使用scatter()绘制散点…...

Linux版本下载Centos操作
目录 一、Centos7 二、下载Centos7镜像 三、下载Centos7 买了个硬件安装裸机(一堆硬件) 把安装盘放到虚拟机里面,给机器加电 配置设置 编辑 网络配置 开启网络功能 四、安装linux客户端 Xshell是什么 Xshell使用(连接…...

Offer必备算法_二分查找_八道力扣OJ题详解(由易到难)
目录 二分查找算法原理 ①力扣704. 二分查找 解析代码 ②力扣34. 在排序数组中查找元素的第一个和最后一个位置 解析代码 ③力扣69. x 的平方根 解析代码 ④力扣35. 搜索插入位置 解析代码 ⑤力扣852. 山脉数组的峰顶索引 解析代码 ⑥力扣162. 寻找峰值 解析代码…...

SpringBoot对Bean的管理
Bean扫描 Spring中使用标签扫描或者注解 Springboot中没有使用标签或者注解它是怎么扫描的我的controlelr,service等等 核心在于springboot启动类中的SpringBootApplication注解 此注解其实是一个组合注解 它组合了一个ComponentScan注解,相当于在启…...

体验 AutoGen Studio - 微软推出的友好多智能体协作框架
体验 AutoGen Studio - 微软推出的友好多智能体协作框架 - 知乎 最近分别体验了CrewAI、MetaGPT v0.6、Autogen Studio,了解了AI Agent 相关的知识。 它们的区别 可能有人要问:AutoGen我知道,那Autogen Studio是什么? https://g…...

超简单的正则表达式从入门到精通
正则表达式,又称规则表达式(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。 概念 正则表达式是对字…...

webpack常用配置
1.webpack概念 本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 …...

nodejs学习计划--(六)包管理工具
包管理工具 1. 介绍 包是什么 『包』英文单词是 package ,代表了一组特定功能的源码集合包管理工具 管理『包』的应用软件,可以对「包」进行 下载安装 , 更新 , 删除 , 上传 等操作 借助包管理工具,可以快…...

数字地球开放平台农作物长势监测解决方案
数字地球开放平台农作物长势监测解决方案 利用遥感技术进行产量预测是一种高效而准确的方法,通过监测植被的生长状况、土地利用、气象等因素,可以为农业决策提供有力支持。数字地球开放平台拥有200颗卫星,为您提供一站式卫星遥感服务。 农情监…...
react hooks 的useState:
React 的 useState Hook 是一种用于在函数组件中管理状态的机制。它可以让函数组件具有类似于类组件的状态管理能力。 useState Hook 接收一个初始值作为参数,并返回一个包含状态值和更新状态值的数组。 import { useState } from react;const [state, setState] …...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

【大模型RAG】Docker 一键部署 Milvus 完整攻略
本文概要 Milvus 2.5 Stand-alone 版可通过 Docker 在几分钟内完成安装;只需暴露 19530(gRPC)与 9091(HTTP/WebUI)两个端口,即可让本地电脑通过 PyMilvus 或浏览器访问远程 Linux 服务器上的 Milvus。下面…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...

无人机侦测与反制技术的进展与应用
国家电网无人机侦测与反制技术的进展与应用 引言 随着无人机(无人驾驶飞行器,UAV)技术的快速发展,其在商业、娱乐和军事领域的广泛应用带来了新的安全挑战。特别是对于关键基础设施如电力系统,无人机的“黑飞”&…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...

什么是VR全景技术
VR全景技术,全称为虚拟现实全景技术,是通过计算机图像模拟生成三维空间中的虚拟世界,使用户能够在该虚拟世界中进行全方位、无死角的观察和交互的技术。VR全景技术模拟人在真实空间中的视觉体验,结合图文、3D、音视频等多媒体元素…...

认识CMake并使用CMake构建自己的第一个项目
1.CMake的作用和优势 跨平台支持:CMake支持多种操作系统和编译器,使用同一份构建配置可以在不同的环境中使用 简化配置:通过CMakeLists.txt文件,用户可以定义项目结构、依赖项、编译选项等,无需手动编写复杂的构建脚本…...
ubuntu22.04 安装docker 和docker-compose
首先你要确保没有docker环境或者使用命令删掉docker sudo apt-get remove docker docker-engine docker.io containerd runc安装docker 更新软件环境 sudo apt update sudo apt upgrade下载docker依赖和GPG 密钥 # 依赖 apt-get install ca-certificates curl gnupg lsb-rel…...

门静脉高压——表现
一、门静脉高压表现 00:01 1. 门静脉构成 00:13 组成结构:由肠系膜上静脉和脾静脉汇合构成,是肝脏血液供应的主要来源。淤血后果:门静脉淤血会同时导致脾静脉和肠系膜上静脉淤血,引发后续系列症状。 2. 脾大和脾功能亢进 00:46 …...