C语言_指针详解

本文面向C语言初学者,介绍了指针的相关知识。指针起源于汇编,C语言保留了汇编的寻址方式。指针与地址的关系是存储地址编号以便查找调用。指针的速度快慢取决于编译器的优化,编译器若采用直接寻址翻译代码,速度会更快。

指针

 

概要

  • 什么是指针?
  • 指针与地址的关系
  • 指针快在哪儿?

前言

本篇博客对初学C语言的人介绍一下什么是指针,但会稍带底层的讲解,当您看完本篇博客之后,想要更加深刻的知道内部寻址的方式可以去参考我底层原理专栏的文章!

什么是指针?

指针起源于汇编,在早期的汇编里寻址方式有三种:

1.直接寻址

2.间接寻址

3.立即寻址

这些寻址方式,都是对内存地址进行寻址,也就是与内存芯片进行交互

但是汇编语言大多数都是助记符,也不存在函数这个概念,所以在发展的过程中诞生了C语言(在此之前还有B语言(半汇编的语言)研发unix过程中诞生的),C语言是完全将汇编封装起来的一门语言,并且用汇编语言编写了很多函数,将其封装了起来,使我们可以直接的调用,比如printf,是向文件IO写入数据,在调用操作系统API来将我们的IO导入到显卡里!

换句话说,C语言其实很依赖系统API,大多数文件级操作或涉及到内存读写(远),必会调用系统API,因为虚拟内存的保护模式(详细可以参考我的其它文章)!

在汇编中寻址是直接ds段地址+偏移地址然后mov到指定寄存器,在由指定寄存器到内存某处(必须先将地址里的值取出来做中转站)

mov ax,[0x1a2b3cH]

在C语言中保留了这种寻址,就是指针

一般声明指针是在变量类型后面加个“*”即可

int *a = NULL;    //int型指针

相信很多人都知道如果指针初始化不给指定地址的话会变成野指针

野指针其实就是一个随机地址,当我们在C语言的一个函数体里声明一个变量时,编译器不会给它初始化,而是给它一个随机值!

换句话说编译器会给它初始化但是不会给0,而是给随机数,可以说是编译器在写push汇编指令时,开辟内存是向操作系统要的栈内存,也就是会调用系统API来要内存,但是这个内存说不定被其它人用过了,然后给释放掉了,但是这个内存里的值操作系统也没有初始化0或者给了它一个随机数,所以程序在拿到这个内存时,无法确定这个里面的值是多少,当我们在后面加一个“=0”的初始化表达时,编译器会增加额外的代码,也就是当我们拿到这个内存,向这个内存写入0,但0的二进制数是00,所以如果我们开了四个字节的内存,一个0仅一个bit就可以表示,那么编译器怎样初始化其它单元的?

答:

也是给0,在转换时是二进制转十进制或其它进制的算法!

4个字节32bit全部都是0,如果是1的话也会从低位到高位逐渐给1!

这个在底层的术语上叫电信号,不叫二进制!二进制是往上层的叫法!

 

C语言将

MOV ax,[0x1a2b3c]

演变成了

int *a = 0x1a2c3b;

也就是将地址保存到栈区里,然后当我们要用时,在到栈区里将地址取出来,然后在使用汇编的寻址:

MOV ax,[0x1a2c3b]

来进行寻址运算,至于做什么取决于你的表达式!

归根到底最后C语言都会将你对指针进行操作的演变成汇编将指针里的值取出来,然后在使用汇编的寻址方式来寻址!

所以总结:C语言的指针实则上是保留了汇编的寻址方式,只是换了个法子来表达,只是将要寻址的地址保存到我们程序的内存栈里,要寻址时在使用汇编的寻址方式将保存的值取出来,然后在对其寻址!

其实这么说是废话,因为我们在不同的硬件平台上开发,就只能使用硬件平台为我们提供的指令集,任何语言写的程序最后都会变成硬件平台上的机器码!

