目录
int a, int arr[10] 是固定地向内存申请连续的一块空间,但不能变长或变短随时调整。
在我们之前写的静态版通讯录中,我们创建了一个peopleinfo类型的数组date[100]用来存放100个人的个人信息,但是当我们的人员信息较小时,100个结构体显得有些浪费,而当我们所需存放的信息超过100时又不够用,随之我们就得修改这个数字,但是学了动态内存管理之后,我们可以动态地分配内存空间变大或变小,从而有效利用空间。
1、为什么要有动态内存分配
int val = 20;//在栈空间上开辟四个字节
 char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
 但是上述的开辟空间的方式有两个特点:
 1. 空间开辟大小是固定的。
 2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
 但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,
 那数组的编译时开辟空间的方式就不能满足了。
 这时候就只能使用动态内存开辟了。

2、动态内存函数介绍
1、malloc
#include<stdlib.h>

这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
 如果开辟成功,则返回一个指向开辟好空间的指针。
 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
 返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己
 来决定。
 如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
  
先在堆区上动态申请一定空间,使用后应该还给操作系统,如果不主动还,程序结束后会自动还,但是如果程序一直不结束,就一直“占着不用”,就会造成空间的浪费。
2、free
用来释放、归还申请的内存

  
   - 
    
     
    
    
     
      int main()
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     	
      //申请40个字节,用来存放10个整型
     
    
- 
    
     
    
    
     	
      int* p = (
      int*)
      malloc(
      40);
     
    
- 
    
     
    
    
     	
      if (p == 
      NULL)
     
    
- 
    
     
    
    
     
      	{
     
    
- 
    
     
    
    
     		
      printf(
      "%s\n", 
      strerror(errno));
     
    
- 
    
     
    
    
     		
      return 
      1;
     
    
- 
    
     
    
    
     
      	}
     
    
- 
    
     
    
    
     	
      //存放 1--10
     
    
- 
    
     
    
    
     	
      int i = 
      0;
     
    
- 
    
     
    
    
     	
      for (i = 
      0; i < 
      10; i++)
     
    
- 
    
     
    
    
     
      	{
     
    
- 
    
     
    
    
     
      		*(p + i) = i + 
      1;
     
    
- 
    
     
    
    
     
      	}
     
    
- 
    
     
    
    
     	
      for (i = 
      0; i < 
      10; i++)
     
    
- 
    
     
    
    
     
      	{
     
    
- 
    
     
    
    
     		
      printf(
      "%d ", *(p+i));
     
    
- 
    
     
    
    
     
      	}
     
    
- 
    
     
    
    
     	
      //free释放申请的内存
     
    
- 
    
     
    
    
     	
      free(p);
     
    
- 
    
     
    
    
      
     
    
- 
    
     
    
    
     	
      return 
      0;
     
    
- 
    
     
    
    
     
      }
     
    
  
用malloc申请后,内存中为随机值,使用时可给它们赋值,当free后,又变为随机值。

仔细观察,我们可以发现,虽然这块空间的值发生了变化,但是指针p指向的地址free前后没有变化。因此,当我们free还给操作系统后,p仍指向这块空间。此时*p就会导致非法访问,因此我们需要将p制为NULL,避免非法访问。
  
   - 
    
     
    
    
     	
      free(p);
     
    
- 
    
     
    
    
     
      	p = 
      NULL;
     
    
总结:malloc申请空间后不会初始化,使用前要判断是否成功申请(是否返回NULL),使用后要free还给操作系统,然后将用于接收这块内存空间的指针p置为NULL。

申请失败时返回NULL,并打印错误原因(没有足够空间)。
void free(void * ptr)
free函数用来释放动态开辟的内存。
 如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
 如果参数 ptr 是NULL指针,则函数什么事都不做。
3、calloc
 
由上图我们可以知道,malloc不会初始化,calloc会将每个元素先初始化为0,可以按需用。由于calloc需要初始化,效率比malloc稍低,此外没有其它区别,都需要进行相关步骤。
4、realloc


  
   - 
    
     
    
    
     
      int main()
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     	
      int* p = (
      int*)
      malloc(
      5 * 
      sizeof(
      int));
     
    
- 
    
     
    
    
     	
      if (
      NULL == p)
     
    
