三、野指针(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 指针后置++
#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; }
❗ 实际在绝大部分编译器上是可行的,但是我们应该避免这么写,因为标准并不保证它可执行;、
🔑 解析:标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的拿个内存位置的指针比较,但是不允许与指向第一个元素之前的拿个内存位置的指针进行比较;