自学内容网 自学内容网

动态内存管理的知识点笔记总结

开始之前,我们解释一为什么存在动态内存分配?

在我们之前写的:

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

但是,

1.这种大小是固定死的,我们是无法改变的。

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

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

对此,动态存的开辟就显得十分优异的做法了:

所以,开始我们的正文吧!

1.我们先了解各区的分布情况

一.malloc函数

头文件:#include <stdlib.h>

(内存块的大小,以字节为单位)

void* malloc (size_t size);

使用规则:

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

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

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

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

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

int main()
{
   int* pf=(int *)malloc(20);     这里要强制类型转换
   if(NULL==pf)                   
建议这里将NULL写在前面,因为不知道有没有一天,你会不小心将==写成了一个,
如果这样写的话它直接出现错误,告知你,
如果你写成if(pf=NULL),它不会显示错误的,对新手很不友好(不容易发现)。
问就是我做过
   { 
      perror("malloc");       
这里如果为空指针,打印出来,你也可以使用其他形式,我喜欢这种,方便
      return 1;  
   }
   return 0;
}

二.free函数

头文件:#include <stdlib.h>

1.作用:释放动态开辟的内存(只能动态)

2.如果参数str指向的空间不是动态,free的行为是未被定义的

3.str指向的空间是空指针,则什么事都不做。

int main()
{
     int num = 0;
     scanf("%d", &num);
     int arr[num] = {0};

     int* pf = NULL;
     pf= (int*)malloc(num*sizeof(int));
     if(NULL != pf)//判断pf指针是否为空
      {
        int i = 0;
       for(i=0; i<num; i++)
        {
          *(pf+i) = 0;
        }
      }
          free(pf);释放pf所指向的动态内存
          pf = NULL;
          return 0;   主动弄为NULL,如果不弄可能会造成非法访问

}

三.calloc

1.其实与malloc的用法差不多

区别:若把申请的值放得足够大
malloc申请到的空间,没有初始化,直接返回起始地址
calloc申请好空间后,会把空间初始化为0,然后返回起始地址

对此以后按照需求使用malloc还是calloc。

对于,我们要申请的内存空间,需要初始化时,使用这个就特别方便了。

2.使用规则:

void* calloc (size_t num, size_t size);

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

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

四.realloc

1.作用:扩容

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

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,对此,我们会对内存的大小做灵活的调整

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

*pf 就是你要扩容的指针

size 调整之后新大小

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

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

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

第一种:原有空间之后有足够大的空间

这种会直接在后面追加,原来空间的数据不会发生改变。

第二种:原有空间之后没有足够多的空间时

这时,我们应该在堆空间上另找一个合适大小的连续空间来使用。

这样函数返回的是一个新的内存地址

也就是把旧的空间的数据,拷贝新空间的前面位置,并且把旧的空间释放掉,同上返回新的空间的地址

使用时需要注意的一些情况:

#include <stdio.h>
int main()
{
   int *ptr = malloc(100);
   if(ptr != NULL)
   {
     perror("malloc");
   }

   //扩展容量
   //代码1
   ptr = realloc(ptr, 1000);这样可以吗?(如果申请失败会如何?)
                            直接返回NULL,如果像这样直接ptr,会把旧的数据搞丢
                            这也是为什么,建议使用新的指针来
    //代码2
    int*p = NULL;
    p = realloc(ptr, 1000);    所以新指针
    if(p != NULL)
    {
     ptr = p;
    }
    //业务处理
    free(ptr);
    return 0; 
    }

列举一些使用动态内存使用时容易出现的错误:

1.对NULL指针的解引用操作

void test()
{
int *p = (int *)malloc(20);
*p = NULL;    问题出现在空指针这里
free(p);
}

失败的问题:当申请空间时太大而造成堆区内存申请的不够。此时,返回空指针。

试图通过空指针对数据进行访问,而导致运行时的错误,

程序试图通过解引用一个非空,(但实际确实是空)的数,会发生空指针解引用错误,导致成了未定义的行为。这种情况大多数的平台会导致程序异常和拒绝服务的情况。

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

int main()
{
   int *p=(int*)malloc(20);  //这里申请了4个int
   if(NULL==p)
   {
     perror("malloc");
     return 1;
   }
   int i=0;
   for(i=0;i<=4;i++)     当=4时,它就超过了申请空间,是越界访问。此时就是野指针了
   {  
     *(p+i)=i;
   }
    free(p);
    p=NULL;
   return ;
}

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

