前言
再言
什么是指针
指针传递
多级指针
指针函数
一个不一样的收尾
前言
我原想好好总结之后写一篇详尽的关于【C指针】的——,文章这个词感觉沉重了,不妨说文字吧。那么“一篇”这个量词显然不搭,得改口说“一些文字”。然而手上相关书籍只有大一时的教材《C语言程序设计》(第三版),以及才不久开始看的《零点起飞学LinuxC》(很low的名字,大概是为了销量起的噱头吧,幸而内容还不错。可是天真了,我自然找的电子版),这显然无论从广度还是深度,都不足以对【指针】详尽。我转而想到业界“圣经”——《C和指针》,但还没来得及翻阅。正确说来是才从网上找到高清PDF下载了,朋友来消息,说我公众号上的东西她都有在看,希望我坚持,能一周写个两三次。
其实因为精力有限,许多时候做不到定时推送,两篇之间时隔长久也是有的。况且自己认为编程“有趣”才将半年,虽然科班出身,很多东西却得从头来过,需要长久持续性充电。而我对这个领域心存无限探索:C/C++,JavaScript,HTML+CSS,Python,Node.js,shell……不仅仅局限语言本身(因为语法规则大同小异),而是背后衍生的所有常用方法。
一朋友问过我,怎样才算精通一门语言。我以为精通,应该是对语言的特性熟练掌握,并借此实现网络编程,系统编程,以及相关的数据结构(前端语言自动忽略)。这跟“全部知道”是不能划等号的。因为每一门语言背后,有着数不清的库、框架,涉及不同领域,沾染方方面面。我若专注爬虫,对Python领域的自动化测试不熟悉再正常不过。Python也是黑客实现攻击的常用语言之一,可如何攻击,此时此刻的我一无所知。
……感觉就要越说越远了。总结一句话:想学的东西N多,需要时间沉淀。为了感谢朋友的支持,想赶紧写一篇出来作为答谢。来不及读《C和指针》了,只好说些我对指针的粗浅见解。
再言
每一门语言都有其特性,说到C,就一定绕不过指针。
指针“随意”“奔放”,穿梭在内存地址之间,用得好就恣意潇洒;相伴的危害也大,使许多程序员“成也指针,败也指针”。要想熟练掌握指针,其难度系数不可谓之小。所以高校老师不爱讲,连给我们实训的老师都说尽量少用指针。What The Fuck?那还是C吗?
不可否认我在很长一段时间里对指针是惧怕的。摸不着头脑的“段错误”让我一度沦陷“一朝敲代码,十年改BUG”的窘境。可过去终究得过去。我有一个大胆的想法:要用最简单的方式讲述如何使用指针,让每个学C的人乐于使用指针。只是此时此刻的自己还能力有限,只好说“任重而道远”。
什么是指针
什么是指针?答:保存地址值的变量。
所以首先可以得到的信息是,指针是一个变量。用来干嘛呢?用来保存地址。
一定要注意,如果这样定义:,指针是p,而不是*p。
一般来说,在32位机上,用四个字节来表示地址,所以无论是int *还是char *的指针,都只占四个字节。对应的,64位机占8个字节。
32位
64位
所以指针只用于存放变量的地址,而不是变量本身。这里可以利用VS的调试器来验证
可见p地址是001AFB88;str地址是001AFB7C。我们可以在内存中看一下,这两个地址里面都存放的什么
根据入栈“先进后出”的原理,我们要对这串数据反向读,所以地址中存放的是001afb94
p作为指针,存放的自然是地址,所以我们在内存中搜索这个地址
这样就可以得到结果了:p地址上存放的是num的地址,而num地址上存放的是512的16进制表现形式
对于指针变量str也是同理
需要区别的是,对字符串、数组来说,指针存放的是它们的首地址,所以“ouguanxinqing”存放在后面地址中
指针传递
首先我们说说什么是值传递。
值传递:指在调用函数时,将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
在C语言中,默认传递以值传递为主。如下代码
调用了swap函数,但main函数中的x、y值并没有发生交换。这是因为在swap函数中,操纵的是x、y的复制本,并非main函数中的x、y。
而如果我们通过指针传递的方式,就可以直接对main中的x、y做修改了。
其实指针传递是很好理解的。因它传给函数的是地址,所以函数的参数要用指针来接收,这就是为什么指针传递时,swap函数的参数定义是了
多级指针
在实际开发中,多级指针并不常用,因为无限的增加指针级别,只会让代码更加混乱。指针的存在是为了方便与人,而非制造混乱。
如何理解***p3呢?
p3这个变量,存放的是p2的地址,所以*p3的意思是从p2的地址上取出存放的数据;
而p2这个变量里边存放的是p1的地址,那么*p3就等于p1的地址;
既然*p3等于p1的地址,那么**p3的意思就是从p1的地址上取出存放的数据,而p1也是个指针,存放的是变量num的地址 ,也就是说:**p3等于num的地址;
既然**p3是num的地址,那么***p的意思就是从num的地址上取出存放的数据,所以***p3就等于512了;
可能有点绕,我们打印一下,验证看是不是如此
而在参数传递中,如果需要传递指针的地址,那么函数必须用指针的指针来接收;依次类推,如果需要传递二级指针的地址,函数便得用三级指针来接收……
指针函数
说到指针函数,我突然想起“指针数组”和“数组指针”,一不小心就会绕进去。
C中存在指针函数以及函数指针两个概念:
指针函数,其根本就是一个函数,只不过这个函数的返回值是指针;
函数指针,其根本就是一个指针,它能够指向一个函数。
首先我们看一个简单的,可以有返回值的函数
事实上,每个变量都有其对应的作用域。num作为局部变量,在调用result()函数的时候被创建,等到result()函数运行完毕之后,内存被释放,num被系统回收。其回收的意思就是:num的地址上存放的数据不再是512。是什么呢?这个就不确定了。
在main函数中,之所以digit能接收到512,实际是系统复制了一份num变量的值,而后赋给digit。有何依据呢?很简单。我们定义一个指针函数,用来返回num的地址,在main()函数中接受一下,看能不能取出这个地址中的数据。
报段错误的原因是:访问了不该或不存的内存(当然,这并非造成段错误的唯一原因)。因为num在result()运行结束之后就被系统回收掉了。我们可以用关键字,让num在函数结束之后不被系统回收,这样一来,我们就可以返回num的地址了。
对于字符串
能够看到,main函数中的指针p接受到的是字符串的真实地址,而不是局部变量str的地址,所以p可以正常访问,并且打印出“hello world”
接下来我们再看一个代码
这段代码能够正常运行,并且输出如下:
其实应该是系统来不及对x的内存进行释放,所以能够拿到正确值。我们加个延时,再行输出试试
能看到两次结果不再一样,所以在指针函数中,一定要注意,返回的不应该是局部变量的地址
一个不一样的收尾
我一直说指针强大,如果强大法呢?不妨用一个小小的实例,来展示它访问地址的能力吧!
使用工具:
别怀疑,就是当年大火的pc端游戏
用于搜索地址
编写代码,生成dll动态库文件
dll注入工具
开始动手:
第一步,开启植物大战僵尸,利用【阳光】值动态改变进行搜索,直到获取到存放【阳光】的地址
第二步:编写代码如下
第三步:生成dll文件
第四步:向程序注入dll文件
最后实现效果,阳光的值从0-1000不断循环
嗯,这就是最LOW版的,所谓的“挂”了。
领取专属 10元无门槛券
私享最新 技术干货