C进阶-动态内存管理+柔性数组(1)

简介: C进阶-动态内存管理+柔性数组

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

根据我们学过的知识,要在内存中开辟一个空间,有两种方式:

int c = 10;//创建一个变量
  int arr[10] = { 0 };//创建一个数组

但是上述开辟空间的方式有两个局限性:

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

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

但是有时候,我们只有在运行程序的时候,才能知道自己需要多大的空间,这时候空间已经开辟好了,不能再改变了,有可能大了,也有可能小了,所以上述开辟方式可能会在我们使用的时候带来极大的不便,这就是我们需要动态内存开辟的原因,动态内存管理会根据我们的需要,对开辟的空间进行放大和缩小。

2.动态内存函数的介绍

动态内存管理函数有4个:malloc、calloc、realloc、free

2.1 malloc和free

void*  malloc( size_t size );

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

int main()
{
  int arr[10] = { 0 };
  int* p = (int*)malloc(40);
  return 0;
}

上述代码中,我们用数组申请了10个整型的空间,也可以用malloc向内存申请10个整型的空间(40个字节) 。

注意我们将这块空间的地址赋给指针p时,指针p是int*型,而malloc函数的返回类型是void*,所以要强制类型转化为int*。

这样我们就用malloc申请了空间,下面我们就可以直接使用了吗?

可能不行,因为malloc申请空间也是会失败的:

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

如果开辟失败,则返回一个NULL指针

因此malloc的返回值一定要做检查:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");//打印错误信息
    return 1;
  }
  //开辟成功
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%d ", *(p + i));
  }
  return 0;
}

用if语句判断是否开辟成功,如果开辟失败,则用perror函数打印错误信息,如果开辟成功,则打印出来。(注意使用perror时,要包含头文件<stdlib.h>

下面我们再来讲一下malloc函数在内存中开辟的空间的具体体现:

之前我们学过,内存中分为栈区、堆区、静态区。栈区存放的是局部变量和形式参数等,静态区存放的是全局变量和静态变量等,那堆区存放的是什么呢?

就是动态内存开辟。

malloc函数申请的空间在堆区,指针变量p开辟的空间在栈区,存放所申请空间的地址。

malloc申请到空间后,直接返回这块空间的地址,不初始化空间的内容,所以上述代码打印的结果是:

而当程序退出时,malloc申请的内存空间不会主动还给操作系统,这时候就需要用free函数了:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");//打印错误信息
    return 1;
  }
  //开辟成功
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%d\n", *(p + i));
  }
  //释放空间
  free(p);
  p = NULL;
  return 0;
}

当free释放掉malloc申请的内存空间后,此时栈区的指针变量p还在啊,它里面还从存放着已经释放掉空间的地址,如果我们不对它进行处理,那p就成了野指针,所以在free(p)后面还令p=NULL。

我们在使用free函数时也要注意,不是动态内存开辟的空间不能用free函数释放,如下面的写法就是错误的:

int p = 0;
  int* ptr = &p;
  free(ptr);//error

2.2 calloc

void* calloc( size_t num, sizr_t size )

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

与函数malloc函数的区别是calloc函数会在返回前将申请的空间的每一个字节初始化为0。

看下面一段代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)calloc(40, sizeof(int));
  if (p == NULL)
  {
    perror("calloc\n");
    return 1;
  }
  //打印数据
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%d ", p[i]);
  }
  free(p);
  p = NULL;
  return 0;
}

运行结果:

我们可以看到calloc函数开辟空间后确实将空间中每个字节初始化为全0

当然,我们也可以来看一下开辟失败的结果:

以上就是calloc函数,它和malloc函数有区别,但是功能差不多,下面我们来讲最最重要的realloc函数:

2.3 realloc

realloc函数让动态内存管理更加灵活。

有时候我们发现申请的空间太小了,有时候我们又会觉得申请的空间太小了,那为了合理利用空间,我们一定会对内存的大小进行调整,那realloc函数就可以做到对动态开辟内存大小的调整。

vioe* realloc( void* ptr,size_t size )

ptr是要调整的内存地址,size是调整之后的大小

realloc函数的返回值是调整之后的内存起始地址,这个地址有可能和之前开辟空间的地址一样,也有可能是一个新的地址,为什么这么说呢?

这就要提到realloc函数调整空间的两种情况了。

假设我们已经用malloc函数开辟了40个字节的空间,但是我们用的时候觉得不够了,需要再增加40个字节的空间,此时要用realloc将空间大小调整为80个字节,但是realloc在调整时会出现如下两种情况:

下面来看一段代码:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  //初始化1~10
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    p[i] = i + 1;
  }
  //增加空间
  p = realloc(p, 80);
  free(p);
  p = NULL;
  return 0;
}

上述代码中,用p直接来接收realloc的返回地址行不行?

