C之内存操作经典问题解析(三十七)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C之内存操作经典问题解析(三十七)相关的知识,希望对你有一定的参考价值。

   在 C 语言中,野指针是一个常见的内存错误。那么野指针到底是什么呢?指针变量中的值是非法的内存地址,进而形成野指针;野指针不是 NULL 桌子,是指向不可用内存地址的指针;NULL 指针并无危害,很好判断也很调试;在 C 语言中无法判断一个指针所保存的地址是否合法,所以我们必须得杜绝野指针!

   那么野指针到底是怎么来的呢?来源有这么几方面:1、局部指针变量没有被初始化;2、指针所指向的变量在指针之前被销毁;3、使用已经释放过的指针;4、进行了错误的指针运算;5、进行了错误的强制类型转换。

   下来我们以代码为例进行分析,代码如下

#include <stdio.h>
#include <malloc.h>
 
int main()
{
   int* p1 = (int*)malloc(40);
   int* p2 = (int*)1234567;
   int i = 0;
     
   for(i=0; i<40; i++)
   {
     *(p1 + i) = 40 - i;
   }
 
   free(p1); 
     
   for(i=0; i<40; i++)
   {
     p1[i] = p2[i];
   }
     
   return 0;
}

   我们看到第 7 行进行了错误的强制类型转换,在第 12 行进行指针的赋值,但是由于申请的只有40字字节的大小,但是我们进行40个 int 类型的数赋值,这个进越界啦。我们在第 15 行释放了指针 p1,但是在第 19 行又使用了指针 p1,这也会出错。我们看看编译后的结果

技术分享图片 

   我们看到直接报段错误了,这便是操作了野指针带来的影响。我们在 C 语言中有这么几个基本原则:a> 绝不返回局部变量和局部数组的地址;b> 任何变量在定义后必须 0 初始化;c> 字符数组必须确认 0 结束符后才能成为字符串;d> 任何使用与内存相关的函数必须指定长度信息。

   我们再看一份代码,在这份代码里野指针更是无处不在

#include <stdio.h> 
#include <string.h>
#include <malloc.h>
 
struct Student
{
   char* name;
   int number;
};
 
char* func()
{
   char p[] = "D.T.Software";
     
   return p;
}
 
void del(char* p)
{
   printf("%s\n", p);
     
   free(p);
}
 
int main()
{
   struct Student s;
   char* p = func();
     
    strcpy(s.name, p); 
     
   s.number = 99;
     
   p = (char*)malloc(5);
   
   strcpy(p, "D.T.Software");
     
   del(p);
     
   return 0;
}

      我们首先在第27行定义了一个结构体变量 s,但是结构体里的指针类型的成员变量没进行初始化。接着定义指针 p 并调用 func 函数进行初始化,但是在 func 函数中返回了局部数组的地址。在第34行申请了5个char*类型大小的空间,在第36行进行字符串的复制,但是越界了。我们看到在这份代码中,野指针是无处不在的。

  内存错误是实际产品开发中最常见的问题,然而绝大多数 bug 都可以通过遵循基本的编程原则和规范来避免。因此,我们在学习的时候要牢记和理解内存操作的基本原则,目的和意义,这样才能达到最小程度的减少 bug。

  常见的内存错误有:a> 结构体成员指针未初始化;b> 结构体成员指针未分配足够的内存;c> 内存分配成功,但并未初始化;d> 内存操作越界。

  我们再以代码为例进行分析说明

#include <stdio.h>
#include <malloc.h>
 
void test(int* p, int size)
{
   int i = 0;
     
   for(i=0; i<size; i++)
   {
       printf("%d\n", p[i]);
   }
     
   free(p);
 }
 
void func(unsigned int size)
{
   int* p = (int*)malloc(size * sizeof(int));
   int i = 0;
     
   if( size % 2 != 0 )
   {
      return; 
   }
     
   for(i=0; i<size; i++)
   {
      p[i] = i;
      printf("%d\n", p[i]);
   }
     
   free(p);
 }
 
int main()
 {
   int* p = (int*)malloc(5 * sizeof(int));
     
   test(p, 5);
     
   free(p); 
     
   func(9);
   func(10);
        
   return 0;
}

      我们看到在程序的第37行申请了堆空间 ,用指针 p 指向这片堆空间。调用了 test 函数,但是在 test 函数内部进行了指针的释放。这是错误的 ,free 是用来释放堆上申请的空间的,但在这块我们释放的是栈上的内存,会造成段错误。我们在第41行继续释放 p,造成了指针的重复释放。我们在 func 函数中如果传入的是奇数便直接返回,申请的内存空间也没有释放,这样会造成内存泄漏。

  编译结果如下

技术分享图片

        再来看一个程序

#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; 
    }
     
    d2.p = (char*)calloc(5, sizeof(char));
     
    printf("%s\n", d2.p);
     
    for(i='a'; i<'z'; i++)
    {
        d2.p[i] = i; 
    }
    
    free(d2.p);
     
    return 0;
}

       我们看到定义的两个结构体,他们的指针成员变量没有进行初始化,在第21行申请堆空间,在第27行进行赋值,但是内存越界了。我们看看编译结果

技术分享图片    

   同样直接报段错误。那么我们在动态内存申请之后,应立即检查指针值是否为 NULL,防止使用 NULL 指针。free 指针之后必须立即赋值为 NULL!任何与内存操作相关的函数都必须带长度信息。malloc 操作和 free 操作必须匹配,防止内存泄漏和多次释放。

   内存错误的本质来源于指针保存的地址为非法值,指针变量为初始化,保存随机值。指针运算导致内存越界。内存泄漏源于 malloc 和 free 不匹配,当 malloc 次数多于 free时,产生内存泄漏;当 malloc 次数少于 free 时,程序可能崩溃!


   欢迎大家一起来学习 C 语言,可以加我QQ:243343083

以上是关于C之内存操作经典问题解析(三十七)的主要内容,如果未能解决你的问题,请参考以下文章

Linux学习总结(三十七)lamp之禁止php解析 user_agent 访问控制 php相关配置

Java经典编程题50道之三十七

第三十七课深度解析QMap与QHash

经典问题解析三(三十)

Powershell管理系列(三十七)PowerShell操作之统计域内计算机硬件资产

java核心学习(三十七) 类加载器