C语言:指针(第五天)

C语言:指针(第五天)

野指针、空指针、空悬指针

野指针

定义:指向一块未知区域(已经销毁或者访问受限的内存区域外的已存在或不存在的内存区域)的指针,被称作野指针。野指针是危险的。

危害:

① 引用也野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault),也有可能编译运行不报错。

② 引用野指针,可能破坏系统的关键数据,导致系统崩溃等严重后果

野指针产生的场景:

  1. 变量未初始化,通过指针访问该变量

    int a;
    int *p = &a;// p是野指针
    printf("%d\n",*p);
    
  2. 指针变量未初始化

    int *p;
    
    
  3. 指针指向的内存空间被(free)回收了

    int *p = malloc(4);
    *p = 12;// 此时的指针不是野指针
    free(p);
    printf("%d\n",*p);// 此时的p就是野指针
    
  4. 指针函数中直接返回了局部变量的地址

    int* get_num()
    {
        int a = 15;
        int *p = &a;// 此时p对应的数据是一个局部部作用域的数据
        return 0;
    }
    main()
    {
        int *p = get_num();// 此时p是野指针
    }
    

    如何避免野指针?

    1. 指针变量要及时初始化,如果暂时没有对应的值,建议赋初值NULL;

    2. 数组操作(遍历和指针运算)时,注意述责的长度,避免越界

    3. 指针指向的内存空间被回收,建议给这个指针变量赋值为NULL

      int *p = (int *)malloc();
      free(p);
      p = NULL;
      
    4. 指针变量使用之前要检查它的有效性(非空校验)

      int* p = NULL;
      
      // if(p==NULL)
      if(!p)
      {
          return -1;
      }
      

空指针

很多情况下,我们不可避免的会遇到野指针,比如我们刚定义的指针无法立即为其分配一块恰当的内存,又或者指针指向的内存已经被释放了等等。一般的做法是将这些危险的野指针指向一块确定的内存,比如零地址内存(NULL)。

在这里插入图片描述

定义:空指针即保存了零地址的指针(赋值为NULL的指针),也就是指向零地址的指针。(NULL是空常量,它的值是0,这个NULL一般存放在内存0x0000 0000的位置,这个地址只能存NULL,不能被其他程序修改)

示例:

// 1.刚定义的指针,让其指向零地址以确保安全
char* p1 = NULL;
int* p2 = NULL;

// 2.被释放了内存的指针,让其指向零地址以确保安全
char *p3 = (char *)malloc(100);
free(p3);
p3 = NULL;

int sum = 0;

空悬指针

在C语言中,悬空指针指的是指向已删除(或释放)的内存位置的指针。如果一个指针指向的内存已经被释放,但指针本身并未重新指向其他有效的内存地址,那么这个指针就变成了悬空指针。悬空指针会引发不可预知的错误,并且如果一旦发生,就很难定位,因此在编程中尽量避免使用悬空指针。

char *p3 = (char *)malloc(100);
free(p3);
printf("%p,%c\n",p3,*p3);

void与void*的区别

定义:

  • void:是空类型,是数据类型的一种
  • void*:是指针类型的一种,可以匹配任意类型的指针,类似通配符,又被称作万能指针

void:

  • 说明:void作为返回值类型使用,表示没有返回值;作为形参,表示形参列表为空,在调用的时候不能给实参

  • 举例:

    // 函数定义
    void fun(void){..} // 等效于void fun(){}
    // 函数调用
    fun();
    

void*:

  • 说明:

    • void*是一个指针类型,但该指针的数据类型不明确,无法通过解引用获取内存中的数据,因为void*不知道访问几个内存单元

    • void*是一种数据类型,可以作为函数返回值类型,也可以作为形参类型

    • void*类型的变量在使用之前必须强制类型转换,明确它能够访问几个自己的内存空间

      int *p = (int*)malloc(4);
      
    • void*作为返回值类型,这个函数可以返回任意类型的指针

    • void*作为形参类型,这个函数在调用时,可以给任意类型的指针

  • 总结:

    • void*类似于通配符,不能对void*类型的变量解引用(因为不明确数据类型,所以无法确定内存单元的大小)
    • void*在间接访问(解引用)前要强制类型转换,但不能太随意,否则存和取的数据类型不一致

内存操作

我们对于内存的操作需要依赖于string库(对应的头文件string.h)完成内存的操作

常用内存操作函数

内存填充
  • 头文件:#include <string.h>
  • 函数原型:void* memset(void* s,int c,size_t n)
  • 函数功能:填充s开始的堆内存空间前n个字节,使得每个字节值为c
  • 函数参数:
    • void* s:代操作内存首地址
    • int c:填充的字节数据
    • size_t n:填充的字节数
  • 返回值:返回s
  • 注意:c常常设置为0,用于动态内存初始化
