我的创作纪念日———C/C++之动态内存管理

简介: 我的创作纪念日———C/C++之动态内存管理

1.前言  

     在上一次的内容中我们写了一个通讯录的小程序,可以点击静态通讯录,在这个程序中存在一些局限性,首先是这个程序只能存固定的个数,一旦超过了这个最大的储存个数就不能存储了,第二个就是当我们退出程序后写的数据就会销毁,不能保存。今天我们就学习如何解决储存上限的问题,想要解决这个问题就需要我们的动态内存规划来解决,这里面包括malloc函数,free函数, calloc函数,realloc函数,接下来就让我们看看其中的乐趣吧。

2.为什么要有动态内存分配

       对于一个数组空间开辟大小是固定的。数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了。简单来说我们开辟的内存是固定的,当不够用或者开辟的空间过多时会造成空间的浪费。

3.malloc函数和free函数

       我们进入cplusplus网站malloc查看malloc函数的参数和返回值

       参数为size_t size这个参数的内容是开辟多少多少字节,返回值为void*由于我们开辟的空间可以是任意类型的,所以为void*类型。

我们同样搜索free函数的参数和返回值,

free函数是专门释放动态开辟的内存,如果参数ptr指向的不是动态开辟的则什么也不做,如果参数ptr指向NULL,什么也不做。我们写一个代码来展示一下malloc函数和free函数的使用,详细代码如下:

#include <stdio.h>
#include <stdlib.h>
int main()
{
  int* p = (int*)malloc(sizeof(int) * 3);
  int i = 0, * ptr = p;
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  for (i = 0; i < 3; i++)
  {
    *ptr = i;
      ptr++;
  }
  ptr = p;
  for (i = 0; i < 3; i++)
  {
        printf("%d ", *ptr);
    ptr++;
  }
  free(p);
  p = NULL;
  return 0;
}

运行结果如下:

4.calloc函数

       我们看到它的第二个参数是size_t size,它是指想要开辟的数据一个元素所占用的空间,第一个参数size_t num是指想要开辟几个大小为size的空间,calloc函数和malloc函数类似,返回值也是void*,我们写一个代码来展示一下calloc函数开辟的空间

#include <stdio.h>
#include <stdlib.h>
int main()
 {
  int* p = (int*)calloc(10, sizeof(int));
  if (p == NULL)
  {
    perror("calloc");
    return 1;
  }
  free(p);
  p = NULL;
  return 0;
}

但是calloc函数和malloc函数有一些不同,calloc函数会讲内存中的值全部初始化为0;

5.realloc函数

       它的第一个参数是想要操作的指针,第二个参数是想要扩容到多少所占的空间,realloc函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。(这里也就是我们通讯录需要扩容操作需要用到的函数)在这里我们通过对通讯录进行扩容来展示一下realloc函数的用法。在这里我们需要增加一个扩容函数详细代码如下:

void addnum(Struct* pc)
{
  if (pc->i == pc->num)
  {
    struct xinxi*ptr = (struct xinxi*)realloc(pc->arr , (pc->num +2)*sizeof(struct xinxi));
    if (ptr != NULL)
    {
      pc->arr = ptr;
      pc->num = pc->num + 2;
    }
    else
    {
      perror("addnum->realloc");
      return;
    }
  }
}

对于realloc函数它有两种开辟空间的方式第一种

如果ptr后面的空间足够扩容的空间,那么会直接在这块空间进行扩容;

第二种         ptr后面的空间不足以进行扩容操作,会重新开辟一个空间,将原来的数据拷贝过来,释放原来的空间,并且重新指向新开辟的空间。

6.小总结

       对于maloc函数和calloc函数和realloc函数,他们都可以被free函数进行释放,他们开辟的空间在堆区,calloc函数开辟后数据全部初始化为0,realloc是专门进行内存扩容。

7.常见的错误(练习)

7.1

void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}

       我们看传的是指针我们直到在创建函数的时候参数是再次创建的,让他进行malloc开辟空间不是让str开辟我们大概可以画为

7.2

char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}

       这里会对野指针的错误引用,在函数中char p[] = "hello world"是在栈区开辟的由于函数结束,内存会销毁,让p成为野指针,从而造成str指向野指针,使程序错误。

7.3

void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}

       在这里由于free()造成str成为野指针,strcpy()对野指针访问,造成错误。

8.柔性数组

       也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。

C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

例如我们创建一个柔性数组:

#include <stdio.h>
#include <stdlib.h>
struct num {
  int i;
  int arr[];
}s;
int main()
{
  size_t sz = sizeof(s);
  printf("%d\n", sz);
}

       其中arr就是柔性数组,柔性数组有哪些特点呢?结构中的柔性数组成员前面必须至少一个其他成员。sizeof 返回的这种结构大小不包括柔性数组的内存。包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。(简单来说定义柔性数组必须在它的前面有至少一个结构体的成员,柔性数组不占用内存,柔性数组可以通过动态内存进行开辟空间)

#include <stdio.h>
#include <stdlib.h>
struct num {
  int i;
  int arr[];
}s;
int main()
{
  struct num* p = (struct num*)malloc(sizeof(struct num)+100*sizeof(int));
  int i;
  for (i = 0; i < 100; i++)
  {
    p->arr[i] = i;
  }
  free(p);
  p = NULL;
}

        柔性数组和动态规划类似,我们可以把他们看成一个东西,这样会更好的去理解,今天的内容就结束了,欢迎大家来三连。

目录
相关文章
|
1月前
|
C++
【C++】深入解析C/C++内存管理:new与delete的使用及原理(二)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
1月前
|
编译器 C++ 开发者
【C++】深入解析C/C++内存管理:new与delete的使用及原理(三)
【C++】深入解析C/C++内存管理:new与delete的使用及原理
|
10天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
33 4
|
1月前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
103 21
|
29天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
1月前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
37 0
【C++打怪之路Lv6】-- 内存管理
|
1月前
|
存储 C语言 C++
【C/C++内存管理】——我与C++的不解之缘(六)
【C/C++内存管理】——我与C++的不解之缘(六)
|
1月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
68 1
|
1月前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
161 1
|
1月前
|
存储 安全 程序员
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
【C++篇】深入内存迷宫:C/C++ 高效内存管理全揭秘
63 3