[C]语言动态内存管理

简介: [C]语言动态内存管理

前言

者:小蜗牛向前冲

名言我可以接收失败,但我不能接收放弃

如果觉的博主的文章还不错的话,还请 点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。 大家好啊!小蜗牛又来为大家分享新的文章了。不知道大家有没有在使用数组时发现超出空间的情况,这时我们往往要重新定义数组的大小。那么这个数组的空间到底定义多大呢?定小了可能等下数组空间又不够了,定大了可能会存在许多内存空间的浪费。那么有什么更好的办法解决吗?

有的,那就是动态内存分配,下面博主会为大家一一道来。

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

我们以往开辟空间的方式:

  int a = 0;//在栈区开辟4个字节的空间
  int arr[10] = { 0 };//在栈区开辟40个字节连续的空间

特点:

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

2数组在声明时,必须指数组大小或者直接初始化数组,它所需要的内存在编译时分配

但有时侯,对于空间的需求在我们写代码的时候是并不知道的,要编译完之后才会知道,这就导致我们可能又要去修改分配内存空间的大小,这是不便的。这时候我们便可以试试用动态内存分配来解决这个问题。

二动态内存函数的介绍

1 malloc函数

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

参数

stze:

内存块的大小,以字节为单位。
size_t是无符号整数类型。

返回值

成功时,指向函数分配的内存块的指针。

此指针的类型始终为 void*,可以将其转换为所需类型的数据指针,以便可取消引用。

如果函数未能分配请求的内存块,则返回空指针。

注意:

malloc函数成功开辟空间返回的是,指向开辟空间的指针

开辟失败的时候返回的是,一个空指针(NULL),所以在用malloc函数开辟空间时一定要去判断是否能否开辟成功。

返回值是void*,所以在接收用malloc开辟的空间时,要强转为自己需要的类型

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

2 free函数


C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的,函数原型如下:

参数

ptr

指向先前使用 malloc、calloc 或 realloc 分配的内存块的指针。

注意:

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

如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。

如果参数 ptr 是NULL指针,则函数什么事都不做。

alloc和free都声明在 stdlib.h 头文件中

代码举例:

int main()
{
  //动态内存的开辟
  int* ptr= (int *)malloc(40);
  //判断空间是否开辟成功
  int i = 0;
  if (ptr != NULL)
  {
    //使用
    for (i = 0;i < 10;i++)
    {
      *(ptr + i) = i;
    }
  }
  for (i = 0;i < 10;i++)
  {
    printf("%d ", *(ptr+i));
  }
  free(ptr);//回收空间
  ptr = NULL;//防止出现野指针
  return 0;
}

3 calloc函数

C语言还提供了一个函数叫 calloc , calloc 函数也用来动态内存分配。原型如下:

作用

分配和零初始化数组

为 num 元素数组分配一个内存块,每个元素的大小都长字节,并将其所有位初始化为零。

有效结果是分配了零初始化的(数字*大小)字节的内存块

如果 size 为零,则返回值取决于特定的库实现(它可能是也可能不是空指针),但返回的指针不应被取消引用

参数

num

要分配的元素数。

size

每个元素的大小。

size_t 是无符号整数类型。

代码举例:

int main()
{
  int i = 0;
  scanf("%d", &i);//要分配的元素数
  int* data = (int*)calloc(i, sizeof(int));
  //判断
  if (data == NULL)
  {
    perror(data);
    return 1;
  }
  //使用
  int j = 0;
  for (j = 0;j < i;j++)
  {
    data[j] = j;
    printf("%d ", data[j]);
  }
  //释放
  free(data);
  data = NULL;
  return 0;
}

首先我来看道calloc函数的第一个功能,能将分配空间中的元素都初始化为0。

其次,否真的分配的内存空间。

所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

4 realloc函数

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

函数原型如下:

功能

更改 ptr 所指向的内存块的大小。

该函数可以将内存块移动到新位置(其地址由函数返回)。

内存块的内容将保留到新旧大小中较小的一个,即使该块被移动到新位置也是如此。如果新大小较大,则新分配部分的值不确定。

如果 ptr 是空指针,则该函数的行为类似于 malloc,分配一个新的大小字节块并返回指向其开头的指针。

参数

ptr

指向先前使用 malloc、calloc 或 realloc 分配的内存块的指针。

或者,这可以是一个空指针,在这种情况下,分配一个新块(就像调用malloc一样)。

