当前位置 : 主页 > 编程语言 > c语言 >

【一文教你学会动态内存管理】

来源:互联网 收集:自由互联 发布时间:2023-09-03
1.为什么会存在动态内存分配? 2. 动态内存函数的介绍 2.1 malloc函数和free函数 2.2 calloc函数 2.3 realloc 3. 常见的动态内存错误 3.1 对NULL指针的解引用操作 3.2 对动态开辟空间的越界访问

1.为什么会存在动态内存分配?

2. 动态内存函数的介绍

  • 2.1 malloc函数和free函数
  • 2.2 calloc函数
  • 2.3 realloc

3. 常见的动态内存错误

  • 3.1 对NULL指针的解引用操作
  • 3.2 对动态开辟空间的越界访问
  • 3.3 对非动态开辟内存使用free释放
  • 3.4 使用free释放一块动态开辟内存的一部分
  • 3.5 对同一块动态内存多次释放
  • 3.6 动态开辟内存忘记释放(内存泄漏)

1.为什么会存在动态内存分配? 我们现在知道的开辟内存方式是创建变量,创建数组。而这些东西是在栈区上开辟空间的,一旦创建完成,是无法更改的,是固定死的。就像数组,在创建数组时,已经指定了数组的大小,编译后无法再更改。这时候就需要动态内存分配。

2. 动态内存函数的介绍

2.1 malloc函数和free函数 1.malloc函数学习新函数时,就从库里面找函数的声明,解析来学习。void* malloc (size_t size);该函数的功能是,向堆区申请一块size个字节大小的空间。如果申请成功,返回申请的空间的起始地址,如果申请失败,返回空指针NULL。 至于malloc函数在库里的为什么是void*,这是因为malloc函数也不知道使用者需要这块空间来存放什么,具体要什么类型的指针,使用者自己决定。如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。下面举个例子来深入了解: 在这段代码中,我们用了malloc函数来社区内40个字节的空间,(一个int是4个字节,*10是40字节) 但是这样写是不对的,前面说过,malloc是向内存中的堆区申请空间,如果申请失败,就返回NULL,如果现在不判断p的值,直接使用的话,就会出问题,所以我们应该判断p的有效性。顺便提一下,perror是一个报错的函数,假如申请空间失败返回NULL,perror会报告相应的错误信息。来演示一下: 假如申请INT_MAX个字节的空间: 这是一个极大的数字,看看是否能够申请成功。 在这里,perror报告错误:没有足够的空间。具体的用法请学习一下。

free函数 free函数和malloc函数是成对出现的。free函数是专门用来释放申请的空间的。具体用法如下:int main() { int* p = (int *)malloc(sizeof(int)*10); if (p == NULL) { perror("malloc"); } free(p); p = NULL; return 0; }在申请完空间之后,如果不用了,就需要释放掉,把申请的空间还给操作系统,即 有借有还,再借不难 的道理。总结:malloc函数是向堆区申请一块空间,如果申请成功,则返回该空间的首地址,如果申请失败,则返回NULL。free函数是将申请的空间释放掉

2.2 calloc函数 calloc函数也是向堆区中申请内存的函数。具体参数如下:void* calloc (size_t num, size_t size);第一个参数是申请的元素个数,第二个参数是申请的每个元素大小。注意:calloc为元素数组分配一个内存块,并将其所有位初始化为零。也就是说,calloc函数不仅申请空间,还将该空间初始化为0。举个例子来证明:int main() { int *p = (int*)calloc(10, sizeof(int)); if (p == NULL) { perror("calloc"); } for (int i = 0; i < 10; i++) { printf("%d ", *(p + i)); } return 0; }结果如下:结果就是:将申请的空间自动初始化为0。calloc函数与malloc函数类似,只是malloc函数只负责申请空间,没有初始化,而calloc函数会申请空间并初始化。不过也不是说calloc函数比malloc函数更高级,calloc函数 初始化也会花费时间,malloc函数不初始化,所以它也节省了一部分时间。每个函数之间各有优缺点。所以在使用函数的时候,结合实际情况来使用。

