c语言进阶篇_动态内存管理(数组可以自动扩容?)

简介: c语言进阶篇_动态内存管理(数组可以自动扩容?)

一、动态内存管理是什么?


如果我们需要创建一个变量,可以直接通过类型名+变量名创建即可.此时会自动向内存申请该类型所需要的的字节空间,例如:int a=0;


该语句会自动向内存申请四个字节的空间(64位机器下),那么如果我们需要多个变量呢?


很显然,在之前,我们就学过数组,数组可以解决创建多个变量的问题,但是,即使是数组也存在一个缺陷.


那就是在创建数组时,我们必须要先确定数组的大小,这样操作系统才会去向内存申请固定大小的字节空间.


而在很多情况下,我们并不能确定要存储的变量个数,这是很常见的问题,


🌰例如:


外卖平台并不能提前知道今天的订单量,淘宝商家也一样不能预测今天商品的销售量等等.


包括我们之前讲解的通讯录简易版我们并不能事先知道要添加的联系人个数,此时用数组去存储,很难确定开多大的数组,开大了浪费,开小了不够用.


  为了解决这个尴尬的问题,c语言提供了一些可以申请内存空间的函数,这些函数被称为动态内存函数.malloc函数,calloc函数以及realloc函数.


二、内存操作函数


2.1 malloc函数与free函数


malloc


文档查询链接:


函数原型:



参数介绍:


参数 意义
size 要申请的字节个数(记住单位是字节)


函数作用:


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


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


如果申请失败:则返回一个NULL指针,所以我们在使用malloc函数申请空间时,要判断返回值是否为空,空指针则代表申请失败。当然这种情况是很少发生的,但是作为一名合格的程序员,还是建议加上返回值的判断,这也是对程序员自己的帮助.


返回值解释:


返回值的类型是 void* ,因为我们在使用malloc函数申请空间时可以给多种类型赋值,不能限制返回值的类型,在具体使用时,强制转换为需要的类型即可.


示例:


#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
  //向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
  int* a = (int*)malloc(10 * sizeof(int));
  if (a == NULL)
  {
    perror("malloc a fail");//申请失败时,打印错误信息
    return 0;
  }
  //向内存申请2个双精度形所占的字节个数的空间,通过强转为int*后赋值给b
  double* b = (int*)malloc(2 * sizeof(double));
  if (b == NULL)
  {
    perror("malloc b fail");//申请失败时,打印错误信息
    return 0;
  }
  return 0;
}


例图:



还有人很调皮,将size设置为0,malloc(0);这就让编译器很无奈,这种行为是未定义的,0就是不申请空间吗?


不申请你找我(malloc)干嘛?咱还是规规矩矩的写代码,做一个乖孩子吧.


free


我们在介绍free函数之前,先简单介绍一下内存部分分区吧!


栈区:


用于存放局部变量,函数参数等临时变量.


堆区:(今天的重点)


是用于供程序员申请的内存区,malloc函数,calloc函数和realloc函数就是在这里申请内存空间.


静态区:


用于存放全局变量和静态变量.



当我们在自定义一个函数时,会在栈区上开辟一块空间给该函数,当函数调用结束,为函数开辟的空间就会被收回,则其中的变量也会被销毁.但是malloc函数申请的空间不会,因为它是在堆区上申请的空间,需要申请者自己去释放,而这项操作就需要使用函数free.


函数模型:



free函数只用来释放动态开辟的内存即用malloc、calloc以及realloc开辟的空间。


参数说明:


如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。同样还是那句话,不和规则的事咱就不要做了.


如果参数 ptr 是NULL指针,则该函数不会进行任何操作.


说了这么多,我们实践操作一下吧!


#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
  int* a = (int*)malloc(10 * sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
  if (a == NULL)
  {
    perror("malloc is fail");//申请失败时,打印错误信息
    return 0;
  }
  //赋值
  for (int i = 0; i < 10; i++)
  {
    a[i] = i;
  }
  //打印
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", a[i]);
  }
  free(a);
  a = NULL;
  return 0;
}


运行结果:


0 1 2 3 4 5 6 7 8 9


