一、指针是什么
这部分内容,在我们之前的文章中已经提及过
这里在简单的回忆一下
学习指针必须先要理解——内存,内存是电脑上的存储设备,一般都是4G/8G/16G等,程序运行的时候会加载到内存中,也会使用内存空间
我们将内存划分为一个个小格子,每一个格子是一个内存单元,也正好是一个字节的大小,对每一个内存单元进行编号,在生活中我们也将这一个个编号称作地址,而地址在c语言中又叫做指针
我们举一个例子,假设我们定义一个变量 int a=10;那么a是一个int类型的变量,需要占用四个字节的空间,而每个字节都有地址,&a取出的是哪一个的地址呢?其实取出的是第一个字节的地址(较小的地址),也就是说,下图中,&a最终取出来的地址是0x0012ff40 ,而这个地址我们可以存放到一个变量中,int* pa=&a,这颗*代表pa是一个指针,int代表pa所指向的类型是int类型,这个pa也叫做指针变量。
理解指针需要理解两个要点
1.指针是内存中一个最小单元的编号,也就是地址
2.平时口头语中所说的指针,通常指的是变量,是用来存放内存地址的变量
总结:指针就是地址,平时口头语中的指针通常指的是指针变量
我们看这段代码
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int a = 10; int* p = &a; printf("%p\n", &a); printf("%p\n", p); return 0; }
运行结果为,打印出来的地址是一样的
我们还可以看这段代码,使用*来解引用指针
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() { int a = 10; int* p = &a; printf("%p\n", &a); printf("%p\n", p); *p = 20; printf("%d", a); return 0; }
运行结果为
总结:指针变量,就是用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
那这里的问题是: 一个小的单元到底是多大? 其实是1个字节
那么如何编址呢?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(高电压)和低电 平(低电压)就是(1或者0)
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
.....
11111111 11111111 11111111 11111111
这里就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给 (2^32Byte == 2^32/1024KB == 2^32/1024/1024MB==2^32/1024/1024/1024GB == 4GB)
4G的空间进行编址。 同样的方法,那64位机器,如果给64根地址线,那能编址多大空间,我们也可以计算出来
这里我们就知道了:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地 址。
总结: 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。 指针的大小在32位平台是4个字节,在64位平台是8个字节
二、指针和指针类型
1.指针类型的意义
我们在上面了解了
32位机器上,地址是4个字节,指针变量的大小也是4个字节
64位机器上,地址是8个字节,指针变量的大小也是8个字节
那在这里就有人产生困惑了,反正都是4个或者8个字节,那为什么要区分int*,char*.....这些呢?为什么不直接弄一个通用指针呢?
其实既然计算机没有这个通用指针,那就说明这些类型是有意义的。我们现在就来探讨以下这些指针的类型的意义。
为了了解这个内容,我们先看这个代码
#include<stdio.h> int main() { int a = 0x11223344; int* pa = &a; *pa=0; return 0; }
我们打开调试,窗口,内存,并将列改为4
继续往下走
我们发现,四个字节全部被改为0
我们在看这一段代码
#include<stdio.h> int main() { int a = 0x11223344; //int* pa = &a; char* pc = &a; *pc = 0; return 0; }
我们仍然监视内存
继续往下走
我们发现只改变了一个字节。
所以我们得出结论:指针类型是有意义的
指针类型决定指针进行解引用时候,一次访问几个字节(访问权限)
int*访问四个字节 char*访问一个字节,float*访问四个字节
我们在继续看这个代码
#include<stdio.h> int main() { int a = 0x11223344; int* pa = &a; char* pc = &a; printf("%p\n", pa); printf("%p\n", pa+1); printf("%p\n", pc); printf("%p\n", pc+1); return 0; }
运行结果为
由此我们发现,pa+1跳过了四个字节,pc+1跳过了一个字节
所以我们得出
指针类型决定指针的步长(指针+1到底跳过几个字节)
字符指针+1,跳过一个字节
整型指针+1,跳过四个字节
我们可以看这个代码
#include<stdio.h> int main() { int a = 0x11223344; char* pc = (char*)&a; int i = 0; for (i = 0; i < 4; i++) { *pc = 0; pc++; } return 0; }
我们先取出a的地址放到一个char*类型的指针中,因为a取地址后是一个int*类型的指针,所以要强制类型转换。对其进行遍历四次,每次只能改一个字节的空间,四次刚好将a改为0。我们在这里在总结一下指针类型的意义:
1.
指针类型决定指针进行解引用时候,一次访问几个字节(访问权限)
int*访问四个字节 char*访问一个字节,float*访问四个字节
2.
指针类型决定指针的步长(指针+1到底跳过几个字节)
字符指针+1,跳过一个字节
整型指针+1,跳过四个字节
2.指针+-整数
我们在上面说过,指针的不同类型,其实提供了不同的视角去观看和访问内存
char*一次访问1个字节,+1跳过一个字节
int *一次访问4个字节,+1跳过四个字节
当然在这里,除了+1,还可以-5,+2等操作
比如int* pa=10;
pa+4 其实就是向后走4*sizeof(int)个字节,也就是+16个字节
pa-5 其实就是向前走5*sizeof(int)个字节,也就是-20个字节
也就是指针类型决定了指针向前或向后走一步有多大(距离)
3.指针解引用
这里也同样在前面说过了
指针的类型决定了对指针解引用时有多大权限(能操作几个字节)
比如char*指针解引用一次就能操作一个字节,而int*指针解引用一次可以操作4个字节
三、野指针
概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
1.野指针的成因
(1)指针未初始化
#include <stdio.h> int main() { int* p;//局部变量指针未初始化,默认为随机值 *p = 20; return 0; }
如上代码所示,指针是一个局部变量未初始化,默认未随机值,这样随意进行修改指针的值是非常危险的行为。
(2)指针越界访问
#include <stdio.h> int main() { int arr[10] = { 0 }; int* p = arr; int i = 0; for (i = 0; i <= 11; i++) { //当指针指向的范围超出数组arr的范围时,p就是野指针 *(p++) = i; } return 0; }
如上代码所示,指针的指向范围超出了数组arr的范围,p就是一个野指针,此时如果随意修改指针里的内容是很危险的,假如这个野指针指向的恰好就是本程序已有的其他值,那么就会出现问题。