动态内存管理详解(上)

简介: 动态内存管理详解

一、动态内存分配与传统开辟内存的比较


我们一致的内存开辟方式有:


int val =20;//在栈空间上开辟4个字节。
char arr[10]={0};//在栈空间上开辟10个字节。


上述开辟空间的方式有两个特点:


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


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


但是有时空间的大小在运行时才能获知,那么上述开辟空间的方式就不能满足了,于是就产生了动态内存开辟。


二.动态内存函数


2.1 malloc 和 free


void* malloc (size_t size);

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


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


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


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


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


另外在使用malloc的时候,要和它的好搭档free搭配使用,free是专门用来做动态内存的释放和回收的。


void free (void*  ptr);


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


具体使用:


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

2.如果参数ptr是NULL,则函数无操作。


2.2 malloc和free具体使用


#include<stdio.h>
#include<errno.h>
int main()
{
    int num;
    scanf("%d", &num);
    int* ptr = NULL;
    ptr = (int*)malloc(num * sizeof(int));
    if (num == NULL)//判断是否为空
    {
        printf("%s\n", strerror(errno));
    }
    for (int i = 0; i < num; i++)
    {
        *(ptr + i) = 0;
    }
    free(ptr);//释放ptr所指向的动态内存
    ptr = NULL;
    return 0;
}


这里要申明两件事:


1.开辟空间后做检查是否为NULL指针是非常重要的。


2.注意误区:free动态开辟的内存后,该动态开辟的空间虽然被释放,但是地址仍旧存在,指针不为NULL,可以访问,但会出错,所以养成良好习惯,要置为NULL指针。(务必记住)


3.free函数只能释放动态开辟的内存或者空指针,释放其他内存会出错。


2.3 calloc


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


void* calloc(size_t num, size_t size);


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


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


例如:


1669215680374.jpg


所以如果要求我们对初始开辟的空间进行初始化时,我们可以很使用calloc函数很轻松解决这个问题。


2.4 realloc


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

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


函数原型:

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


函数解析:


· ptr是要调整的内存地址


· size 调整之后新大小


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


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


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


1.原有空间之后有足够大的空间。


1669215702174.jpg


对于情况一,要扩展内存就直接原有内存之后直接追加空间。


2.原有空间之后没有足够大的空间。

1669215711722.jpg


当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。

由于上述的两种情况,realloc函数的使用就要注意一些,我们通常会再创建一个变量来充当桥梁的作用用于过渡。


譬如:


int main()
{
  int* ptr = (int*)calloc(10, sizeof(int));
  if (NULL == p)
  {
  printf("%s\n", strerror(errno));
  }
  int* p = (int*)realloc(ptr, 100);
  if (p != NULL)
  {
  ptr = p;
        p=NULL;
  }
  free(ptr);
  ptr = NULL;
  return 0;
}

通过对上述函数的介绍,我们对于动态开辟内存有了一定的了解,简单总结一下需要注意:


1.开辟空间后要记住做检查是否为NULL指针。


2.注意free动态开辟的内存后,指针不为NULL,可以访问,但会出错,所以养成良好习惯,要置为NULL指针。(务必记住)


3.free函数只能释放动态开辟的内存或者空指针,释放其他内存会出错。


4.在使用realloc函数时要记住创建一个中间变量来过渡,使用过后,记得中间变量置为NULL空指针,防止有两个地址指向同一块空间,防止因为情况一空间不够导致开辟失败。


三、常见的动态内存错误


3.1 对NULL指针的解引用操作


这个问题也就是我在上面提到的,动态开辟后要进行检查,防止为NULL指针。


void test()
{
  int* p = (int*)malloc(INT_MAX / 4);
  //要进行检查
  *p = 20;//如果p的值是NULL,就会有问题
    free(p);
}


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

int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
  printf("%s\n", strerror(errno));
  return 1;
  }
  //访问
  int i = 0;
  for (i = 0; i <= 10; i++)//越界访问
  {
  p[i] = i;
  }
  free(p);
  p = NULL;
  return 0;
}

这个问题比较常见,在常规开辟内存时也是应该注意的问题。


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


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

答案肯定是不ok的,free只能释放动态开辟的空间或者是NULL空指针(无操作).


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


这个问题比较不容易发现,很多时候调试出现错误可能就是因为这里出了错。


大家可以看一下以下两组代码,例如:

int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
  return 1;
  }
  //使用
  int i = 0;
  for (i = 0; i < 10; i++)
  {
  *(p + i) = i;
  }
  //释放
  free(p);//仅释放一部分,不合理,应该释放指向起始位置的。
  p = NULL;
  return 0;
}

代码二:

int main()
{
  int* p = (int*)malloc(40);
  if (p == NULL)
  {
  return 1;
  }
  //使用
  int i = 0;
  for (i = 0; i < 10; i++)
  {
  *p = i;
  p++;
  }
  //释放
  free(p);//仅释放一部分,不合理,应该释放指向起始位置的。
  p = NULL;
  return 0;
}


这两段代码的不同之处就在于,malloc开辟空间初始化的方式,在这里第一种不会出错,而第二种则会出错。


希望大家要注意++是有副作用的,这里第一个代码p指针所指向的地址始终是开辟空间的初始地址,而第二个代码p的地址被++给更改,这样free就会出错。

相关文章
|
3月前
|
程序员 编译器 C语言
|
5月前
|
编译器 C语言
动态内存管理(1)
动态内存管理(1)
40 4
|
5月前
|
程序员 C语言 C++
动态内存管理(2)
动态内存管理(2)
37 1
|
C语言 Python
动态内存管理(下)
动态内存管理(下)
55 0
|
5月前
|
程序员
21.动态内存管理
21.动态内存管理
|
11月前
|
程序员 C语言 C++
动态内存管理-2
动态内存管理
41 0
|
11月前
|
程序员 编译器 C语言
动态内存管理总结
动态内存管理总结
53 0
|
12月前
|
编译器 文件存储 数据库
Day_17> 动态内存管理
Day_17> 动态内存管理
|
程序员 编译器 C语言
动态内存管理(上)
动态内存管理(上)
45 0
动态内存管理(下)
动态内存管理(下)
37 0