【C语言航路】第十三站:动态内存管理(上)

简介: 【C语言航路】第十三站:动态内存管理

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

我们已经知道的内存开辟方式有

int val = 20;//在栈空间上开辟四个字节

char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是这样开辟出来的空间有两个特点

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

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

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态内存开辟了。

二、动态内存函数

 

1.内存的分区

我们首先要知道,我们的内存大致可以分为栈区,堆区,静态区的

我们现在所说的动态内存函数都是在堆区的。

2.malloc和free

(1)malloc和free库函数文档

从这里我们可以得知,我们这里接受一个整型变量size,开辟一个size字节的空间大小。这块空间是连续的。然后返回这块空间的起始地址,这个返回类型是void*,所以我们开辟好后需要进行强制类型转化

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

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

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己

来决定。

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

free这个函数是用于释放我们动态开辟的内存空间。因为动态开辟的空间都是在堆区的。和局部变量这些开辟在栈区不同的是。他们会有栈帧的销毁的。所以不用我们去回收这块空间。而堆区的空间只有靠free来销毁回收。虽然说当程序结束的时候堆区的内存也会被销毁掉,但是如果程序很长时间不关闭的话,就很容易出现内存泄漏

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

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

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

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

(2)malloc和free的使用

这就是我们malloc的使用了,要注意我们一定要检查malloc出来的指针是否为空,因为有可能申请失败了。就会返回空指针,而我们后续一旦解引用空指针就会出现大问题

这里我们也使用了free这个函数,这个函数的功能是释放掉使用的堆区的内存。我们这个程序没有free固然是没有任何问题的。因为程序结束后也会释放这个内存。但是如果对于有循环不会结束的程序,那么问题就严重了,会造成内存泄漏,而我们free后也要记得将p置空,否则p就是野指针

#include<stdio.h>
#include<errno.h>
#include<string.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    printf("%s", strerror(errno));
    return 1;
  }
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    *(p + i) = i;
  }
  for (i = 0; i < 10; i++)
  {
    printf("%d ", *(p + i));
  }
  free(p);
  p = NULL;
  return 0;
}

当然我们也可以演示一下内存开辟失败的情况

2.calloc

(1)calloc的库函数文档

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

与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。

(2)calloc的使用

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

(3)与malloc的区别

malloc申请到的空间没有初始化,直接返回起始地址

calloc申请到的空间,会将所有元素初始化为0,然后返回起始地址

当然由于calooc需要初始化,所以calloc的效率会更慢一些

3.realloc

(1)realloc的库函数文档

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

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

ptr 是要调整的内存地址,他时被malloc、calloc、realloc出来的

size 调整之后新大小

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

这个函数调整原内存空间大小的基础上,如果还会将原来内存中的数据移动到新的空间

realloc在调整内存空间的是存在几种情况:

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

在这种情况下,relloc就会在原来的地址后面续上新的空间,返回旧的起始地址

情况2:原有空间之后没有足够大的空间
在这种情况下,realloc就会重新找一块满足空间大小的新的连续空间,把旧的空间的数据,拷贝到新的空间中去,然后释放旧的空间。同时返回新的空间的地址

情况3:realloc开辟失败

直接返回空指针,但是不会销毁旧的空间数据

情况4:realloc传一个空指针

此时就相当于一个malloc

(2)realloc的使用

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

三、常见的动态内存错误

1.对NULL指针的解引用

如下代码所示,我们使用malloc时候,未进行判断指针p是否为NULL,如果没有判断且指针为空,那么则会程序崩溃

#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(INT_MAX);
  *p = 20;//如果p的值是NULL,就会有问题
  free(p);
  return 0;
}

2.对动态开辟的内存越界访问

如下代码所示,我们开辟了100个字节,也就是25个整型,但是我们访问了100个整型,这样就越界访问了,程序会崩溃

#include<stdio.h>
#include<stdlib.h>
int main()
{
  int* p = (int*)malloc(100);
  if (p == NULL)
  {
    perror("malloc");
    return 1;
  }
  int i = 0;
  for (i = 0; i < 100; i++)
  {
    *(p + i) = i + 1;
  }
  free(p);
  p = NULL;
  return 0;
}
相关文章
|
2月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
188 0
|
2天前
|
存储 程序员 C语言
C语言动态内存管理
C语言动态内存管理
|
2天前
|
程序员 C语言
C语言内存函数精讲
C语言内存函数精讲
|
3天前
|
编译器 C语言 C++
【C语言】精妙运用内存函数:深入底层逻辑的探索
【C语言】精妙运用内存函数:深入底层逻辑的探索
|
3天前
|
程序员 编译器 数据处理
【C语言】深度解析:动态内存管理的机制与实践
【C语言】深度解析:动态内存管理的机制与实践
|
1月前
|
存储 大数据 C语言
C语言 内存管理
本文详细介绍了内存管理和相关操作函数。首先讲解了进程与程序的区别及进程空间的概念,接着深入探讨了栈内存和堆内存的特点、大小及其管理方法。在堆内存部分,具体分析了 `malloc()`、`calloc()`、`realloc()` 和 `free()` 等函数的功能和用法。最后介绍了 `memcpy`、`memmove`、`memcmp`、`memchr` 和 `memset` 等内存操作函数,并提供了示例代码。通过这些内容,读者可以全面了解内存管理的基本原理和实践技巧。
|
1月前
|
缓存 Linux C语言
C语言 多进程编程(六)共享内存
本文介绍了Linux系统下的多进程通信机制——共享内存的使用方法。首先详细讲解了如何通过`shmget()`函数创建共享内存,并提供了示例代码。接着介绍了如何利用`shmctl()`函数删除共享内存。随后,文章解释了共享内存映射的概念及其实现方法,包括使用`shmat()`函数进行映射以及使用`shmdt()`函数解除映射,并给出了相应的示例代码。最后,展示了如何在共享内存中读写数据的具体操作流程。
|
2月前
|
存储 程序员 C语言
【C语言】动态内存管理
【C语言】动态内存管理
|
2月前
|
存储 编译器 C语言
C++内存管理(区别C语言)深度对比
C++内存管理(区别C语言)深度对比
74 5
|
2月前
|
C语言
C语言动态内存管理
C语言动态内存管理
33 4