【维生素C语言】第六章 - 指针(二)

简介: 本章是指针部分的开始,将对C语言中非常重要的指针进行讲解。本章结束后有能力的读者可对应指针进阶部分进行进一步学习。指针专题配备了一些笔试题,建议尝试。

三、野指针(Wild pointer)


0x00 野指针的概念


📚 概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的);


野指针指向了一块随机的内存空间,不受程序控制;


0x01 野指针的成因

📚 原因:


    ① 指针未初始化;


    ② 指针越界访问;


    ③ 指针指向的空间已释放;


💬 指针未初始化


∵ 局部变量不初始化默认为随机值:


int main()
{
    int a;//局部变量不初始化默认为随机值
    printf("%d", a);
    return 0;
}
∴ 同理,局部的指针变量,如果不初始化,默认为随机值:
int main()
{
    int *p; //局部的指针变量,就被初始化随机值
    *p = 20; //内存中随便找个地址存进去
    return 0;
}

💬 指针越界访问


指针越界,越出arr管理范围时会产生野指针:


int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<12; i++)
    {
        //当指针越出arr管理的范围时,p就称为野指针
        p++;
    }
    return 0;
}

💬 指针指向的空间已释放


int* test()
{
    int a = 10;
    return &a;
}
int main()
{
    int *pa = test();
    *pa = 20;
    return 0;
}

🔑 解析:


    ① 一进入test 函数内时就创建一个临时变量 a(10 - 0x0012ff44),这个a是局部变量,进入范围时创建,一旦出去就销毁,销毁就意味着这个内存空间还给了操作系统,这块空间(0x0012ff44)就不再是 a 的了;


    ② 进入这个函数时创建了 a,有了地址,ruturn &a 把地址返回去了,但是这个函数一结束,这块空间就不属于自己了,当你使用时,这块空间已经释放了,指针指向的空间被指放了,这种情况就会导致野指针的问题;


    ③ 只要是返回临时变量的地址,都会存在问题(除非这个变量出了这个范围不销毁);


0x02 如何规避野指针


💬 指针初始化


int main()
{
    int a = 10;
    int* pa = &a;  // 初始化
    int* p = NULL; // 当你不知道给什么值的时候用NULL
    return 0;
}
💬 指针指向空间释放及时置 NULL
int main()
{
    int a = 10;
    int *pa = &a;
    *pa = 20;
    //假设已经把a操作好了,pa指针已经不打算用它了
    pa = NULL; //置成空指针
    return 0;
}

💬 指针使用之前检查有效性


int main()
{
    int a = 10;
    int *pa = &a;
    *pa = 20;
    pa = NULL; 
    //*pa = 10; 崩溃,访问发生错误,指针为空时不能访问
    if(pa != NULL) { // 检查 如果指针不是空指针
        *pa = 10; // 检查通过才执行
    }  
    return 0;
}

四、指针运算


0x00 指针加整数

💬 指针加整数:打印 1 2 3 4 5 6 7 8 9 10


int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = arr; // 指向数组的首元素 - 1
    for(i=0; i<sz; i++) {
        printf("%d ", *p);
        p = p + 1; //p++   第一次循环+1之后指向2
    }
    return 0;
}

🚩  1 2 3 4 5 6 7 8 9 10


0x01 指针减整数

💬 指针减整数:打印 10 8 6 4 2


int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = &arr[9]; // 取出数组最后一个元素的地址
    for(i=0; i<sz/2; i++) {
        printf("%d ", *p);
        p = p - 2;
    }
    return 0;
}

0x02 指针后置++

5dd97e452b545943cc6a194c484b7196_20210601093008611.png

#include <stdio.h>
#define N_VALUES 5
int main() {
    float value[N_VALUES];
    float* vp;
    for (vp = &value[0]; vp < &value[N_VALUES];) {
        *vp++ = 0;
        printf("%d ", *vp);
    }
    return 0;
}

0x03 指针减指针

📚 说明:指针减指针得到的是元素之间元素的个数;


📌 注意事项:当指针减指针时,他们必须指向同一空间(比如同一个数组的空间);


💬 指针减指针:


int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("%d\n", &arr[9] - &arr[0]); // 得到指针和指针之间元素的个数
    return 0;
}

🚩  9


❌ 错误演示:不在同一内存空间


int ch[5] = {0};
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n", &arr[9] - &ch[0]); // 没有意义,结果是不可预知的

💬 手写 strlen 函数(用指针方法实现):


int my_strlen(char* str)
{
    char* start = str;
    char* end = str;
    while(*end != '\0') {
        end++;
    }
    return end - start; //return
}
int main()
{
    //strlen - 求字符串长度
    //递归 - 模拟实现了strlen - 计数器方式1, 递归的方式2
    char arr[] = "abcdef";
    int len = my_strlen(arr); //arr是首元素的地址
    printf("%d\n", len);
    return 0;
}


⚡ 简化(库函数的写法):


int my_strlen(const char* str)
{
    const char* end = str;
    while(*end++);
    return (end - str - 1);
}
int main()
{
    char arr[] = "abcdef";
    int len = my_strlen(arr);
    printf("%d\n", len);
    return 0;
}

0x04 指针的关系运算(比较大小)

💬 指针减减指针:


#define N_VALUES 5
int main()
{
    float values[N_VALUES];
    float *vp;
    for(vp=&values[N_VALUES]; vp> &values[0]; ) {
        *--vp = 0; //前置--
    }
    return 0;
}

⚡ 简化(这么写更容易理解,上面代码 *--vp在最大索引后的位置开始访问的):


int main()
{
    float values[5];
    float *vp;
    for(vp=&values[N_VALUES]; vp> &values[0]; vp--) {
        *vp = 0;
    }
    return 0;
}

❗  实际在绝大部分编译器上是可行的,但是我们应该避免这么写,因为标准并不保证它可执行;、


🔑  解析:标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的拿个内存位置的指针比较,但是不允许与指向第一个元素之前的拿个内存位置的指针进行比较;

2daeda667e730fc6b65bb5581cba3a69_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

相关文章
|
13天前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
66 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
13天前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
41 9
|
13天前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
38 7
|
24天前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
86 13
|
17天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
17天前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
50 3
|
18天前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
17天前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
31 1
|
21天前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
21天前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。