【维生素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

相关文章
|
5天前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
8天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
14天前
|
存储 安全 C语言
C语言 二级指针应用场景
本文介绍了二级指针在 C 语言中的应用,
|
30天前
|
存储 编译器 C语言
【C语言篇】深入理解指针2
代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
|
30天前
|
存储 程序员 编译器
【C语言篇】深入理解指针1
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
|
1月前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
29天前
|
C语言
【C语言】指针速览
【C语言】指针速览
16 0
|
1月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
1月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
|
3月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)