在为赋值之前,我们观察一下a空间中存放的值,明显是一些未初始化而产生的的"随机值".


赋值前:



赋值后:



注意:


free(a)后,a指针所指向的内存空间就被释放掉了,后续就不能使用了,则应当为了防止出现空指针,则需要进行"置空"操作.a = NULL;


2.2 calloc函数


函数模型:



参数说明:


num:要申请的元素个数.


size:一个元素所占的内存大小.


将上面的malloc代码改成calloc函数后:


#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
  int* a = (int*)calloc(10 , sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
  if (a == NULL)
  {
    perror("calloc is fail");//申请失败时,打印错误信息
    return 0;
  }
  //赋值
  for (int i = 0; i < 10; i++)
  {
    a[i] = i;
  }
  //打印
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", a[i]);
  }
  free(a);
  a = NULL;
  return 0;
}


该函数的重点是能理解与malloc函数区别.


赋值前:



赋值后:



malloc与calloc的区别:


很明显,相比于malloc函数,在申请空间成功后,calloc函数会将申请到的空间全部初始化为0.这里可能有人要问,为啥我们不直接用calloc函数,还需要malloc函数干嘛?


原因是malloc是只需要将空间申请下来就行,而calloc函数还需要清理空间(都初始化为0),这样calloc函数的执行效率就没有malloc快.在很多情况下,我们并不需要初始化为0,这时候直接使用mallo函数就行,效率会高一些.


总结:


malloc calloc
申请到的空间未被初始化 申请到的空间全部被初始化为0.
执行效率相对较高 执行效率相对较低


2.3 realloc函数


函数原型:



参数说明:


参数 意义
ptr 需要重新分配内存空间的地址
size 重新分配后内存空间的大小


函数功能:


realloc函数就是为了使得动态内存函数更加配得上"动态"之词的函数.


回到之前的问题,有时会我们发现过去申请的空间太小了,有时候我们又会发现申请的空间过大了导致内存浪费,那为了合理的申请内存.我们需要对内存的大小做灵活的调整。


realloc函数就是重新分配之前开辟的空间大小.


返回值:


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


很重要!!!


这时有两种情况:


①:原地扩容:


原地址后面有足够的空间支持扩容.这时,会占用后面未被分配的内存空间用于扩容.


②:异地扩容:


原地址后面的内存空间不够支持扩容,则需要找到另外一块内存空间,将数据拷贝过去,然后再扩容.返回新的地址.


图解:



三、动态内存函数操作不当造成的错误:


(1)访问空指针


对申请的空间忘记进行NULL指针判断,导致访问空指针


这里一次申请大量的内存空间,内存没有那么多,会申请失败,返回NULL指针.


#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
  int* a = (int*)malloc(100000000000000*sizeof(int));
  //赋值
  for (int i = 0; i < 10; i++)
  {
    a[i] = i;
  }
  //打印
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", a[i]);
  }
  free(a);
  a = NULL;
  return 0;
}


运行结果:



(2)对同一块空间就行多次释放:


#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
  int* a = (int*)calloc(10, sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
  if (a == NULL)
  {
    perror("malloc a fail");//申请失败时,打印错误信息
    return 0;
  }
  free(a);
  //中间含有大量代码
  free(a);//导致忘记已经释放过了
  a = NULL;
  return 0;
}


(3)向释放申请空间的一部分:


申请的空间不能释放其中的一部分,只能一次全部释放.


#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
  int* a = (int*)calloc(10, sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
  if (a == NULL)
  {
    perror("malloc a fail");//申请失败时,打印错误信息
    return 0;
  }
  int* b = a + 3;
  free(b);
  b = NULL;
  return 0;
}


(4)内存泄漏(重点):


忘记释放在堆区上释放的空间


#include <stdio.h>
#include <stdlib.h>//malloc函数所需要的头文件
int main()
{
  int* a = (int*)calloc(10, sizeof(int));//向内存申请10个整形所占的字节个数的空间,通过强转为int*后赋值给a
  if (a == NULL)
  {
    perror("malloc a fail");//申请失败时,打印错误信息
    return 0;
  }
  //赋值
  for (int i = 0; i < 10; i++)
  {
    a[i] = i;
  }
  //打印
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", a[i]);
  }
  return 0;
}


