在计算机科学中,指针(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`方法<br />

> - `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`方法<br />

> - `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`方法<br />

> - `ldr x8, [sp, #0x8]`:读取`sp + #0x8`地址,写入`x8`寄存器
> - 由于`*a`未初始化,从栈中读取的值为`0`<br />

> - `ldr w9, [x8]`:将`x8`的值`0`,寻址写入`x9`。此时:`EXC_BAD_ACCESS`<br />

> 案例7:
> 指针移动步长的反汇编形式
> 打开`ViewController.m`文件,写入以下代码:
>
void func(){ int a; int b = a; int c = (a + 0); }
> 真机运行项目,来到`func`方法<br />

> - 两种方式的反汇编代码完全相同
> - 步长`+0`,所在地址直接取值
>
---
> 修改代码,查看步长`+1`的反汇编形式
>
void func(){ int a; int b = a; int c = (a + 0); int d = (a + 1); }
> 真机运行项目,来到`func`方法<br />

> - 步长`+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`方法<br />

> - 步长`+1`,相当于从`地址 + #0x8`获取值
> - `#0x8`:二级指针指向的数据类型是指针,占`8字节`
> - 当`x8`从栈中取值后,先将`x8`的值作为地址,取值写入`x8`。再将`x8`的值作为地址,取值写入`w9`
> - 遇到`ldr`寻址两次,说明此处是二级指针寻址
> 案例9:
> 二级指针移动步长的反汇编形式
> 打开`ViewController.m`文件,写入以下代码:
>
void func(){ char *a; char b = (*(a + 2) + 2); }
> 真机运行项目,来到`func`方法<br />

> - 第一次运算为二级指针`char **`,指向的数据类型是`char *`,占`8字节`。步长`+2`为`16字节`,即:`#0x10`
> - 第二次运算为指针`char *`,指向的数据类型是`char`,占`1字节`。步长`+2`为`2字节`,即:`#0x2`
>
---
> 另一种移动步长的写法:
>
void func(){ char **a; char b = a[1][2]; } ```
真机运行项目,来到
func
方法
- 第一次运算
#0x8
- 第二次运算
#0x2
总结
指针
- 指针的宽度是指它所指向的数据类型的宽度,即:步长
- 指针的自增、自减,按照指针的步长来计算的