- 
    
     
    
    
     
      	{
     
    
- 
    
     
    
    
     		
      perror(
      "malloc");
     
    
- 
    
     
    
    
     		
      return 
      1;
     
    
- 
    
     
    
    
     
      	}
      //使用
     
    
- 
    
     
    
    
     	
      int i = 
      0;
     
    
- 
    
     
    
    
     	
      for (i = 
      0; i < 
      5; i++)
     
    
- 
    
     
    
    
     
      	{
     
    
- 
    
     
    
    
     
      		p[i] = 
      1;
     
    
- 
    
     
    
    
     
      	}
      //空间不够,增加5个整型的空间
     
    
- 
    
     
    
    
     	
      //此时不能用p接收
     
    
- 
    
     
    
    
     	
      int* ptr = (
      int*)
      realloc(p, 
      10 * 
      sizeof(
      int));
     
    
- 
    
     
    
    
     	
      //先用ptr接收,再赋给p,防止返回NULL,p找不到原来的数据
     
    
- 
    
     
    
    
     	
      if (ptr != 
      NULL)
     
    
- 
    
     
    
    
     
      	{
     
    
- 
    
     
    
    
     
      		p = ptr;
     
    
- 
    
     
    
    
     
              ptr = 
      NULL;
      //释放但不置空,需手动置空
     
    
- 
    
     
    
    
     
      	}
     
    
- 
    
     
    
    
     	
      //继续使用
     
    
- 
    
     
    
    
     	
      for (i = 
      0; i < 
      10; i++)
     
    
- 
    
     
    
    
     
      	{
     
    
- 
    
     
    
    
     		
      printf(
      "%d ", p[i]);
     
    
- 
    
     
    
    
     
      	}
      //realloc会把旧的空间释放,不用自己释放
     
    
- 
    
     
    
    
     	
      free(p);
     
    
- 
    
     
    
    
     
      	p = 
      NULL;
     
    
- 
    
     
    
    
     
      }
     
    
 
3、动态内存常见的错误
1、对NULL解引用(未判断是否为空)
2、非法访问内存,越界访问(解引用野指针)
3、对非动态开辟内存的空间用free释放
  
   - 
    
     
    
    
     
      void test()
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     
      int a = 
      10;
     
    
- 
    
     
    
    
     
      int *p = &a;
     
    
- 
    
     
    
    
     
      free(p);
      //ok?
     
    
- 
    
     
    
    
     
      }
     
    
对栈区的空间进行释放(x)
4、使用free释放一块动态开辟内存的一部分
  
   - 
    
     
    
    
     
      void test()
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     
      int *p = (
      int *)
      malloc(
      100);
     
    
- 
    
     
    
    
     
      p++;
     
    
- 
    
     
    
    
     
      free(p);
      //p不再指向动态内存的起始位置
     
    
- 
    
     
    
    
     
      p=
      NULL;
     
    
- 
    
     
    
    
     
      }
     
    
5、对同一块内存多次释放
  
   - 
    
     
    
    
     
      void test()
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     
      int *p = (
      int *)
      malloc(
      100);
     
    
- 
    
     
    
    
     
      free(p);
     
    
- 
    
     
    
    
     
      free(p);
      //重复释放
     
    
- 
    
     
    
    
     
      }
     
    
free一个NULL空指针时,什么事都不会发生。
malloc等函数是申请一块空间,获得使用它的权限,free释放后,将权限还给操作系统,如果再访问或是释放,就是非法访问。
 6、动态开辟的内存忘记释放(内存泄漏)
malloc和free要成对出现,防止出现内存泄漏
  
   - 
    
     
    
    
     
      int* test()
     
    
- 
    
     
    
    
     
      //函数内部进行了malloc操作,返回了malloc开辟的空间的起始地址
     
    
- 
    
     
    
    
     
      //谁接收了 要记得释放和置空
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     	
      int* p = (
      int*)
      malloc(
      100);
     
    
- 
    
     
    
    
     	
      if (
      NULL == p)
     
    
- 
    
     
    
    
     
      	{
     
    
- 
    
     
    
    
     		
      return 
      1;
     
    
- 
    
     
    
    
     
      	}
     
    
- 
    
     
    
    
     	
      return p;
     
    
- 
    
     
    
    
     
      }
     
    
- 
    
     
    
    
     
      int main()
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     	
      int* ptr = 
      test();
     
    