此时,程序并不会报错,但是这时会出现一个很严重的问题,那就是内存泄漏,用malloc函数申请的空间并没有被释放,导致一直占用内存空间.


当然,在程序结束时,系统会自动回收这些未被释放的空间,但是对于一些大型的程序或者在特定情况下,这是非常可怕的.


例如:


1)如果内存泄漏发生在手机上,一次泄漏一点点,手机长期不关机,几天或者几个星期之后,运行内存都被挤满了,会导致手机特别卡.


2)大型服务器是开机后,除了维修或者老化被替代,都是一直不关机的,此时内存泄漏是很可怕的,造成的损失也特别严重.


四、柔性数组与变长数组.


什么是柔性数组?


可能有人在此之前并没有听过柔性数组这个词.


柔性数组表示,在进行定义结构体类型时,结构体的最后一个成员可以是一个不指定大小的数组,这个数组就被称为柔性数组.


例如:


typedef struct test
{
  char name[10];
  int data[];//柔性数组
  //也可以写成int data[0];
}test_struct;


柔性数组的规则:


1.柔性数组前面至少要有一个成员变量,且柔性数组是最后一个成员.


2.在用sizeof对结构体进行计算时,不会计算柔性数组的大小.


3.柔性数组不能直接使用,需要malloc函数进行分配时分配,且分配的大小必须比不计算柔性数组所占的空间要大,要给柔性数组预留空间.


#include <stdio.h>
#include <stdlib.h>
//定义一个包含柔性数组的结构体
typedef struct test
{
  char name[10];
  int data[];
  //也可以写成int data[0];
}test_struct;
int main()
{
  //创建一个结构体指针,并未柔性数组分配空间
  test_struct* test1 = (test_struct*)malloc(sizeof(test_struct)+ 10 * sizeof(int));
  //sizeof(test_struct)表示不计算柔性数组时结构体所占内存大小.
  //10 * sizeof(int)表示在此基础上再增加10个整形的字节空间,会分配给柔性数组.
  //初始化柔性数组
  for (int i = 0; i < 10; i++)
  {
    test1->data[i] = i;
  }
  //打印
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", test1->data[i]);
  }
  return 0;
}


柔性数组的优点:


1.由于是连续的内存空间,所以释放时可以一次性释放,不需要分两次释放.


2.同样是因为连续的空间,访问速度较于不连续空间速度更快,因为寄存器一次性读取数据是按连续内存读取的,不连续则需要读取多次.


变长数组:


在c99标准中支持可以用变量来定义数组的大小.


即:


int main()
{
  int a = 10;
  int arr[a];
  return 0;
}
目录
相关文章
|
25天前
|
存储 编译器 程序员
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
39 5
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
|
25天前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
54 6
|
25天前
|
传感器 算法 安全
【C语言】两个数组比较详解
比较两个数组在C语言中有多种实现方法,选择合适的方法取决于具体的应用场景和性能要求。从逐元素比较到使用`memcmp`函数,再到指针优化,每种方法都有其优点和适用范围。在嵌入式系统中,考虑性能和资源限制尤为重要。通过合理选择和优化,可以有效提高程序的运行效率和可靠性。
78 6
|
28天前
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
46 6
|
28天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
54 5
|
29天前
|
大数据 C语言
C 语言动态内存分配 —— 灵活掌控内存资源
C语言动态内存分配使程序在运行时灵活管理内存资源,通过malloc、calloc、realloc和free等函数实现内存的申请与释放,提高内存使用效率,适应不同应用场景需求。
|
28天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
29天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
27天前
|
并行计算 算法 测试技术
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面
C语言因高效灵活被广泛应用于软件开发。本文探讨了优化C语言程序性能的策略,涵盖算法优化、代码结构优化、内存管理优化、编译器优化、数据结构优化、并行计算优化及性能测试与分析七个方面,旨在通过综合策略提升程序性能,满足实际需求。
61 1
|
1月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。

热门文章

最新文章