打印
[其它产品/技术]

指针是什么啥?

[复制链接]
53|5
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
szt1993|  楼主 | 2025-2-22 22:56 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式

变量放在哪?上面我说,定义一个变量实际就是向计算机申请了一块内存来存放。

那如果我们要想知道变量到底放在哪了呢?可以通过运算符&来取得变量实际的地址,这个值就是变量所占内存块的起始地址。

PS: 实际上这个地址是虚拟地址,并不是真正物理内存上的地址

我们可以把这个地址打印出来

printf("%x", &a);

大概会是像这样的一串数字:0x7ffcad3b8f3c

上面说,我们可以通过&符号获取变量的内存地址,那获取之后如何来表示这是一个地址,而不是一个普通的值呢?

也就是在 C 语言中如何表示地址这个概念呢?

对,就是指针,你可以这样


int *pa = &a; 

pa 中存储的就是变量 a 的地址,也叫做指向 a 的指针。

在这里我想谈几个看起来有点无聊的话题:

  为什么我们需要指针?直接用变量名不行吗?

当然可以,但是变量名是有局限的。

  变量名的本质是什么?

是变量地址的符号化,变量是为了让我们编程时更加方便,对人友好,可计算机可不认识什么变量 a,它只知道地址和指令。

所以当你去查看 C 语言编译后的汇编代码,就会发现变量名消失了,取而代之的是一串串抽象的地址。

使用特权

评论回复
沙发
szt1993|  楼主 | 2025-2-22 22:57 | 只看该作者
你可以认为,编译器会自动维护一个映射,将我们程序中的变量名转换为变量所对应的地址,然后再对这个地址去进行读写。

也就是有这样一个映射表存在,将变量名自动转化为地址:


a  | 0x7ffcad3b8f3c
c  | 0x7ffcad3b8f2c
h  | 0x7ffcad3b8f4c

....
说的好!

可是我还是不知道指针存在的必要性,那么问题来了,看下面代码:

int func(...) {
  ...
};

int main() {
int a;
func(...);
};


假设我有一个需求:

要求在func 函数里要能够修改 main 函数里的变量 a,这下咋整,在 main 函数里可以直接通过变量名去读写 a 所在内存。
但是在 func 函数里是看不见a 的呀。


你说可以通过&取地址符号,将 a 的地址传递进去:

int func(int address) {
  ....
};

int main() {
int a;
func(&a);
};


这样在func 里就能获取到 a 的地址,进行读写了。

理论上这是完全没有问题的,但是问题在于:

编译器该如何区分一个 int 里你存的到底是 int 类型的值,还是另外一个变量的地址(即指针)。

这如果完全靠我们编程人员去人脑**了,会引入复杂性,并且无法通过编译器检测一些语法错误。

而通过int * 去定义一个指针变量,会非常明确:这就是另外一个 int 型变量的地址。

使用特权

评论回复
板凳
szt1993|  楼主 | 2025-2-22 22:58 | 只看该作者
编译器也可以通过类型检查来排除一些编译错误。

这就是指针存在的必要性。

实际上任何语言都有这个需求,只不过很多语言为了安全性,给指针戴上了一层枷锁,将指针包装成了引用。

可能大家学习的时候都是自然而然的接受指针这个东西,但是还是希望这段啰嗦的解释对你有一定启发。

同时,在这里提点小问题:

既然指针的本质都是变量的内存首地址,即一个 int 类型的整数。

那为什么还要有各种类型呢?


比如 int 指针,float 指针,这个类型影响了指针本身存储的信息吗?


这个类型会在什么时候发挥作用?


解引用

上面的问题,就是为了引出指针解引用的。

pa中存储的是a变量的内存地址,那如何通过地址去获取a的值呢?

这个操作就叫做解引用,在 C 语言中通过运算符 *就可以拿到一个指针所指地址的内容了。

比如*pa就能获得a的值。

我们说指针存储的是变量内存的首地址,那编译器怎么知道该从首地址开始取多少个字节呢?

这就是指针类型发挥作用的时候,编译器会根据指针的所指元素的类型去判断应该取多少个字节。

