在计算机科学中,指针(Pointer
),是编程语言中的一类数据类型及其对象或变量,用来表示或存储一个存储器地址,这个地址的值直接指向存在该地址的对象的值。
指针参考了存储器中一个地址。通过被称为指针反参考(Dereferencing
)的动作,可以取出在那个地址中存储的值。保存在指针指向的地址中的值,可能代表另一个变量、结构、对象或函数。但是从指针值是无法得知它所引用的存储器中存储了什么资料类型的信息。
基础常识
一个变量的地址称为该变量的指针,它类似于公民的身份证号码。如果把人比作对象,那么身份证号码就是这个人的地址
指针也是一个对象,只是负责的工作特殊。它类似于明星的经纪人,你想找到明星,必须由经纪人传达,找到经纪人相当于找到明星本人
每个对象都会占用内存空间,每个对象都会存在一个对应的内存地址。因此,同样身为对象的指针,也有它自己的内存地址
基本定义
- 指针:一个变量的地址称为该变量的指针
- 指针类型:例如,指向整形数据的指针类型为
int *
- 指针变量:存放另外一个变量地址的变量,指针变量的值是地址
- 指针变量中只能存放地址,不能将一个整数赋值给指针变量
- 指针变量的定义:
类型名 * 指针变量名
- 类型可以强制转换,但结构体和基本类型不能强制转换
- 任何变量都可以使用取地址运算符进行内存寻址
运算符
&
:取地址运算符。例如:&a
代表a
的地址*
:指针运算符,又称间接访问运算符。例如:p
代表指针,p
指向的对象a = xxx
案例1:
指针类型的数据宽度
打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" void func(){ int *a; printf("%lu",sizeof(a)); } @implementation ViewController - (void)viewDidLoad { // [super viewDidLoad]; func(); } @end
真机运行项目,来到
func
方法
![]()
sizeof
不是函数,而是一个操作符。编译后直接显示数字- 指针类型的数据宽度
8字节
案例2:
指针的自增、自减
打开
ViewController.m
文件,写入以下代码:void func(){ int *a; a = (int *)100; a++; printf("%d", a); }
- 指针的自增、自减运算,和指向的数据类型的宽度有关
int
类型占4字节
,a++
相当于+4
- 案例打印结果:
104
如果
a
指向char
类型void func(){ char *a; a = (char *)100; a++; printf("%d", a); }
char
类型占1字节
,a++
相当于+1
- 案例打印结果:
101
如果
a
为二级指针void func(){ int **a; a = (int **)100; a = a + 1; printf("%d", a); }
- 指针的加减,无论何种写法,都跟指向的数据宽度有关
a
为二级指针,指向的还是指针类型,占8字节
,a = a + 1
相当于+8
- 案例打印结果:
108
自增、自减和编译器有关,在
a++
和a--
的时候,部分编译器不遵守规则,得到的结果会出现问题,所以尽量使用a = a + 1
的方式代替
案例3:
指针的运算单位
打开
ViewController.m
文件,写入以下代码:void func(){ int *a; a = (int *)100; int *b; b = (int *)200; int x = a - b; printf("%d", x); }
- 指针的运算单位是指向的数据类型的宽度
- 案例中
a - b
等于-100
,由于指向的int
类型,运算结果还要除以4
- 案例打印结果:
-25
指针之间比较大小
void func(){ int *a; a = (int *)100; int *b; b = (int *)200; if(a > b){ printf("a > b"); } else{ printf("a <= b"); } }
案例打印结果:
a <= b
案例4:
指针的反汇编形式
#import "ViewController.h" void func(){ int *a; int b = 100; a = &b; } @implementation ViewController - (void)viewDidLoad { // [super viewDidLoad]; func(); } @end
真机运行项目,来到
func
方法
![]()
add x8, sp, #0x4
:将sp + #0x4
地址,写入x8
寄存器mov w9, #0x64
:将#0x64
,即10进制
的100
写入w9
寄存器,相当于b = 100
str w9, [sp, #0x4]
:将w9
的值,写入sp + #0x4
地址。写入w9
值的地址和x8
寄存器指向的是相同地址,相当于a = &b
str x8, [sp, #0x8]
:将x8
的值,写入sp + #0x8
地址。这里sp
偏移8字节
写入的就是指针,存储指向w9
值的所在地址![]()
案例5:
指针的基本用法
打开
ViewController.m
文件,写入以下代码:void func(){ int arr[5] = {1, 2, 3, 4, 5}; for (int i = 0; i < 5; i++) { printf("%d", arr[i]); } }
- 常规方式,循环遍历
arr
数组的元素- 案例打印结果:
12345
使用指针运算的方式,循环遍历
arr
数组的元素void func(){ int arr[5] = {1, 2, 3, 4, 5}; for (int i = 0; i < 5; i++) { printf("%d", *(arr + i)); } }
arr
也是一个指针int *a == arr == &arr[0]
,它们之间是对等关系- 指针指向
int
类型,所以步长为4字节
- 每次
arr + i
相当于指针移动了i * 4
字节- 案例打印结果:
12345
案例6:
EXC_BAD_ACCESS
的反汇编形式打开
ViewController.m
文件,写入以下代码:#import "ViewController.h" void func(){ int *a; int b = *a; } @implementation ViewController - (void)viewDidLoad { // [super viewDidLoad]; func(); } @end
真机运行项目,来到
func
方法
![]()
ldr x8, [sp, #0x8]
:读取sp + #0x8
地址,写入x8
寄存器- 由于
*a
未初始化,从栈中读取的值为0
![]()
ldr w9, [x8]
:将x8
的值0
,寻址写入x9
。此时:EXC_BAD_ACCESS
![]()
案例7:
指针移动步长的反汇编形式
打开
ViewController.m
文件,写入以下代码:void func(){ int *a; int b = *a; int c = (a + 0); }
真机运行项目,来到
func
方法
![]()
- 两种方式的反汇编代码完全相同
- 步长
+0
,所在地址直接取值
修改代码,查看步长
+1
的反汇编形式void func(){ int *a; int b = *a; int c = *(a + 0); int d = *(a + 1); }
真机运行项目,来到
func
方法
![]()
- 步长
+1
,相当于从地址 + #0x4
位置取值#0x4
:指针指向的数据类型int
占4字节
- 代码中
b
、c
、d
三个指针变量,共计24字节
。对栈空间的操作为16字节
对齐,所以需要拉伸栈空间32字节
,即:#0x20
案例8:
二级指针寻址的反汇编形式
打开
ViewController.m
文件,写入以下代码:void func(){ int **a; int b = **a; int c = **(a + 0); int d = **(a + 1); }
真机运行项目,来到
func
方法
![]()
- 步长
+1
,相当于从地址 + #0x8
获取值#0x8
:二级指针指向的数据类型是指针,占8字节
- 当
x8
从栈中取值后,先将x8
的值作为地址,取值写入x8
。再将x8
的值作为地址,取值写入w9
- 遇到
ldr
寻址两次,说明此处是二级指针寻址
案例9:
二级指针移动步长的反汇编形式
打开
ViewController.m
文件,写入以下代码:void func(){ char **a; char b = *(*(a + 2) + 2); }
真机运行项目,来到
func
方法
![]()
- 第一次运算为二级指针
char **
,指向的数据类型是char *
,占8字节
。步长+2
为16字节
,即:#0x10
- 第二次运算为指针
char *
,指向的数据类型是char
,占1字节
。步长+2
为2字节
,即:#0x2
另一种移动步长的写法:
void func(){ char **a; char b = a[1][2]; }
真机运行项目,来到
func
方法
![]()
- 第一次运算
#0x8
- 第二次运算
#0x2
总结
指针
- 指针的宽度是指它所指向的数据类型的宽度,即:步长
- 指针的自增、自减,按照指针的步长来计算的
网友评论