当然不行,要知道realloc函数开辟空间也会失败的,要是开辟成功了,我们用p接收可以,但是要是开辟失败了,这时realloc函数就会返回一个空指针NULL,那此时我们malloc函数开辟的空间的起始地址也是p啊,里面还存放着10个值呢?要是用p接收了空指针,我们这些数据该怎么办?

所以最好为realloc函数返回的新空间的起始地址重新创建一个指针变量ptr,经过判断后再将ptr赋给p:

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  //初始化1~10
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    p[i] = i + 1;
  }
  //增加空间
  int*ptr = (int*)realloc(p, 80);
  if (ptr != NULL)
  {
    p = ptr;
        ptr = NULL;
  }
  else
  {
    printf("realloc");
    return 1;
  }
  //打印数据
  for (i = 0; i < 20; i++)
  {
    printf("%d\n", p[i]);
  }
  //释放空间
  free(p);
  p = NULL;
  return 0;
}

打印结果(可以看到前10个数据还在,后面又开辟了10个int型大小的空间):

以上就是使用realloc函数增加空间,要想减少空间的话,将传给size的值变小点就行了。

还有一点,要是传给realloc函数参数ptr的是空指针NULL,此时realloc函数和malloc函数的功能一样。

以上就是动态内存管理的4个函数的介绍。

3.常见的动态内存错误

3.1 对NULL指针的解引用操作

int main()
{
  int* p = (int*)malloc(INT_MAX);
  *p = 20;//如果p是NULL,就会出现问题
  free(p);
  return 0;
}

这个我们上文也讲过,要对p进行判断,是不是空指针,如果不是再使用。

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  int i = 0;
  for (i = 0; i < 20; i++)
  {
    p[i] = i + 1;//开辟了10个整型,访问20个整型,越界访问了
  }
  free(p);
    p = NULL;
  return 0;
}

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

上文中讲过:

int p = 0;
  int* ptr = &p;
  free(ptr);//error

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    *p = i ;
    p++;
  }
  free(p);
  p = NULL;
  return 0;
}

上述代码中,我们使用了开辟的空间中的前5个元素,但是注意在使用后进行p++,那当我们运行完,p指向的就是第5个元素所在空间的地址,此时再用free释放,释放的是第5个元素后面的空间,这样程序会崩溃,也就是说,不能使用free释放一块动态开辟内存的一部分

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

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  free(p);
  //
  free(p);
  p = NULL;
  return 0;
}

我们在写代码的时候很可能出现,前面已经对这块内存释放过了,后面忘记了,又释放了一次,这时就出现错误了,所以最好养成一个习惯,就是在每次释放之后,将p置为NULL,这样即使重复释放,后面的free也没有任何作用。

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  free(p);
  p = NULL;
  free(p);
  p = NULL;
  return 0;
}
目录
相关文章
|
9月前
|
编译器
动态内存管理与柔性数组 1
动态内存管理与柔性数组
33 0
|
9月前
|
程序员 C语言 C++
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(下)
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(下)
29 0
|
9月前
|
程序员 编译器 C语言
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(上)
动态内存管理函数的使用与优化技巧(内存函数、柔性数组)(上)
40 0
|
9月前
|
程序员 编译器 C语言
动态内存函数,内存开辟,柔性数组(超详细)
动态内存函数,内存开辟,柔性数组(超详细)
48 0
|
9月前
|
编译器 程序员 测试技术
详解动态内存管理【malloc/calloc/realloc/free函数/柔性数组】【C语言/进阶/数据结构基础】
详解动态内存管理【malloc/calloc/realloc/free函数/柔性数组】【C语言/进阶/数据结构基础】
180 0
|
9月前
|
编译器 程序员 C语言
【C语言】动态内存管理(malloc,free,calloc,realloc,柔性数组)
【C语言】动态内存管理(malloc,free,calloc,realloc,柔性数组)
|
8天前
|
程序员 C语言 C++
【C语言】:柔性数组和C/C++中程序内存区域划分
【C语言】:柔性数组和C/C++中程序内存区域划分
9 0
|
2月前
|
存储 安全 编译器
【C语言】动态内存管理 -- -- 深入了解malloc、calloc、realloc、free、柔性数组(万字深入了解)
【C语言】动态内存管理 -- -- 深入了解malloc、calloc、realloc、free、柔性数组(万字深入了解)
18 0
【C语言】动态内存管理 -- -- 深入了解malloc、calloc、realloc、free、柔性数组(万字深入了解)
|
2月前
|
程序员 编译器 C语言
C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(下)
C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free
28 0
C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(下)
|
2月前
|
编译器 数据库 C语言
C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(上)
C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free
27 0
C语言进阶⑰(动态内存管理)四个动态内存函数+动态通讯录+柔性数组_malloc+free(上)