C语言之动态内存分配一

简介: C语言之动态内存分配一

为什么存在动态内存分配?:

但是这两种方式在某些情况下是不适用的,假设现在我们要在结构体中存放学生的信息。

struct S
{
  char name[20];
  int age;
};
int main()
{
  struct S arr[50];
  return 0;
}

但很明显,学生的数量是不确定的,如果我们分配内存为50个学生的大小,当学生人数超过这个数量就会出现存放不下的情况,那么有的人会说,我们可以开辟足够大的空间,这种方式看似解决了这个问题,但是会出现内存浪费的情况,假如说你申请了1000个内存空间,结果你只用了10个或者100个,这就浪费了大量的内存空间,那么我们是否可以将arr[]中的元素值先设置为n,再通过scanf函数输入n的值?

答案是:C语言不支持,但C99支持,要想创建变长数组,gcc是支持这种操作的,而我们使用的VS编译器并不支持这种操作。


什么叫变长数组?


数据的长度可以任意变化。


举例:

int main()
{
  int n;
  scanf("%d", n);
  struct S arr[n];//数组的长度并不是不变的
  return 0;
}

由此,C语言给出了一种动态内存分配。

动态内存分配的介绍:

malloc和free:头文件:#include<stdlib.h>

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

void* malloc(size_t size);

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


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


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


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


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


申请空间:

*void*malloc(sizeof(ELemType)num)

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
  //向内存申请10个整形空间
  int *p=(int*)malloc(10 * sizeof(int));
  if (p == NULL)
  {
    //打印错误原因
    printf("%s\n", strerror(errno));
  }
  else
    //正常使用空间
  {
    int i = 0;
    for (i = 0; i < 10; i++)
    {
      *(p + i) = i;
    }
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));
    }
  }
  return 0;
}

输出如下所示:

0 1 2 3 4 5 6 7 8 9

释放空间:

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

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

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

当动态申请的空间不在使用的时候,就应该还给操作系统:


方法:

free(p);

将该语句加入到上面的代码中,再运行!

输出结果为:

0 1 2 3 4 5 6  7 8 9

输出这个结果难免会让我们产生疑惑,好像释放空间和不释放没有任何区别,其实事实并非如此。


上述代码即使没有加free,其内存空间也会被释放,其原因是当程序的生命周期结束时,动态申请的内存空间会自动还给操作系统,但我们并不提倡这种做法,原因是:上述代码,恰好在free语句下面就执行完了所有的程序,因此加不加free的效果是相同的,但是,如果free后面还有其他的逻辑语句,就会导致上面不用的代码一直占着内存,因此在以后的代码中,我们一定不能忘记加free.


但是这种释放方式还不够彻底,p的内存空间只是被释放了,但是它的值并没有改变,要想彻底使p释放,我们应在释放内存空间的语句后面再将p指向NULL,这才会彻底释放。

calloc函数:

calloc函数也可以用来动态内存分配;

void*calloc(size_t num,size_t size);


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

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

举例:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
  //malloc(10*sizeof(int));
  int* p=(int*)calloc(10, sizeof(int));
  if (p == NULL)
  {
    printf("%s\n",strerror(errno));
  }
  else
  {
    int i = 0;
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));
    }
  }
  free(p);//释放动态开辟的空间
  p = NULL;
  return 0;
}

输出如下:

0 0 0 0 0 0 0 0 0 0

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

realloc函数:

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

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间太大了,那为了合理的使用内存,我们必须要对内存的大小做灵活的调整,那么realloc函数就可以做到对动态开辟内存大小的调整,函数原型如下:

void*realloc(void*ptr,size_t size);

ptr:要调整的内存地址

size:调整之后新的大小

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

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


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

情况一:原有空间之后有足够大的空间,不需要开辟新的空间,直接进行追加

原有空间的数据不发生变化。

情况二:原有空间之后没有足够大的空间,需要开辟新的空间


