c语言动态内存分布

前言:

随着我们深入的学习c语言,之前使用的静态内存分配已经难以满足我们的实际需求。比如前面我们的通讯录功能的实现,如果只是静态内存分配,那么也就意味着程序开始的内存分配大小就是固定的,应该开多大的空间呢?开大了是浪费,开小了又不能满足自己的需求。而动态内存分配可以完美的解决这个问题,真正地做到需要多少空间就开多大的空间(根据需要动态地分配和释放内存空间).

总的来说,动态内存分配比静态内存分配更灵活,效率也更高,避免了空间的浪费。

下面就开始动态内存的学习吧。

1.动态内存函数:

1.malloc

2.free

3.calloc

4.realloc

这些函数都声明在stdlib头文件中。

1.1 malloc和free

malloc:

void* malloc (size_t size);

malloc函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

参数size表示需要开辟的空间字节大小,返回类型为void*,泛型指针,指向被开辟空间首元素地址。具体指向什么类型的数据需要使用者自己决定。

需要注意的是,如果开辟空间失败这个函数会返回NULL,所以为了避免使用空指针,在使用这个函数之后需要判断是否返回了NULL.

free:

void free (void* ptr);

free函数用来释放掉动态开辟的内存。

参数prt表示的是需要释放动态内存空间的指针。 

这个函数非常重要,因为如果动态内存分配的空间没有及时释放的话,可能会造成内存泄漏,而长时间运行的程序中存在内存泄漏会逐渐消耗系统的可用内存,最终可能导致程序崩溃或系统变得不稳定。

所以为了避免内存泄漏的情况发生,我们应该养成即使释放动态内存的习惯。

举例:

int main() {//使用malloc动态开辟空间的例子int num = 0;scanf("%d", &num);int* ptr = NULL;ptr = malloc(num * sizeof(int));//开辟了num个整数类型元素大小的空间,返回这片空间的起始位置给ptrif (ptr == NULL) { //判断是否申请空间成功perror("malloc");}//初始化申请空间的元素for (int i = 0; i < num; i++) {*(ptr + i) = i;}//释放空间,防止空间泄漏,指针置空,防止野指针free(ptr);ptr = NULL;return 0;
}

这里我输入的num=10.

监视:

我们发现ptr指针确实指向了10个整型大小的空间,能正常初始化。

释放空间后:

为什么要释放掉空间后将ptr置空?

这是因为虽然我们将动态开辟的空间释放掉了,但是ptr依旧指向这片空间,但是此时ptr已经没有权限操作这片空间了,所以我们需要置空。

1.2 calloc

void* calloc (size_t num, size_t size);

calloc函数与malloc函数相似,都是向内存申请一块连续可用的空间,并返回指向这块空间的指针,区别是,calloc申请的空间里的每个字节会默认初始化为0。

第一个参数num是元素的个数,size表示的是元素的字节大小,开辟num个大小为size的元素的空间,返回开辟空间的起始地址。

 其实malloc和calloc的用法也基本一致,只不过比malloc更加省事,不用担心初始化的问题。

举例:

int main() {int* p = (int*)calloc(10, sizeof(int));if (p == NULL) {perror("calloc");//打印错误信息}free(p);p = NULL;return 0;
}

监视指针p:

我们可以看到,在申请到空间后,空间里的元素都被初始化为了0.

1.3 realloc

void* realloc (void* ptr, size_t size);

realloc函数可以做到对动态开辟内存大小的调整,如果此时申请的动态空间不够,那么我们可以使用realloc函数来进行“扩容”。

ptr 是要调整的内存地址,size 调整之后新大小,返回值为调整之后的内存起始位置。 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。

 realloc申请空间会遇到两种情况:

1.原空间后面有足够空间,会直接在原来ptr指向空间后面追加,返回的地址也是ptr指向的地址。

2.原有空间后面空间不够,那就在堆空间另找一片空间,这个时候返回的的地址就会是一个随机值。

 举例:

int main()
{int* ptr = (int*)malloc(120);int ptr2 = ptr;//记录原来ptr指向的地址if (ptr == NULL){perror("malloc");}//扩展容量ptr = (int*)realloc(ptr, 1000);if (ptr == NULL)//如果申请失败返回NULL{perror("realloc");}if (ptr == ptr2) {printf("情况1\n");}else {printf("情况2\n");}free(ptr);ptr = NULL;return 0;
}

我们看到,realloc申请的空间返回的地址不是原来的ptr指向的地址.

我们再来改一下代码:

int main()
{int* ptr = (int*)malloc(120);int ptr2 = ptr;if (ptr == NULL){perror("malloc");}//扩展容量ptr = (int*)realloc(ptr, 140);//扩容的空间很小if (ptr == NULL)//如果申请失败返回NULL{perror("realloc");}if (ptr == ptr2) {printf("情况1\n");}else {printf("情况2\n");}free(ptr);ptr = NULL;return 0;
}

现在我们把realloc申请的空间改小一点,为了能找到符合情况1(realloc在原来ptr指向空间后面追加).

当然这也只是偶然情况,就算是扩容改小一点也不一定就会直接追加空间在ptr指向空间后面。

2.常见的动态内存错误

在介绍了几个动态内存函数的用法之后,我们再来看看在使用动态内存函数时经常犯的错误吧。

2.1 对NULL指针的解引用操作

void test()
{int *p = (int *)malloc(INT_MAX/4);*p = 20;//如果p的值是NULL,就会有问题free(p);
}

如果我们申请的空间太大就有可能开辟空间失败,这个时候指针p接收到的就是NULL,而对NULL解引用操作就会导致错误。

2.2 对动态开辟空间的越界访问

void test()
{int i = 0;int *p = (int *)malloc(10*sizeof(int));if(NULL == p){exit(EXIT_FAILURE);}for(i=0; i<=10; i++){*(p+i) = i;//当i是10的时候越界访问}free(p);
}

我们这里只是申请了10个整型元素大小的空间,这个时候*(p+10)其实已经越界访问了,此时程序就会出错。

2.3 对非动态开辟内存使用free释放

void test()
{int a = 10;int *p = &a;free(p);//ok?
}

上面我们已经知道了free是用来释放动态内存的空间的,而如果对非动态开辟内存使用free释放,等待你的就是这个:

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

void test()
{int *p = (int *)malloc(100);p++;free(p);//p不再指向动态内存的起始位置
}

在执行malloc函数分配内存后,指针p指向动态内存的起始位置。然而,在将指针p增加1后,它不再指向分配的内存起始位置。因此,当在之后使用free函数来释放内存时,由于free函数要求传入指向malloc分配内存起始位置的指针,传入p将导致未定义的行为。这可能会导致程序崩溃或出现其他问题。

2.5 对同一块动态内存多次释放

void test()
{int *p = (int *)malloc(100);free(p);free(p);//重复释放
}

重复释放同一内存块会导致未定义的行为,可能会导致程序崩溃或出现其他问题。 

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

void test()
{int *p = (int *)malloc(100);if(NULL != p){*p = 20;}
}
int main()
{test();while(1);
}

内存泄漏指的是程序在执行期间申请了一定量的内存空间,但在使用完毕后没有及时释放,导致这部分内存空间永远无法被程序使用,从而浪费了宝贵的系统资源。所以我们一定要及时释放掉申请的空间,这样才能“有借有还再借不难”。

总结

学习动态内存是如何分配的可以让我们以后写出来的程序更加高效灵活,这也是我们作为程序员的基本素养,同时,我们也应该注意正确使用动态内存分配,避免一些常见的错误。

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

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

相关文章

【力扣-每日一题】714. 买卖股票的最佳时机含手续费

class Solution { public:int maxProfit(vector<int>& prices, int fee) {//[i][0]-不持有 [i][1]-持有int mprices.size();vector<vector<int>> dp(m,vector<int>(2));dp[0][0]0; //初始状态dp[0][1]-prices[0];for(int i1;i<m;i){dp[i]…

TDengine+OpenVINO+AIxBoard,助力时序数据分类

时间序列数据分析在工业&#xff0c;能源&#xff0c;医疗&#xff0c;交通&#xff0c;金融&#xff0c;零售等多个领域都有广泛应用。其中时间序列数据分类是分析时序数据的常见任务之一。本文将通过一个具体的案例&#xff0c;介绍 Intel 团队如何使用 TDengine 作为基础软件…

react-antd 文件导入按钮增加一个加载状态

1、效果图实例: 2、部分代码 2.1 props : 2.2 handleChange、上传的文件检验 : construction中定义 construction(props) { super(props); this.state { loadingStaus: flase, loadingDisabled: flase, // 作用:按钮如果在加 载状态中&#xff0c;没…

【Vue面试题十一】、Vue组件之间的通信方式都有哪些?

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;Vue组件之间的通信方式都…

IDEA-2023-jdk8 HelloWorld的实现

目录 1 新建Project - Class 2 编写代码 3 运行 1 新建Project - Class 选择"New Project"&#xff1a; 指名工程名、使用的JDK版本等信息。如下所示&#xff1a; 接着创建Java类&#xff1a; 2 编写代码 public class HelloWorld {public static void main(S…

javaee SpringMVC文件上传 项目结构

引入依赖 <?xml version"1.0" encoding"UTF-8"?><project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0…

爱普生LQ1900KIIH复位方法

爱普生EPSON 1900KIIH是一部通用针式打印机&#xff0c;136列&#xff08;10cpi下&#xff09;的打印宽度&#xff0c;缓冲区128KB&#xff0c;打印速度为270字/秒。 打印机类型 打印方式&#xff1a;24针击打式点阵打印、打印方向&#xff1a;双向逻辑查找、安全规格标准&am…

ChatGPT 背后包含了哪些技术?

ChatGPT 是由OpenAI开发的一款基于GPT-3&#xff08;Generative Pre-trained Transformer 3&#xff09;的人工智能语言模型。这个模型是使用多种编程语言和技术组合编写的。 首先&#xff0c;ChatGPT 使用了 Python 作为主要的编程语言。Python 是一种流行的高级编程语言&…

(StackOverflow)使用Huggingface Transformers从磁盘加载预训练模型

这是在Stack Overflow上的一个问答&#xff0c;链接如下&#xff1a; Load a pre-trained model from disk with Huggingface Transformers - Stack Overflowhttps://stackoverflow.com/questions/64001128/load-a-pre-trained-model-from-disk-with-huggingface-transformers…

python笔记:pandas/geopandas DataFrame逐行遍历

在Pandas和GeoPandas中&#xff0c;可以使用几种不同的方法来遍历DataFrame的每一行 0 数据 import pandas as pddata {column1: range(1, 1001),column2: range(1001, 2001) } df pd.DataFrame(data) df 1 iterrows for index, row in df.iterrows():print(index)print(r…

C语言学生成绩录入系统

一、系统概述 该系统是一个由链表创建主菜单的框架&#xff0c;旨在快速创建学生成绩录入系统的主菜单结构。其主要任务包括&#xff1a; 实现链表的创建、插入和遍历功能&#xff0c;用于存储和展示学生成绩录入系统各个模块的菜单项。 2. 提供用户友好的主菜单界面&#xf…

一文带你了解IAM(身份与访问管理)

一文带你了解IAM&#xff08;身份与访问管理&#xff09; 在进入新公司时&#xff0c;领导说让我们改版之后采用IAM的方式进行资源管理&#xff0c;what&#xff1f;IAM是什么&#xff1f;恕我无知了。后来查阅了相关资料才知道。 IAM不仅是一种产品&#xff0c;也是大部分产品…