31 内存操作问题分析

1 野指针

1.1 野指针危害

  • 指针变量中的值是非法的内存地址,进而形成野指针
  • 野指针不是 NULL 指针,是指向不可用内存地址的指针
  • NULL 指针并无危害,很好判断,也很好调试
  • C 语言中无法判断一个指针所保存的地址是否合法

1.2 野指针的由来

  • 局部指针变量没有被初始化

  • 指针所指向的变量在指针之前被销毁:返回局部数组

  • 使用已经释放过的指针:指针 free

  • 进行了错误的指针运算

  • 进行了错误的强制类型转换:整型值当作指针值

  • 示例:野指针

    • Demo

      #include <stdio.h>
      #include <malloc.h>
      
      int main()
      {
          int* p1 = (int*)malloc(40);  //p1指向malloc申请的动态内存
          int* p2 = (int*)1234567;  //p2指向一个整型值强制类型转换而来的地址,极大可能是野指针。但是程序不会在此处崩溃,取决于如何使用p2
          int i = 0;
          
          printf("%p
      ",p1);
          
          for(i = 0; i < 40; i++)
          {
              *(p1 + i) = 40 - i;  //野指针:进行了错误的指针运算(40个字节内存当作了40个int字节内存的),改写了非法的内存地址。
          }
      
          free(p1);  //p1所指向的内存释放后,p1保存的值还是原先的地址值,需要重新赋值为NULL
          
          printf("%p
      ",p1);
          
          for(i = 0; i < 40; i++)
          {
              p1[i] = p2[i];  //野指针使用:使用已经释放了的内存空间,非常难以调试,因为不会立即产生影响
          }
          
          return 0;
      }
      
    • 编译运行

      0x9baa008
      0x9baa008
      段错误
      
    • 修改

      #include <stdio.h>
      #include <malloc.h>
      
      int arr[40] = {1,2,3,4,5,6,7};
      
      int main()
      {
          int* p1 = (int*)malloc(40*sizeof(int));  
          int* p2 = arr;
          int i = 0;
          
          printf("%p
      ",p1);
          
          for(i = 0; i < 40; i++)
          {
              *(p1 + i) = 40 - i;
          }
      
          free(p1);
          p1 = NULL;
          
          printf("%p
      ",p1);
          
          for(i = 0; i < 40; i++)
          {
              p1[i] = p2[i];  //这里编译运行就会报错
          }
          
          return 0;
      }
      
    • 编译运行

      0x8ea8008
      (nil)
      段错误
      

1.3 避免野指针的基本原则

  • 绝不返回局部变量和局部数组的地址

  • 任何变量在定义后必须 0 初始化

  • 字符数组必须确认 0 结束符后才能成为字符串

  • 任何使用与内存操作相关的函数必须指定长度信息

  • 示例:无处不在的野指针

    • Demo

      #include <stdio.h>
      #include <string.h>
      #include <malloc.h>
      
      struct Student
      {
          char* name;
          int number;
      };
      
      char* func()
      {
          char p[] = "ABCD";
          
          return p;  //返回局部字符数组的地址
      }
      
      void del(char* p)
      {
          printf("%s
      ", p);
          
          free(p);
      }
      
      int main()
      {
          struct Student s;  //没有进行初始化,s.name为野指针
          char* p = func();  //产生野指针,p为野指针
          
          strcpy(s.name, p);  //使用野指针p,s.name
          
          s.number = 99;
          
          p = (char*)malloc(5);
          
          strcpy(p, "ABCDEFGHI");  //没有指定长度信息,产生内存越界,操作了野指针指向的内存空间
          
          del(p);
          
          return 0;
      }
      
    • 编译

      test.c: In function 'func':
      test.c:15: warning: function returns address of local variable
      
    • 运行

      段错误
      

2 常见内存错误

  • 结构体成员指针未初始化

  • 结构体成员指针未分配足够的内存

  • 内存分配成功,但并未初始化

  • 内存操作越界

  • 示例:常见的内存错误

    • Demo1

      #include <stdio.h>
      #include <malloc.h>
      
      void test(int* p, int size)
      {
          int i = 0;
          
          for(i = 0; i < size; i++)
          {
              printf("%d
      ", p[i]);
          }
          
          free(p);  //多次释放p指针所指向的内存空间 
      }
      
      void func(unsigned int size)
      {
          int* p = (int*)malloc(size * sizeof(int));
          int i = 0;
          
          //如果size为奇数,p指针所指向的堆空间不会释放
          if( size % 2 != 0 )
          {
              return; 
          }
          
          for(i = 0; i < size; i++)
          {
              p[i] = i;
              printf("%d
      ", p[i]);
          }
          
          free(p);
      }
      
      int main()
      {
          //在哪个函数中所申请的内存,就在哪个函数中进行释放
          int* p = (int*)malloc(5 * sizeof(int));
          
          test(p, 5);
          
          free(p);
          
          func(9);
          func(10);
             
          return 0;
      }
      
    • 编译运行:程序崩溃

    • Demo2

      #include <stdio.h>
      #include <malloc.h>
      
      struct Demo
      {
          char* p;
      };
      
      int main()
      {
          struct Demo d1;  //结构体变量未进行初始化
          struct Demo d2;  //结构体变量未进行初始化
          
          char i = 0;
          
          for(i = 'a'; i < 'z'; i++)
          {
              d1.p[i] = 0;  //d1.p指向一个随机的地址,是一个野指针,这里操作了野指针
          }
          
          d2.p = (char*)calloc(5, sizeof(char));  //申请的内存空间全部置零,产生段错误
          
          printf("%s
      ", d2.p);  //d2.p指向一个空串
          
          for(i = 'a'; i < 'z'; i++)
          {
              d2.p[i] = i;  //d2.p所指向的内存空间只有5个字节大小,不能容纳下26个字节,内存越界(本质是指针运算产生野指针),不会立即产生影响
          }
          
          free(d2.p);
          
          return 0;
      }
      
    • 编译运行

      段错误
      

3 内存操作的规则

  • 动态内存申请之后,应该立即检查指针值是否为 NULL ,防止使用 NULL 指针

    int* p = (int*)malloc(56);
    if(p != NULL)
    {
        //Do something here!
    }
    free(p);
    
  • free 指针之后必须立即赋值为 NULL

    int* p = (int*)malloc(20);
    
    free(p);
    p = NULL;
    
    //...
    
    if(p != NULL)
    {
        //Do something here!
    }
    
  • 任何与内存操作相关的函数都必须带长度信息

    void print(int* p,int size)
    {
        int i = 0;
        char buf[128] = {0};
        
        snprintf(buf,sizeof(buf),"%s","ABCDEFGH");
        
        for(i = 0;i < size; i++)
        {
            printf("%d
    ",p[i]);
        }
    }
    
  • malloc 操作和 free 操作必须匹配,防止内存泄漏和多次释放

    • malloc 次数多于 free 时,产生内存泄漏
    • malloc 次数少于 free 时,程序可能崩溃
    void func()
    {
        int* p = (int*)malloc(20);
        
        free(p);
    }
    
    int main()
    {
        int* p = (int*)malloc(40);
        
        func();
        
        free(p);
        
        return 0;
    }
    
原文地址:https://www.cnblogs.com/bky-hbq/p/13774038.html