realloc函数,让我们动态申请的空间更加灵活。当我们觉得动态申请的空间太大或者太小时,可以用realloc函数来调整动态申请的空间大小。realloc函数的原型如下:void* realloc (void* ptr, size_t size);ptr是需要重新调整的空间的起始地址, size 调整之后新大小 注意:是调整后的大小假如刚开始动态申请的空间是40字节,发现不够用了,想加大10个字节的空间,那么使用realloc函数重新调整空间时,参数就是50。realloc函数的返回值是调整后的空间的起始地址。既然返回调整后的起始地址,那会不会出现申请失败的情况?会不会出现空间不足以申请的情况? 会的。情况1: 重新申请空间时,假如后面的空间足够大,那么realloc函数就会返回空间的首地址。原来有的数据不会变化: 情况2: 如果重新申请空间失败,则返回NULL。情况3:****如果在原来的空间后面没有足够大的空间来增容,realloc函数会自动在内存中的其他位置找一块满足我们要求的空间,并把原空间的数据拷贝到新空间中,然后返回新空间的起始地址。 基于情况2和情况3,我们是不是用原空间的指针来接收呢?int main() { int* p = (int*)malloc(sizeof(int) * 5); if (p == NULL) { perror("malloc"); return ; } int i = 0; for (i = 0; i < 5; i++) { *(p + i) = i; } 不够了,增加空间 int* p = (int*)realloc(p, sizeof(int) * 10);还是刚才的例子,上面这段代码对吗? 不对。 因为在重新realloc调整空间的时候,如果用原空间的地址来接收的话,万一申请失败呢? 如果申请失败,realloc函数会返回一个NULL,如果用p来接收的话,p原来指向的空间的数据就丢失了!就找不到了。 所以不能用原空间p来接收realloc返回的地址,我们需要用一个临时指针来接收返回的地址。int* ptr = (int*)realloc(p, sizeof(int) * 10);这样写才是正确的,然后判断ptr是否为空,如果不为空,再把这个ptr存的地址赋给p。if (ptr != NULL) { p = ptr; ptr = NULL;(防止ptr成为野指针) }以上就是三个申请空间的函数的基本情况,根据需求,选择不同的函数。

3. 常见的动态内存错误

3.1 对NULL指针的解引用操作 void test() { int* p = (int*)malloc(INT_MAX); *p = 20; free(p); } int main() { test(); return 0; }这段代码的错误是,没有判断p的值就对p进行解引用,如果申请的空间失败,返回NULL,对NULL进行*操作,是非法的。改正很简单:只需判断p是否为NULL即可。void test() { int* p = (int*)malloc(INT_MAX / 4); if (p == NULL) { perror("malloc"); return; } *p = 20; free(p); p = NULL; }改正结果如上。

3.2 对动态开辟空间的越界访问 void test() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { perror("malloc"); return; } for (i = 0; i < 20; i++) { *(p + i) = i;//当i是10的时候越界访问 } free(p); p = NULL; } int main() { test(); return 0; }上面这段代码的错误是,malloc函数只申请了10个整型大小的空间,(40)字节,但是在赋值的时候,i的范围取到了20,造成对后面的空间非法访问。改正如下:只需把i的范围调整到i<10即可,或者最初申请的空间到20个整型大小(80字节)void test() { int i = 0; int* p = (int*)malloc(10 * sizeof(int)); if (NULL == p) { perror("malloc"); return; } for (i = 0; i < 10; i++) { *(p + i) = i;//当i是10的时候越界访问 } free(p); p = NULL; }

3.3 对非动态开辟内存使用free释放 void test() { int a = 10; int *p = &a; free(p); p = NULL; } int main() { test(); return 0; }上面代码的错误在于,a是一个变量,在内存中的栈区创建,不是用malloc等函数申请出来的空间,后面free(p)的时候,free释放的是堆区上申请的空间,明显两者有差异。释放是非法的。 运行时也产生错误,并且关掉这个程序会很卡顿。改正方法有多种,可以把free(p)去掉。

3.4 使用free释放一块动态开辟内存的一部分 void test() { int* p = (int*)malloc(100); if(p == NULL) { perror("malloc"); return; } p++; free(p); p = NULL; } int main() { test(); return 0; }上面代码的错误在于,申请的100字节的空间,用p来接收,但是p又++了,此时p不再指向申请的空间的起始地址,释放的时候,只释放一部分,剩下的空间没有释放完,造成内存泄漏。 运行时一样会报错。所以申请的空间的起始地址不能丢失。

3.5 对同一块动态内存多次释放 void test() { int *p = (int *)malloc(100); free(p); free(p);//重复释放 } int main() { test(); return 0; }重复释放也会出现错误。 运行时依然报错。

3.6 动态开辟内存忘记释放(内存泄漏) void test() { int *p = (int *)malloc(100); if(NULL != p) { *p = 20; } } int main() { test(); while(1); }

在test函数内部申请的空间,除了test函数后,没有返回值,意味着申请的这块空间丢失了,没有人记得这块空间的存在,造成了内存泄露!关于动态内存,你学会了吗?学会了不妨关注我吧!

上一篇:QT基础教程之六布局管理器和常用控件
下一篇:没有了
网友评论