初识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] …...

C++实现分布式网络通信框架RPC(3)--rpc调用端
目录 一、前言 二、UserServiceRpc_Stub 三、 CallMethod方法的重写 头文件 实现 四、rpc调用端的调用 实现 五、 google::protobuf::RpcController *controller 头文件 实现 六、总结 一、前言 在前边的文章中,我们已经大致实现了rpc服务端的各项功能代…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...

LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...

mac:大模型系列测试
0 MAC 前几天经过学生优惠以及国补17K入手了mac studio,然后这两天亲自测试其模型行运用能力如何,是否支持微调、推理速度等能力。下面进入正文。 1 mac 与 unsloth 按照下面的进行安装以及测试,是可以跑通文章里面的代码。训练速度也是很快的。 注意…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...