初识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] …...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
CTF show Web 红包题第六弹
提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框,很难让人不联想到SQL注入,但提示都说了不是SQL注入,所以就不往这方面想了 先查看一下网页源码,发现一段JavaScript代码,有一个关键类ctfs…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
3-11单元格区域边界定位(End属性)学习笔记
返回一个Range 对象,只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意:它移动的位置必须是相连的有内容的单元格…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
LINUX 69 FTP 客服管理系统 man 5 /etc/vsftpd/vsftpd.conf
FTP 客服管理系统 实现kefu123登录,不允许匿名访问,kefu只能访问/data/kefu目录,不能查看其他目录 创建账号密码 useradd kefu echo 123|passwd -stdin kefu [rootcode caozx26420]# echo 123|passwd --stdin kefu 更改用户 kefu 的密码…...
Java数值运算常见陷阱与规避方法
整数除法中的舍入问题 问题现象 当开发者预期进行浮点除法却误用整数除法时,会出现小数部分被截断的情况。典型错误模式如下: void process(int value) {double half = value / 2; // 整数除法导致截断// 使用half变量 }此时...
