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

简介: 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语言:动态内存管理
9 1
|
2天前
|
存储 编译器 程序员
C语言:数据在内存中的存储
C语言:数据在内存中的存储
10 2
|
2天前
|
存储 编译器 C语言
C语言:字符函数 & 字符串函数 & 内存函数
C语言:字符函数 & 字符串函数 & 内存函数
11 2
|
11天前
|
存储 C语言 开发者
【C言专栏】C 语言实现动态内存分配
【4月更文挑战第30天】C语言中的动态内存分配允许程序运行时按需分配内存,提供处理未知数据量的灵活性。这涉及`malloc()`, `calloc()`, `realloc()`, 和 `free()`四个标准库函数。`malloc()`分配指定大小的内存,`calloc()`同时初始化为零,`realloc()`调整内存大小,而`free()`释放内存。开发者需谨慎处理内存泄漏和指针使用,确保程序的稳定性和性能。动态内存分配是C语言中的重要技能,但也需要良好的内存管理实践。
|
12天前
|
存储 程序员 C语言
C语言进阶第九课 --------动态内存管理-2
C语言进阶第九课 --------动态内存管理
|
12天前
|
编译器 C语言
C语言进阶第九课 --------动态内存管理-1
C语言进阶第九课 --------动态内存管理
|
17天前
|
程序员 编译器 C语言
C语言知识:动态内存管理(malloc free calloc realloc)
C语言知识:动态内存管理(malloc free calloc realloc)
|
17天前
|
存储 编译器 C语言
C语言基础知识:数据在内存中的存储解析(整数,浮点数)
C语言基础知识:数据在内存中的存储解析(整数,浮点数)
|
17天前
|
C语言
C语言:内存函数(memcpy memmove memset memcmp使用)
C语言:内存函数(memcpy memmove memset memcmp使用)
|
11天前
|
缓存 安全 编译器
【C 言专栏】C 语言函数的高效编程技巧
【5月更文挑战第1天】本文探讨了C语言中函数的高效编程技巧,包括函数的定义与作用(如代码复用和提高可读性)、设计原则(单一职责和接口简洁)、参数传递方式(值传递、指针传递和引用传递)、返回值管理、调用约定、嵌套与递归调用,以及函数优化技巧和常见错误避免。掌握这些技巧能提升C语言代码的质量和效率。
【C 言专栏】C 语言函数的高效编程技巧