问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

C语言基础:空指针、野指针、空悬指针及内存操作详解

创作时间:
作者:
@小白创作中心

C语言基础:空指针、野指针、空悬指针及内存操作详解

引用
CSDN
1.
https://blog.csdn.net/m0_64837308/article/details/144657131

在C语言编程中,指针的正确使用是确保程序安全和稳定的关键。本文将详细介绍空指针、野指针和空悬指针的概念及其区别,并通过具体示例说明如何避免野指针带来的风险。此外,本文还将深入探讨void和void*的区别,以及常用的内存操作函数的使用方法和注意事项。

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

野指针

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

危害:

  1. 引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault),也有可能编译运行不报错
  2. 引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果

野指针产生的场景:

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

    int a;
    int *p = &a;
    printf("%d\n", *p); // 访问野指针,数据不安全
    
  2. 指针变量未初始化

    int* p; // p是野指针
    printf("%d\n", *p);
    int a = get();
    p = &a;
    
  3. 指针指向的内存空间被(free)回收了

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

    int *get_num()
    {
        int a = 15;
        int *p = &a;
        return p;
    }
    main()
    {
        int *p = get_num();
    }
    

如何避免野指针:

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

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

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

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

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

空指针

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

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

示例:

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

空悬指针

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

// 2.被释放了内存的指针,让其指向雪地址以确保安全
char *p3 = 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,用于动态内存的初始化
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    // 申请空间
    int *p = (int *)malloc(4 * sizeof(int));
    // if(p===NULL)
    if (!p)
    {
        perror("内存申请失败");
        return -1;
    }
    // 给这块内存进行初始化
    memset(p, 0, 4 * sizeof(int));
    printf("%d\n", *(p + 1));
    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)
    
  • 函数功能:拷贝s开始的堆内存空间前n个字节,到dest对应的内存中。
  • 函数参数:
  • void*dest 目标内存首地址
  • void*src:原内存首地址
  • size_t n:拷贝的字节数
  • 返回值:返回dest
  • 注意:内存申请了几个内存空间,就访问几个内存空间,否则数据不安全
    因为memmgve函数是从自适应(从后往前或者从前往后)拷贝,当被拷贝的内存和目的地的内存有重叠时,数据不会出现拷贝错误。而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中
    memove(dest, src, 2 * sizeof(int));
    // 测试输出
    printf("原数组-src:\n");
    for (int i = 0; i < 4; i++)
        printf("%-5d, src[i]);
    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中的数据小于s
  • 注意:n建议和srs,dest的总容量一致;如果不一致,内存比较的结果就不确定了。
#include <stdio.h>
#include <stdlib.h>
#include <string.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, 12);
    *src = 65;
    *(src + 1) = 70;

    *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);
    return 0;
}

内存查找

  • 头文件:

    #include <string.h>
    
  • 函数原型:

    int *memchr|*memrchr(const void *s, int c, size_t n)
    
  • 函数功能:在s开始的堆内存空间前n个字节中查找字节数据c

  • 函数参数:

  • const void *s:代操作内存首地址

  • int c:待查找的字节数据

  • size_tn:查找的字节数

  • 返回值:返回查找到的字节数据地址

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

  • 注意

    void *memrchr(..)//直接用的时候编译报错,需要外部声明
    extern
    
  • 案例

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    int main()
    {
        // 在堆内存申请内存
        int *s = (int *)calloc(4, sizeof(int));
        if (!s)
        {
            perror("内存申请失败!");
            return -1;
        }
        // 给变量赋值
        for (int i = 0; i < 4; i++)
        {
            s[i] = 2 * i;
        }
        for (int i = 0; i < 4; i++)
            printf("%d\n", s[i]);
        printf("\n");
        // 内存查找
        int *x = (int *)memchr(s, 2, 4 * sizeof(int));
        printf("%p,%p,%d\n', x, s, *x);
        free(s);
        s = NULL;
    }
    
    #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] = 2 * i;
        }
        for (int i = 0; i < 4; i++)
            printf("%d\n", s[i]);
        printf("\n");
        // 内存查找
        int *x = (int *)memrchr(s, 2, 4 * sizeof(int));
        printf("%p,%p,%d\n', x, s, *x);
        free(s);
        s = NULL;
    }
    
© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号