C语言动态内存管理(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: C语言动态内存管理(一)

本篇重点内容;

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

2.动态内存函数的介绍

✨malloc

✨free

✨calloc

✨realloc

3.常见的动态内存错误

4.几个经典的笔试题

5.柔性数组


一、为什么存在动态内存分配?

我们已经掌握的内存开辟方式有:

创建一个变量或者创建一个数组

但是上面这样的开辟空间的方式有两个特点:

1.空间开辟的大小是固定的

2.数组在申明的时候,必须指明数组的长度,它所需要的内存在编译时分配

实际上,对于空间的需求,有时候我们需要的空间大小在程序运行时才能知道,那么上述数组的开辟方式就不能满足了。

这时就需要使用动态内存开辟。

二、动态内存函数的介绍

1.malloc

malloc头文件<stdlib.h>

C语言提供了一个动态内存开辟的函数

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

(1)如果开辟成功,则返回一个指向开辟好空间的指针。

(2)如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。(注意开辟失败是因为malloc不会无节制的开辟下去)

对malloc的返回值进行判断:

if(p= =Null){
printf(“%s\n”,strerror(errno));
return 1;
}else{

}

注意:

a. strerror(errno):将错误码对应的错误信息的字符串地址返回

b. strerror 头文件<string.h>

c. errno 头文件<errno.h>

(3)返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

(4)如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。

(5)malloc的使用:

//malloc的使用
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main() {
  //张三
  //申请
  int* p = (int*)malloc(20);
  //对malloc的返回值进行判断
  if (p == NULL) {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //使用
  int i = 0;
  for (i = 0; i < 5; i++) {
    p[i] = i + 1;//相当于*(p+i)=i+1;
  }
  for (i = 0; i < 5; i++) {
    printf("%d ", p[i]);
  }
  //释放malloc开辟的空间并置空
  free(p);
  p = NULL;
  return 0;
}

(6)堆默认给malloc出来的空间初始化

如果不给malloc空间放值,直接打印出来空间的值是一些随机值

//堆默认给malloc出来的空间初始化
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main() {
  //张三
  //申请
  int* p = (int*)malloc(20);
  //对malloc的返回值进行判断
  if (p == NULL) {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //直接打印
  int i = 0;
  for (i = 0; i < 5; i++) {
    printf("%d ", p[i]);
  }
  //释放malloc开辟的空间并置空
  free(p);
  p = NULL;
  return 0;
}

2.free

free头文件<stdlib.h>

专门是用来做动态内存的释放和回收的,函数原型如下:

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

(1)要释放哪块空间就把哪块空间的起始地址传给free

(2)ptr必须指向的是动态开辟的空间,才能用free释放

(3)free空指针,则什么事情也不做

(4)free前,指针ptr本来指向一个地方,释放ptr指针之后,ptr里面还存着一个地址,如果不置空,这个地址被别有用心的人利用,ptr就是野指针了。

所以:释放指针完毕后,将指针主动置为空指针,这样就再也找不到刚刚开辟的空间了,避免出现野指针

3.calloc

calloc函数也用来动态内存分配。原型如下:

callloc(num个元素,每个元素的字节大小)

calloc会开辟一个像数组一样的内存块

注意点:

(1)calloc的功能是为num个大小为size字节的元素开辟一块空间,并且把空间的每个字节初始化为0

(2)calloc与malloc开辟空间的不同点是calloc会在返回地址之前把申请的空间的每个字节初始化为0

(3)calloc与malloc的对比:

a. 参数不一样

b. 都是在堆区上申请内存空间,但是malloc不初始化,calloc会初始化为0

如果要初始化,就使用calloc

如果不需要初始化,就使用malloc

(4)举例使用calloc:

//举例使用calloc
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
int main() {
  int* p = (int*)calloc(10, sizeof(int));
  //判断是否开辟成功
  if (p == NULL) {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //使用空间
  int i = 0;
  for (i = 0; i < 10; i++) {
    printf("%d ", p[i]);
  }
  //释放
  free(p);
  p = NULL;
  return 0;
}

4.realloc

realloc可以对空间进行大小调整

函数原型如下:

realloc(要调整的空间的内存地址,调整后空间的新大小)

返回值为调整之后的内存的起始位置

注意点

(1)realloc调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

(2)realloc在调整内存空间时存在两种情况:

第一种情况:原有空间之后有足够大的空间,realloc后面追加的新的空间是不会赋初值的,只会把旧的数据拷贝下来,此时realloc返回的是旧地址

第二种情况:原有空间之后没有足够大的空间,扩展的方法是在堆空间上另找一个更大的连续空间来使用,将原来的数据拷贝到新的空间,释放旧的空间,此时函数返回一个新的内存地址

(3)堆中开辟的内存不一定是连续的

(4)如果realloc调整的空间要比原空间小, 多余的空间会全释放不使用,还给操作系统

(5)举例使用realloc:

//举例使用realloc:
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
int main() {
  int* p = (int*)malloc(20);
  if (p == NULL) {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //使用
  int i = 0;
  for (i = 0; i < 5; i++) {
    p[i] = i + 1;
  }
  int* ptr = (int*)realloc(p, 40);
  if (ptr != NULL) {
    p = ptr;
  }
  else {
    printf("%s\n", strerror(errno));
    return 1;
  }
  //调整空间后使用
  for (i = 0; i < 10; i++) {
    p[i] = i + 1;
  }
  for (i = 0; i < 10; i++) {
    printf("%d ", p[i]);
  }
  //释放malloc开辟的空间
  free(p);
  p = NULL;
  return 0;
}

三、常见的动态内存错误

1.对NULL指针的解引用操作

malloc/calloc动态开辟失败,没有检查返回值

举例说明:

解析:

如果动态开辟内存失败,p指针就是空指针,当i=0时,对p指针(空指针)解引用出问题,不能对空指针解引用

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

动态开辟申请了多少字节就访问多少字节,不要越界

举例说明:

解析:

总共开辟了20个字节的整型空间,在访问的时候访问了10个整型(40个字节)的数据,越界访问

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

在内存中开辟的空间如果不在堆上,不能用free释放

举例说明:

解析:

函数里面的局部变量或者数组都是在栈上开辟的空间,不能用free释放

4.使用free释放动态开辟内存的一部分

free释放空间必须提供要释放空间的起始位置

举例说明:

解析:动态开辟的空间的起始位置的指针在使用的过程中被修改,不在再指向起始位置,使用完这块空间用free回收的时候,无法提供动态开辟内存空间的起始位置,程序出错

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

(1)malloc / calloc动态开辟的空间是放过一次就不能再释放了。如果在第一次释放完之后置为空指针,第二次释放相当于释放空指针没有问题
(2)与别的函数交互时,不了解函数内部,很可能在函数内部已经释放过一次,又回到主函数中释放一次导致对同一块动态内存多次释放出错;又或者在函数的内部没有释放动态开辟的空间,回到主函数中也没有释放,导致内存泄漏,程序出错

举例说明:

解析:

连续重复释放同一块动态内存出现问题

释放后置空,再释放是没有问题的,如下:

6.动态开辟内存忘记释放(内存泄露)

指的是动态开辟内存既不使用free释放,也不结束程序时造成的

✨重点✨

(1)malloc / calloc /realloc所申请的空间如果不想使用,需要用free释放。如果不释放,程序结束后,也会由操作系统回收。
(2)如果不使用free释放,程序也不结束,就会造成内存泄漏

意思是不用不还,程序也不结束,别人也无法利用这块空间,等于这块空间浪费掉了

(3)回收内存的两种方式:
a. free掉
b. 程序结束后,由操作系统回收

举例说明:

解析:

申请的20个字节的地址放在p指针里面,p是个局部变量,函数一旦返回,p就销毁不见了,并没有通过p把这20个字节的地址返回来。所以出了函数,来到主函数内部,主函数根本找不到这块内存空间在哪里,想释放都释放不了,造成了内存泄漏

相关文章
|
2月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
|
29天前
|
存储 大数据 C语言
C语言 内存管理
本文详细介绍了内存管理和相关操作函数。首先讲解了进程与程序的区别及进程空间的概念,接着深入探讨了栈内存和堆内存的特点、大小及其管理方法。在堆内存部分,具体分析了 `malloc()`、`calloc()`、`realloc()` 和 `free()` 等函数的功能和用法。最后介绍了 `memcpy`、`memmove`、`memcmp`、`memchr` 和 `memset` 等内存操作函数,并提供了示例代码。通过这些内容,读者可以全面了解内存管理的基本原理和实践技巧。
|
29天前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
2月前
|
存储 程序员 C语言
【C语言】动态内存管理
【C语言】动态内存管理
|
2月前
|
存储 编译器 C语言
C++内存管理(区别C语言)深度对比
C++内存管理(区别C语言)深度对比
72 5
|
2月前
|
C语言
C语言动态内存管理
C语言动态内存管理
30 4
|
2月前
|
存储 NoSQL 程序员
C语言中的内存布局
C语言中的内存布局
37 0
|
2月前
|
C语言
【C语言篇】字符和字符串以及内存函数详细介绍与模拟实现(下篇)
perror函数打印完参数部分的字符串后,再打印⼀个冒号和⼀个空格,再打印错误信息。
|
2月前
|
存储 安全 编译器
【C语言篇】字符和字符串以及内存函数的详细介绍与模拟实现(上篇)
当然可以用scanf和printf输入输出,这里在之前【C语言篇】scanf和printf万字超详细介绍(基本加拓展用法)已经讲过了,这里就不再赘述,主要介绍只针对字符的函数.
|
3月前
|
存储 缓存 C语言
【C语言】字符函数,字符串函数,内存函数
C语言中的字符串函数和内存函数
35 0
【C语言】字符函数,字符串函数,内存函数