使用特权

评论回复
地板
szt1993|  楼主 | 2025-2-22 23:00 | 只看该作者
如果是 int 型的指针,那么编译器就会产生提取四个字节的指令,char 则只提取一个字节,以此类推。

下面是指针内存示意图:


pa 指针首先是一个变量,它本身也占据一块内存,这块内存里存放的就是 a 变量的首地址。

当解引用的时候,就会从这个首地址连续划出 4 个 byte,然后按照 int 类型的编码方式解释。

别看这个地方很简单,但却是深刻理解指针的关键。

举两个例子来详细说明:

比如:

float f = 1.0;
short c = *(short*)&f;


你能解释清楚上面过程,对于 f 变量,在内存层面发生了什么变化吗?或者 c 的值是多少?1 ?

实际上,从内存层面来说,f 什么都没变。

如图:


假设这是f 在内存中的位模式,这个过程实际上就是把 f 的前两个 byte 取出来然后按照 short 的方式解释,然后赋值给 c。

详细过程如下:


&f取得f 的首地址
(short*)&f

上面第二步什么都没做,这个表达式只是说 :

“噢,我认为f这个地址放的是一个 short 类型的变量”

最后当去解引用的时候*(short*)&f时,编译器会取出前面两个字节,并且按照 short 的编码方式去解释,并将解释出的值赋给 c 变量。

这个过程 f的位模式没有发生任何改变,变的只是解释这些位的方式。

当然,这里最后的值肯定不是 1,至于是什么,大家可以去真正算一下。

使用特权

评论回复
5
szt1993|  楼主 | 2025-2-22 23:01 | 只看该作者
如果是 int 型的指针,那么编译器就会产生提取四个字节的指令,char 则只提取一个字节,以此类推。

下面是指针内存示意图:


pa 指针首先是一个变量,它本身也占据一块内存,这块内存里存放的就是 a 变量的首地址。

当解引用的时候,就会从这个首地址连续划出 4 个 byte,然后按照 int 类型的编码方式解释。

别看这个地方很简单,但却是深刻理解指针的关键。

举两个例子来详细说明:

比如:

float f = 1.0;
short c = *(short*)&f;


你能解释清楚上面过程,对于 f 变量,在内存层面发生了什么变化吗?或者 c 的值是多少?1 ?

实际上,从内存层面来说,f 什么都没变。

如图:


假设这是f 在内存中的位模式,这个过程实际上就是把 f 的前两个 byte 取出来然后按照 short 的方式解释,然后赋值给 c。

详细过程如下:


&f取得f 的首地址
(short*)&f

上面第二步什么都没做,这个表达式只是说 :

“噢,我认为f这个地址放的是一个 short 类型的变量”

最后当去解引用的时候*(short*)&f时,编译器会取出前面两个字节,并且按照 short 的编码方式去解释,并将解释出的值赋给 c 变量。

这个过程 f的位模式没有发生任何改变,变的只是解释这些位的方式。

当然,这里最后的值肯定不是 1,至于是什么,大家可以去真正算一下。

使用特权

评论回复
6
szt1993|  楼主 | 2025-2-22 23:02 | 只看该作者
那反过来,这样呢?


<p>short c = 1;</p><p>float f = *(float*)&c;</p>

如图:


具体过程和上述一样,但上面肯定不会报错,这里却不一定。

为什么?

(float*)&c会让我们从c  的首地址开始取四个字节,然后按照 float 的编码方式去解释。

但是c是 short 类型只占两个字节,那肯定会访问到相邻后面两个字节,这时候就发生了内存访问越界。

当然,如果只是读,大概率是没问题的。

但是,有时候需要向这个区域写入新的值,比如:

*(float*)&c = 1.0;

那么就可能发生 coredump,也就是访存失败。

另外,就算是不会 coredump,这种也会破坏这块内存原有的值,因为很可能这是是其它变量的内存空间,而我们去覆盖了人家的内容,肯定会导致隐藏的 bug。

如果你理解了上面这些内容,那么使用指针一定会更加的自如。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

300

主题

2425

帖子

6

粉丝