size

内存块的新大小,以字节为单位。

size_t是无符号整数类型

realloc函数调整空间后存在二种情况

情况1:原有空间之后有足够大的空间

情况2:原有空间之后没有足够大的空间

对于情况1要扩展的空间直接在原有空间之间后扩展,原空间的数据不发生变化。

对于情况2由于后续空间不足以扩展,所以realloc函数会重新在堆区找足够大一块空间,把原空间的数据放到新空间中,并让ptr重新指向新空间。

代码举例:

int main()
{
  int* ptr = (int*)calloc(5, sizeof(int));
  if (ptr != NULL)
  {
    int i = 0;
    for (i = 0;i < 5;i++)
    {
      *(ptr + i) = i;
      printf("%d ", *(ptr + i));
    }
  }
  else
  {
    perror(ptr);//报错信息
  }
  printf("\n");
  //增容
  int* p = NULL;
  p = realloc(ptr, 10 * sizeof(int));
  printf("增容成功\n");
  if (p != NULL)
  {
    ptr = p;
    int i = 0;
    for (i = 0;i < 10;i++)
    {
      *(ptr + i) = i;
      printf("%d ", *(ptr + i));
    }
  }
  //释放
  free(ptr);
  ptr = NULL;
  return 0;
}

三 常见的动态内存错误

虽然我们在使用malloc,calloc和realloc开辟动态空间很方便,但也容易引起一些错误。下面我们就一起看看吧。

1 对NULL指针的解引用操作

void test()
{
  int* p = (int*)malloc(INT_MAX);
  *p = 20;//如果p是NULL,就会有问题
  free(p);//释放
}

其中INT_MAX是一个比较大的数,内存分配可能会失败,当分配空间失败时,p被置为NULL,后面对空指针解引用是不可以取的。

我们可以这样避免

void test()
{
  int* p = (int*)malloc(INT_MAX);
  if (p == NULL)
  {
    return 1;
  }
  *p = 20;//如果p是NULL,就会有问题
  free(p);//释放
}

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

void test()
{
  int i = 0;
  int* p = (int*)malloc(10 * sizeof(int));
  if (NULL == p)
  {
    perror(p);  
  }
  for (i = 0; i <= 10; i++)
  {
    *(p + i) = i;//当i是10的时候越界访问
  }
  free(p);
  p = NULL;

这里就告诉我们在写代码还是要多思考。

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

void test()
{
 int a = 10;
 int *p = &a;
 free(p);//ok?
}

这里我们是对非动态空间继续释放,这肯定是不可取的,a变量开辟的空间是在栈区是,而free释放的空间是在堆区。

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

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}

这里程序会崩溃。

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

void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放
}

哈哈,别认为这不存在噢,当我们写代码写多了,会有的噢。

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

void test()
{
 int *p = (int *)malloc(100);
 if(NULL != p)
 {
 *p = 20;
 }
}
int main()
{
 test();
 while(1);
}

忘记释放不再使用的动态开辟的空间会造成内存泄漏。

切记: 动态开辟的空间一定要释放,并且正确释放 。

几个经典的笔试题

学完上面的知识点,我们来实战在训练一下。

题目1:

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

请问运行Test 函数会有什么样的结果?

我们发现什么都没打印,为什么呢?

其中在于p变量是个形参,出函数就会销毁,那么分配的空间就找不到了,使用str并没有分到内存空间,就无法完成拷贝。

其实我们稍作改动将可以完成代码的实现,将传值调用改为传址调用就可以了。

题目2:

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

请问运行Test 函数会有什么样的结果?

我们发现打印出来了随机值,为什么呢?

这是因为函数使用完后就会销毁,p其实是个野指针,p指向的空间在出函数后就会被回收。

题目3:

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

请问运行Test 函数会有什么样的结果?

虽然结果是对的,但大家发现没malloc函数开辟的空间并没有被回收,这会造成内存空间的泄漏,所以我们在使用完动态函数开辟的空间后一定要回收内存。

题目4:

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

 请问运行Test 函数会有什么样的结果?

怎么会是world呢?不少会一疑问,str空间不是被free释放吗?

其实空间虽然是释放了,但str仍然记得那块空间的地址,我们*还是能找到那块空间,但是那块空间已经是不属于我们了,这时str相当于野指针是相当危险的。为了避免这种错误的结果出现,我们最好在free释放了str所指向的空间时,在将str置为空指针(str=NULL).