- 
    
     
    
    
     	
      free(ptr);
     
    
- 
    
     
    
    
     
      	ptr = 
      NULL;
     
    
- 
    
     
    
    
     
      }
     
    
 4、动态内存开辟相关好题
  
   - 
    
     
    
    
     
      void GetMemory(char *p)
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     
          p = (
      char *)
      malloc(
      100);
     
    
- 
    
     
    
    
     
      }
     
    
- 
    
     
    
    
     
      void Test(void)
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
         
      char *str = 
      NULL;
     
    
- 
    
     
    
    
         
      GetMemory(str);
     
    
- 
    
     
    
    
         
      strcpy(str, 
      "hello world");
     
    
- 
    
     
    
    
         
      printf(str);
     
    
- 
    
     
    
    
     
      }
     
    
- 
    
     
    
    
     
      int main()
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
         
      Test();
     
    
- 
    
     
    
    
     
      }
     
    
 这里GetMemory函数是值传递,且没有返回值,对str无影响。因此str还是指向NULL,即0地址处,然后调用strcpy需访问0地址处内容,导致非法访问。
同时,上面用malloc申请了一块空间,但是函数内没有释放,函数销毁后,p也销毁,这块空间就会内存泄漏。记得malloc要与free搭配使用。
修改后,可传入&str,用char**p接收,*P=(char*)malloc(100),使p指向开辟的100byte,进而存放拷贝的内容,打印后free(str) str=NULL
或者将p(char*)直接返回,用str接收,因为没有free,所以malloc(100)仍然存在,进而可以strcpy,如果此时不是malloc申请,而是利用数组,函数销毁后,数组的空间也会释放,如果再进行打印就不行了。(如下图)这类问题简称为 返回栈空间地址的问题

可以在p前面加上static使其变为静态区变量,函数销毁后它不会销毁,或者去掉[],p变为char*类型,即从数组变为常量字符串,常量字符串也是在静态区,也就是把在栈区存储的数据放入静态区中存储,从而避免了返回栈空间地址的问题。
printf(str)括号内直接加str是可以的,str是一个地址,例如printf(“hehe”),括号内有引号+字符串,也就是首字符的地址,相当于括号内直接加一个地址,最终结果都是打印字符串。
  
   - 
    
     
    
    
     
      void GetMemory(char **p, int num)
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
     
          *p = (
      char *)
      malloc(num);
     
    
- 
    
     
    
    
     
      }
     
    
- 
    
     
    
    
     
      void Test(void)
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
         
      char *str = 
      NULL;
     
    
- 
    
     
    
    
         
      GetMemory(&str, 
      100);
     
    
- 
    
     
    
    
         
      strcpy(str, 
      "hello");
     
    
- 
    
     
    
    
         
      printf(str);
     
    
- 
    
     
    
    
     
      }
     
    
- 
    
     
    
    
     
      int main()
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
         
      Test();
     
    
- 
    
     
    
    
     
      }
     
    
 这段代码的整体思路没有问题,但是会导致内存泄漏, malloc等函数需要与free共同使用
  
   - 
    
     
    
    
     
      void Test(void)
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
         
      char *str = (
      char *) 
      malloc(
      100);
     
    
- 
    
     
    
    
         
      strcpy(str, 
      "hello");
     
    
- 
    
     
    
    
         
      free(str);
     
    
- 
    
     
    
    
     
      }
     
    
- 
    
     
    
    
         
      if(str != 
      NULL)
     
    
- 
    
     
    
    
     
      {
     
    
- 
    
     
    
    
         
      strcpy(str, 
      "world");
     
    
- 
    
     
    
    
         
      printf(str);
     
    
- 
    
     
    
    
     
      }
     
    
free(str)后已经没有权限访问了,但后面又调用strcpy访问空间,导致非法访问
开辟--释放--置空
习题来自《高质量C/C++编程》
5、c/c++程序内存开辟示意图

 1. 栈区(stack):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结
 束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是
 分配的内存容量有限。 栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
 回地址等。
 2. 堆区(heap):一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。分
 配方式类似于链表。
 3. 数据段(静态区)(static)存放全局变量、静态数据。程序结束后由系统释放。
 4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码,只读常量(不能被修改),字符串常量。
转载:https://blog.csdn.net/lzfnb666/article/details/128700281
 
					