void test()
{
  int a = 10;
  int *p = &a;
  free(p);
  p=NULL;
  return 0;
}

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);
}

!!!重要!!!!:动态开辟的空间一定要释放,并且正确释放

下面再给几道题,使得更加清晰的了解:

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

分析:

1.str传给p的时候,p是str的临时拷贝,有独立的空间
当GetMemory函数内部申请了空间后,地址放在p中时,str依然是NULL,
当GetMemory函数返回之后,strcpy拷贝的时候,形成了非法访问内存
2.在GetMemory函数内部,动态申请了内存,但是没有释放,会内存泄漏 

那么,怎么修改呢?
因为str本来就是一级指针,取地址后,那是不是变成了二级指针了
所以应该是char**p,而p里面放的是str的地址,想要找到str,
那么就应该是*p被赋值给函数

改正版本:

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

有小伙伴会问:为什么这个可以printf(str)?

例如:

char* p="hehe\n"
printf(“hehe\n”);
printf(p);
这两者本质上传的都是h元素的地址,所以一样的

同样,这个代码也是将放在str的地址
str是不是指向这个典型空间,然后strcpy
把hello world拷贝放在str里面,然后str不是指向那字符串的起始位置上吗?

题目二:

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

返回栈空间地址的问题:
这里虽然确实得到了p中首元素h的地址
但是当你出去返回函数了之后,char p[]里面的数就销毁了
但是出去了之后,在Test函数中记住了之前的的地址,就会进行访问,从而造成了非法访问

怎么改呢? -按照实际需求改就行
方法1:
改为char* p就可以了
为什么呢?“hello world”是一个常量字符串,这个就在内存里面存放着(某区域)
然后把这个地址交给p,p这个地方可以销毁,但是我把这个地址放到str里面去了,
所以仍然可以通过str去找到这个常量字符串,常量字符串并不是像刚刚的局部变量一样
局部变量进来创建,出去销毁
但是p是局部变量,p指向常量字符串,常量字符串并不是局部范围的一部分,所以并不会随函数返回而销毁
方法2
可以加static,因为这个不会还给操作系统

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

题目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);
}

问题:
内存泄漏,忘记释放内存空间

题目4:

void Test(void)
{
   char *str = (char *) malloc(100);
   strcpy(str, "hello");
   free(str);  <---注意,没有NULL
   if(str != NULL)
   {
     strcpy(str, "world");
     printf(str);
   }
} 

虽然已经释放,不属于我们了,但是看清,它没有设为NULL,所以仍然指向h那里,但它已经没有权限访问了
在一步中,只要前面申请成功了,基本不会NULL,所以变成了野指针,将world\0覆盖到了hello\0里,强行,造成非法访问

柔性数组

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

struct S 
{
   int a=20;
   char arr[];   表示数组的大小是未知的
   也可以改成
   //char arr[0];   一些编译器不可这样,那就看两个哪个可以吧

};

柔性数组的特点:

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

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

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

柔性数组结构体中计算大小时,不算进去

struct S
{
   int i;
   int a[0];//柔性数组成员
};
int main()
{
   printf("%d\n", sizeof(struct S));
}
 
答案为4

 柔性数组的使用

int main()
{
  int i = 0;
  struct S *p = (struct S*)malloc(sizeof(struct S*)+100*sizeof(int));
  //业务处理
  p->i = 100;
  for(i=0; i<100; i++)
  {
    p->a[i] = i;
  }
  free(p);    这里释放了一次
  p=NULL;
  return 0;
}

不使用柔性数组:

typedef struct S
{
   int i;
   int *p_a;
}S;

int main()
{ 
  S *p = malloc(sizeof(S));
  p->i = 100;
  p->p_a = (int *)malloc(p->i*sizeof(int));
  //业务处理
  for(i=0; i<100; i++)
  {
     p->p_a[i] = i;
  }
  //释放空间
   free(p->p_a);
   p->p_a = NULL;     这里释放了两次
   free(p);
   p = NULL;

总结:

好处:1.方便内存释放   

           2.这样有利于访问速度
 

最后的最后,让我们一起在心中默念点赞今天努力的自己吧!

点赞我们无论多么困难,仍不断奋斗,在努力使自己变强的路上,做一个孤独的奋斗者!


原文地址:https://blog.csdn.net/go_bai/article/details/144119310

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!