C/C++程序的内存开辟

这里简单和大家分享一下,C/C++程序的内存开辟。

C/C++程序内存分配的几个区域:

1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返回地址等。

2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分 配方式类似于链表。

3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。

4. 代码段存放函数体(类成员函数和全局函数)的二进制代码。

六 柔性数组

也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。 C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

下面就是柔性数组:

typedef struct s
{
  int a;
  int arr[];//柔性数组
}s;

1 柔性数组的特点

结构中的柔性数组成员前面必须至少有一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小

       

我们可以看到,这个结构体的大小是不包含柔性数组的大小的。

2 柔性数组的使用

//代码1
typedef struct s
{
  int a;
  int arr[];//柔性数组
}s;
 
int main()
{
  //代码1
  int i = 0;
  s* p = (s*)malloc(sizeof(s) + 100 * sizeof(int));//为结构体和柔性数组开辟空间
  //业务处理
  p->a = 100;
  for (i = 0; i < 100; i++)
  {
    p->arr[i] = i;
  }
  free(p);
  p = NULL;
  return 0;
}

用malloc为柔性数组开辟的100的连续的空间。

3 柔性数组的优势

下面我们在另外一个方式,实现代码1的结果。

//代码2
struct s
{
  int n;
    int* arr;
};
 
 
int main()
{
  struct s* p = (struct s*)malloc(sizeof(struct s));//为结构体开辟空间
  if (p == NULL)
  {
    return 1;
  }
  p->n = 100;
  p->arr = (int*)malloc(40);//为arr指针开辟空间
  if (p->arr == NULL)
  {
    perror("");//报错信息
    return 1;
  }
  //使用
  int i = 0;
  for (i = 0;i < 10;i++)
  {
    p->arr[i] = i;
  }
  //空间释放
  free(p->arr);
  free(p);
  p = NULL;
}

上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:

第一个好处是:方便内存释放

如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好 了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

第二个好处是:这样有利于访问速度.

连续的内存有益于提高访问速度,也有益于减少内存碎片。

总结

在这篇博客中,我们主要学习了动态内存函数和柔性数组,对于动态内存函数我们主要还是要注意,使用后一定要记得用free释放空间,柔性数组的优点方便内存释放和提高访问速度。

最后送大家于自己一句话:

纸上得来终觉浅 绝知此事要躬行!

 


相关文章
|
18天前
|
存储 Java
百度搜索:蓝易云【Java语言之float、double内存存储方式】
由于使用IEEE 754标准进行存储,float和double类型可以表示非常大或非常小的浮点数,并且具有一定的精度。然而,由于浮点数的特性,它们在进行精确计算时可能会存在舍入误差。在编写Java程序时,需要注意使
61 0
|
8月前
|
存储 编译器 Go
Go 语言内存逃逸案例
Go 语言内存逃逸案例
37 0
|
8月前
|
存储 算法 编译器
Golang 语言的内存管理
Golang 语言的内存管理
22 0
|
8月前
|
安全 编译器 Go
Golang 语言的内存模型
Golang 语言的内存模型
30 0
|
18天前
|
C语言
C - 语言->内存函数
C - 语言->内存函数
|
Go
【go 语言】PProf 的使用——CPU和内存占用分析(二)
PProf 的使用——CPU和内存占用分析(二)
1292 0
【go 语言】PProf 的使用——CPU和内存占用分析(二)
|
9天前
|
存储 安全 程序员
C++语言中的内存管理技术
C++语言中的内存管理技术
|
11天前
|
存储 算法 编译器
C语言:数据在内存中的存储`
C语言:数据在内存中的存储`
20 0
|
18天前
|
监控 Java 编译器
Go语言内存与并发性能综合优化策略
【2月更文挑战第11天】Go语言以其高效的并发处理能力和简洁的内存管理机制成为了现代软件开发中的热门选择。然而,在实际应用中,如何综合优化Go程序的内存使用和并发性能,仍然是一个值得探讨的话题。本文将深入探讨Go语言内存与并发性能的综合优化策略,包括内存布局优化、并发模式设计、资源池化以及性能监控与分析等方面,旨在帮助开发者全面提升Go程序的整体性能。
|
18天前
|
存储 编译器 C++
c-语言->数据在内存的存储
c-语言->数据在内存的存储