你不可能使用独特的寻址方式来寻址,因为这样CPU的翻译器根本不认得这样的寻址方式,会报出硬件中断!

当然你可以使用独特的方式来封装这些寻址方式,硬件平台为我们提供的接口很多,至于你怎么封装,比如C++利用内存布局引用出类,构造函数!

指针与地址的关系

如果将第一篇文章看懂了的话,这篇文章相信指针已经无需介绍了,而地址这个概念的东西,在我很多文章里已经介绍的数不胜数,想吐!

但是这里还是要啰嗦一遍,但不会提虚拟地址,物理地址以及线性地址,甚至CPU是怎样寻址的!

这些可以参考我的其它文章

当我们买一个内存条时,这个内存里包含了很多小型的半导体电子器件,非常小(依靠纳米技术,1MB就有1024个半导体电子器件在里面),这些半导体电子器件是通过内存芯片进行管控的,那么如何管控每一个电子器件呢?

答:

编号,因为内存芯片会接引脚在每一个电子器件上,这样当给定编号,那么内存芯片会根据自己数据位宽来将这个编号后的多少位取出来送给CPU!

所以地址,就算这些电子器件上的编号,地址的上限,也就是编号的最大值!

而指针地址与地址的关系就是指针存储这些编号,供我们方便寻找和调用!

因为内存那么多单元,我们怎么知道要寻找哪一个?所以事先记录,方便下次使用!

使用方法这里就不说了,可以参考CPU底层原理专栏!

指针快在哪儿?

要说指针快在哪儿,这个还真说不上来,网上很多人就在哪儿说:

指针很快

指针立即寻址

指针怎么怎么样

你问他为什么?说依据,就哑口无言了!

反正博主是碰到过一个天天说指针快,写代码一大堆指针,好家伙,基础变量就不用了,频繁malloc!

要说指针快,也不能说快,因为从汇编角度来看的话,你可以看看编译器是怎样反汇编你的程序的!

编译器是否对你的程序进行优化!

也就是对指针操作的,因为在编译期间通过文件的地址的映射,可以找出已经确定了的变量的地址!

这里说的是基础变量在编译期间编译器在分配内存时可以确定它的内存地址!

这里确定方法我说一下:

当我们在int a;一个变量的时候,

编译器在编译的时候会根据文件映射关系来判断出它大概会被分配到哪个地址上!

因为栈区的地址映射,这也是内存布局的原因,int a, 那么编译器会往栈布局里push四个字节进去!并把首地址记录下来,往后的操作a 就会变成记录的地址!

但是malloc的也一样根据映射关系会被分配到哪儿,这些都是虚拟地址的映射,这里太深入的可以参考我的其它文章~!

当编译器确定了这些文件映射的地址之后会在对指针进行操作时,会直接临时将指针地址里的值给取出来,然后直接使用汇编的直接寻址!

所以这样优化的话会感觉到很快,但是有的编译器不给你这样做,硬让你先寻指针栈里的值,然后在寻址那就很慢了!

数组的方法和这个一样,只是多了一个首地址和偏移的递增计算!

最后在提一下普通变量的寻址!

根编译器的不同,普通变量会直接变成地址,也就是int a,编译器确定映射地址之后往后对a的操作比如:

a = 10;

则在汇编里就是

mov edx,a
mov edx,10
mov a,edx

cpu无法直接操作内存单元,所以需要先放入寄存器!

上面这种也就是直接寻址,和对指针的寻址一样!

但是有的编译器可能不会这样做,非要给你间接寻址:

mov exp,[a]
mov ax,exp    //将a地址里的值取到ax里
mov ax,10
mov exp,ax       //将递增10的值放回去

但对于寻址方式,还有很多,这个要看编译器是怎样根据映射关系来翻译你的代码,所以快不快取决于编译器,如果编译器是直接寻址的方式来翻译的那么速度上就快了一步!

END

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

17岁boy想当攻城狮

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值