举例:

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
int main()
{
  int* p = (int*)malloc(20);
  if (p == NULL)
  {
    printf("开辟失败");
  }
  else
  {
    int i = 0;
    for (i = 0; i < 5; i++)
    {
      *(p + i) = i;
    }
  }
  //上述所使用的空间为我们在程序前面通过malloc函数申请的20个空间
  //假设此时该空间不够用了,我们还需要20个字节的空间,此时我们可以使用realloc来调整动态内存空间的大小
  int*ptr=(int*)realloc(p, 40);
  if (ptr != NULL)//追加成功
  {
    p = ptr;
    int i = 0;
    for (i = 5; i < 10; i++)
    {
      *(p + i) = i;
    }
    for (i = 0; i < 10; i++)
    {
      printf("%d ", *(p + i));
    }
  }
  free(p);//如果返回的是新地址,新地址需要用free函数释放,而旧地址会被realloc函数释放
  p = NULL;
  return 0;
}

常见的动态内存错误:

对NULL指针进行解引用操作:

int main()
{
  int* p = (int*)malloc(40);
  int i = 0;
  //如果malloc没能正确进行,p就被赋值为NULL,此后进行++操作为非法操作
  for (i = 0; i < 10; i++)
  {
    *(p + i) = i;
  }
  free(p);
  p = NULL;
  return 0;
}

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

int main()
{
  int* p = (int*)malloc(5*(sizeof(int)));
  if (p == NULL)
  {
    return 0;
  }
  else
  {
    int i = 0;
    for (i = 0; i < 10; i++)//上面只开辟了5个整形元素的空间,这里i<10;为越界访问
    {
      *(p + i) = i;
    }
  }
  free(p);
  p = NULL;
  return 0;
}

对非动态开辟内存的free:

int main()
{
  //p并不是存在堆区的,free不能对齐进行访问
  int a = 10;
  int* p = &a;
  *p = 20;
  free(p);
  p = NULL;
  return 0;
}

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

#include<string.h>
int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    return 0;
  }
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    *p++ = i;//p的指向位置已经发生改变,已经不是动态开辟的内存的起始位置
  }
  free(p);
  p = NULL;
  return 0;
}

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

int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
    return 0;
  }
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    *p++ = i;//p的指向位置已经发生改变
  }
  free(p);
  //...
  free(p);//多次释放p
  p = NULL;
  return 0;
}

编译程序崩溃,原因是我们对p进行多次释放。

那么如何避免这种错误呢?

方法:释放完内存,立即将其指向NULL,上面我们提到过,如果指针指向的是NULL,再对其进行释放,编译器将不会做任何事情。

free(p);
p = NULL;
//...
free(p);

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

int main()
{
  while (1)
  {
    malloc(1);//不断的开辟空间,但不进行释放
  }
  return 0;
}

将程序运行后,打开资源管理器,我们会发现,内存的占用比一直在增加。

忘记释放不再使用的开辟的空间会造成内存泄漏。
切记:动态开辟的空间一定要释放,并且正确释放。

相关文章
|
1月前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
33 3
|
3月前
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
366 0
|
20天前
|
C语言
【c语言】动态内存管理
本文介绍了C语言中的动态内存管理,包括其必要性及相关的四个函数:`malloc`、``calloc``、`realloc`和`free`。`malloc`用于申请内存,`calloc`申请并初始化内存,`realloc`调整内存大小,`free`释放内存。文章还列举了常见的动态内存管理错误,如空指针解引用、越界访问、错误释放等,并提供了示例代码帮助理解。
32 3
|
1月前
|
编译器 程序员 C语言
深入C语言:动态内存管理魔法
深入C语言:动态内存管理魔法
|
1月前
|
存储 程序员 编译器
C语言——动态内存管理与内存操作函数
C语言——动态内存管理与内存操作函数
|
1月前
|
程序员 C语言
C语言内存函数精讲
C语言内存函数精讲
|
22天前
|
存储 C语言
【c语言】字符串函数和内存函数
本文介绍了C语言中常用的字符串函数和内存函数,包括`strlen`、`strcpy`、`strcat`、`strcmp`、`strstr`、`strncpy`、`strncat`、`strncmp`、`strtok`、`memcpy`、`memmove`和`memset`等函数的使用方法及模拟实现。文章详细讲解了每个函数的功能、参数、返回值,并提供了具体的代码示例,帮助读者更好地理解和掌握这些函数的应用。
19 0
|
1月前
|
C语言
保姆级教学 - C语言 之 动态内存管理
保姆级教学 - C语言 之 动态内存管理
18 0
|
1月前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储
|
1月前
|
C语言 C++
c语言回顾-内存操作函数
c语言回顾-内存操作函数
39 0