// 内存操作:内存填充(memset)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char* argv[])
{
    // 在堆内存中申请空间
    int *p = (int*)malloc(4 * sizeof(int));
    // int *p = {int*}calloc(4,sizeof(int));
    if(!p)
    {
        printf("内存申请失败!");
        return -1;
    }
    // 给这块内存进行初始化操作
    memset(p,0,4 * sizeof(int));
    
    printf("%d\n",*(p+1));
    
    // 释放内存
    free(p);
    p = NULL;
    return 0;
}
内存拷贝
  • 头文件:string.h

  • 函数原型:void* memcpy(void* dest,const void* src,size_t n)适合目标地址与源地址内存无重叠的情况。

    void* memmove(void* dest,const void* src,size_t n)

  • 函数功能:拷贝src开始的堆内存空间前n个字节,到dest对应的内存中

  • 函数参数:

    • void* dest:目标内存首地址
    • void* src:源内存首地址
    • size_t n:拷贝的字节数
  • 返回值:返回dest

  • 注意:内存申请了几个内存空间,就访问几个内存空间,否则数据不安全

  • 注意:memcpy与memmove一般情况下是一样的,更建议使用memmove进行内存拷贝;

    因为memmove函数是从自适应(从后往前或者从前往后)拷贝,当被拷贝的内存和目的地的内存有重叠时,数据不会出现拷贝错误。而memgpy函数是从前往后拷贝,当被拷贝的内存和目的地内存有重叠时,数据会出现拷贝错误。

  • 案例:

    // 内存操作:内存拷贝(memmove | memcpy)
    
    #include <stdio.h>
    #include <string.h>
    
    int main()
    {
        // 创建源空间和目标空间
        int src[4] = {11,22,33,44};
        int dest[6] = {111,222,333,444,555,666};
        
        // 将src中的数据拷贝到dest中
        memmove(dest+1,src+1,2 * sizeof(int));// {11,22,333,444,555,666}
        
        // 测试输出
        printf("源数组-src:\n");
        for(int i = 0;i < 4;i++)
        {
            printf("%-5d",src[i]);
        }
        printf("\n");
        printf("目标数组-dest:\n");
        for(int i = 0;i < 6;i++)
        {
            printf("%-5d",dest[i]);
        }
        printf("\n");
        
        return 0;
    }
    
内存比较
  • 头文件:#include <string.h>

  • 函数原型:int memcmp(void *dest,const void *src,size_t n)

  • 函数功能:比较src和dest所代表的内存前n个字节的数据

  • 函数参数:

    • void* dest:目标内存首地址
    • const void* src:源内存首地址
    • size_t n:比较的字节数
  • 返回值:

    • 0:数据相同
    • >0:dest中的数据大于src
    • <0:dest中的数据小于src
  • 注意:n一般和src,dest的总容量一致;如果不一致,内存比较的结果就不确定了。

  • 案例:

    // 内存操作-内存比较:memcmp()
    
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    
    int main()
    {
        // 申请内存
        int* src = (int*)malloc(3*sizeof(int));
        int* dest = (int*)calloc(4,sizeof(int));
        
        if(!src || !dest)
        {
            perror("内存申请失败!");
            return -1;
        }
        // 对使用malloc申请的空间清零
        bzero(src,3 * sizeof(int));
        
        *src = 65;
        *(src+1) = 66;
        
        *dest = 70;
        *(dest+1) = 55;
        
        int result = memcmp(dest,src,2 * sizeof(int));
        
        char *a = (char*)src;
        char *b = (char*)dest;
        
        int result2 = memcmp(b,a,sizeof(char));
        printf("%d,%d\n",result,result2);
        
        free(src);
        free(dest);
        
        src = NULL;
        dest = NULL;
        
        return 0;
    }
    
内存查找
  • 头文件:#include <string.h>

  • 函数原型:int *memchr | *memrchar

  • 函数功能:在s开始的堆内存空间前n个字节中查找字节数据c

  • 函数参数:

    • const void *s:代操作内存首地址
    • int c:带查找的字节数据
    • size_t n:查找的字节数
  • 返回值:返回查找到的字节数据地址

  • 注意:如果内存中没有重复数据,memchr和memrchr结果是一样的;如果内存中有重复数据,memchr和memrchr结果就不一样

  • 注意:

    void *memrchr(..);// 在使用时编译器会报错,需要使用外部声明
    
    // 外部声明
    extern void* memrchr(..);
    
  • 举例:

    案例1:

    // 内存操作-内存查找:memchr | memrchr(需要外部声明)
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    // 声明外部定义的函数
    extern void* memrchr(const void* s,int c,size_t n);
    
    int main()
    {
        // 在堆内存申请内存
        int *s = (int*)calloc(4,sizeof(int));
        
        if(!s)
        {
            perror("内存申请失败!");
            return -1;
        }
        
        // 给变量赋值
        for(int i = 0;i < 4;i++)
        {
            s[i] = i * 2;
        }
        s[3] = 2;
        
        // 输出
        for(int i = 0;i < 4;i++)
        {
            printf("%d\n",s[i]);
        }
        printf("\n");
        
        // 内存查找 memchr,匹配首个查到的位置
        int *x = (int*)memchr(s,2,4 * sizeof(int));
        printf("%p,%p,%d\n",x,s,*x);
        
        // 内存查找 memrchr,匹配末个查到的位置
        int *y = (int*)memrchr(s,2,4 * sizeof(int));
        printf("%p,%p,%d\n",y,s,*y);
        
        // 内存回收
        free(s);
        s = NULL;
        return 0;
    }
    

    案例2:

    // 内存操作-内存查找:memchr | memrchr(需要外部声明)
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    // 声明外部定义的函数
    extern void* memrchr(const void* s,int c,size_t n);
    
    int main()
    {
        // 在堆内存申请内存
        int *s = (int*)calloc(4,sizeof(char));
        
        if(!s)
        {
            perror("内存申请失败!");
            return -1;
        }
        
        // 给变量赋值
        for(int i = 0;i < 4;i++)
        {
            s[i] = i + 65d;
        }
        s[3] = 2;
        
        // 输出
        for(int i = 0;i < 4;i++)
        {
            printf("%d\n",s[i]);
        }
        printf("\n");
        
        // 内存查找 memchr,匹配首个查到的位置
        int *x = (int*)memchr(s,2,4 * sizeof(int));
        printf("%p,%p,%d\n",x,s,*x);
        
        // 内存查找 memrchr,匹配末个查到的位置
        int *y = (int*)memrchr(s,2,4 * sizeof(int));
        printf("%p,%p,%d\n",y,s,*y);
        
        // 内存回收
        free(s);
        s = NULL;
        return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值