添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

在初始化阶段,指针和引用的行为都是一样的:先将x的地址加载到寄存器eax中,然后把eax的值拷贝到另一个内存地址中。

从反汇编中可以看到,x的地址就是ebp-0ch,对于指针来说,用x的地址来初始化指针ptr实际上就是把x的地址ebp-0c放到了ptr的地址单元中,而ptr的地址则是ebp-18h;

对于引用来说,这里就有点问题了:常说的引用都是“变量的别名”,似乎ref和x就应该是同一个地址,而实际上这里做了和指针初始化相同的操作——把x的地址放到了另一个内存单元(地址为ebp-24h)中。其实在这就可以有一种猜测: 虽然ref是x的引用,但是ref也有自己的地址的,而在初始化阶段,它的地址单元中存放的是它所引用的变量x的地址。 如下所示:

为了便于叙述,下文中把ref叫做“引用变量”,来表明它也是一个有地址的“变量”(这种说法并不准确,只是暂时找不到其它说法)。

由于自加自减也是一个赋值的过程,为了便于叙述,这里就用自加来进行分析。写出以下代码并查看反汇编代码:

int main()
	......
        ptr++;   //指针自加
        ref++;   //引用自加
        return 0;

       从反汇编代码可以看到,指针的自加和引用的自加是不同的,引用的步骤更多。

       在指针方面,一共有三步:①把指针变量ptr内存单元中的数据拷贝到eax;②eax加4(这里的4对应32位编译器下int型的size);③将eax的值写回到指针变量ptr的内存单元中。这三个步骤其实就做了一件事:把指针变量ptr内存单元中的数据加4。而在此之前指针变量ptr的内存单元中存放的是变量x的地址,因此,这就相当于断开了ptr和x之间的联系;

       在引用方面,一共有五步:①把引用变量ref内存单元中的数据拷贝到eax;②根据eax的值找到相应的内存单元并把该内存单元中的数据拷贝到ecx中;③ecx加1;④再次把引用变量ref内存单元中的数据拷贝到edx中;⑤把ecx的值写到edx的值对应的内存单元中。要分析这五步做了什么,一定要知道在此之前,引用变量ref的内存单元中存的是变量x的地址。这五步做的事就是:根据ref存放的x的地址来找到x的内存单元,然后把x的内存单元中的值加1。从这个过程可以知道,ref++;表面上是对ref进行自加,而实际上,ref只是一个媒介,通过ref找到x,然后对x进行自加。

        通过对二者赋值,可以发现,引用变量ref的确有自己的地址,它在内存中是占空间的。并且指针变量ptr完全具有“主导权”,对指针变量ptr进行赋值,改变的就是它本身;而引用变量ref则完全没有“主导权”,对引用变量ref进行赋值,改变的并不是它本身,而是通过它所找到的x。如下所示:

        对ptr和ref分别进行取地址,这里的变量t和t1可以不用管它,只需要观察取地址的过程。查看相应反汇编代码:

int main()
	......
	//取地址
	int ** t1 = &ptr;
	int * t = &ref;
    return 0;

     由于不需要管t和t1,因此只需要关注ptr和ref取地址各自反汇编代码第一行即可。

     在指针方面,取出ptr的地址&ptr是通过lea eax,[ebp-18h]来实现的。这句汇编代码的意思是,把ebp-18h这个地址,加载到eax中。而在ebp-18h就是指针变量ptr的内存地址,因此,指针变量ptr取地址很直接,就是取出ptr的内存地址;

      再看引用方面,取出ref的地址&ref是通过mov eax,dword ptr [ebp-24h]实现的。这句汇编代码的意思是,取出ebp-24h这个地址的内存单元中的值,拷贝到eax中。根据前面已经知道,ebp-24h是ref的地址,在这个地址中存放的是ref引用的x的地址,因此&ref实际上是x的地址

      由此可以发现,对指针取地址,取出的就是指针变量的地址,而对引用取地址,取出的实际上是引用变量所在内存单元中的值,也就是它引用的变量的地址。

1.引用和指针都是占用内存的。引用变量和指针变量各自的内存单元中,存放的都是它们引用或指向的变量的地址;

2.引用实际上是把引用变量本身给隐藏了,表面上对引用变量进行赋值,实际上改变的是它引用的变量的值;表面上是对引用变量取地址,实际上取出来的是它引用的变量的地址;

3.由于引用变量的操作的实际对象都是引用变量所在内存单元中的值,因此引用变量必须和另一个变量关联起来。也就是说,引用必须初始化。原因很简单,如果你不对一个引用进行初始化,那么引用变量的内存单元中存放的都是垃圾数据,后面对引用变量进行操作时就会通过这些垃圾数据找到对应的内存地址,这是非常危险的;

     那么直接初始化的时候对引用赋一个常量值呢?这实际上也是不对的,因为引用的初始化实际是用用来初始化的变量的地址来初始化引用变量它自己的内存单元,而一个常量值哪有地址呢?虽然在C++11中已经可以用一个常量来初始化右值引用,但是通过反汇编可以看到,右值引用的根本,还是先用一个地址去保存这个常量值,然后再用这个地址去初始化引用变量。

     而指针变量则完全不一样了,指针变量的操作的实际对象都是它自己,因此指针变量不像引用那样必须初始化,但是为了安全,也应该初始化。

4.依然是由于引用变量的一切操作实际对象都是它引用的变量,因此从用户角度来说是没有入口让用户去修改引用变量本身的。换句话说,用户层面没有任何办法去改变引用变量内存单元中存放的地址,这也就是为什么引用一旦初始化后就无法再修改

5.由于引用变量自身对于用户是不可见的,对引用变量取地址得到的也不是引用变量的地址,因此你无法让一个引用变量的内存中存放另一个引用变量的地址,换句话说,不存在引用的引用。而相反,由于指针变量取出来的地址就是它本身的地址,因此你完全可以把一个指针变量的地址存放在另一个指针变量的内存中,这也就是为什么可以存在多级指针,但是多级引用是不允许的。

6.关于“引用是变量的别名”的考虑。感觉这句话既对也不对,说它不对是因为引用变量和引用的变量二者实际上是独立的两块内存单元,只不过前者依托后者而存在,但是这并不能说前者就是后者的别名,这是矛盾的;说它对,是因为对引用变量进行操作时,改变的对象实际上都是引用的变量而不是引用变量,这就感觉引用变量只是一层伪装,真正的还是它引用的变量,从这个角度来说,引用确实可以说是变量的别名。

7.函数传指针与传引用的区别。函数传指针的原型类似于int add(int * a, int * b);这类函数的形参是指针,调用方式为int x = 1, y = 2; add(&x,&y);那么传入的实参则是变量的地址,因此在函数内部,是用地址型实参&x和&y来初始化形参a和b,相当于int *a = &x,int * b = &y的;因此形参a和b内存单元中存放的是x和y的地址。

     而对于函数传引用,函数原型则类似于int add(int & a,int & b),这类函数的形参是引用,调用方式为int x = 1, y = 2; add(x,y);传入的实参就是x和y变量自身,在函数内部,则是用x和y来初始化a和b,相当于int &a = x,int &b = y,因此,形参a和b内存单元中存放的也是x和y的地址,不过它毕竟是引用,如前面所说,当你试图对形参a或b的值进行改变的时候,改变的不是它内存中存放的&x和&y,其实是x或y;当你对形参a或b取地址时,取出来的并不是形参本身的地址,而是它内存中存放的&x和&y。

      而再说一个与本文无关的函数传值,如int add(int a,int b);这类函数的形参只是普通的int型变量,当调用add(x,y)时,在函数内部实际上就是用x和y来初始化a和b,相当于int a = x,int b = y,所以形参和实参实际上只是值相同而已,改变形参并不会影响实参。

目录初始化赋值取地址总结本文主要基于反汇编代码,从初始化、赋值以及取地址三个角度来理解指针和引用的区别。初始化 写出以下代码并查看反汇编代码:int main(){ int x = 5; int * ptr = &x; //指针 int & ref = x; //引用 return 0;}... 从Windows出现开始,汇编语言似乎在慢慢地销声匿迹,但本书可以让人放弃这个观点,其实在Win32环境下,汇编语言依然强大。 Why——为什么选择Win32汇编 选择Win32汇编的理由是什么呢? 在DOS时代,学习汇编就是学习系统底层编程的代名词,仅要成为一名入门级的汇编程序员,就需要学习从CPU结构、CPU工作方式、各种硬件的编程方法到DOS工作方式等范围很广的知识。随着Windows时代的到来,Windows像一堵巨大的墙,把我们和计算机的硬件隔离开。对于DOS的汇编程序员来说,就像在一夜之间,我们发现自己曾经学过的几乎所有的东西都被Windows封装到内核中去了,由于保护模式的存在,我们又无法像在DOS下那样闯入系统内核为所欲为。在Windows下用任何语言编程都必须遵循Windows的规范,汇编也不例外,也就是说,汇编不再是一种“有特权”的语言。面对汹涌而来的Visual C++,Visual Basic,PowerBuilder和Java等各个领域的猛将,从DOS时代“为所欲为”的“系统警察”岗位下岗,在其他领域又没有一技之长,汇编语言似乎失去了生存的意义,有很多人在DOS转向Windows的时候放弃了汇编语言。 但是经过短暂的失落,摆正了自己在系统中的位置,我们发现从“系统警察”转换到遵循Windows规范的“好市民”后,汇编语言又慢慢地在这个世界流行起来了。毕竟,不能为所欲为也可以有好的一面,我们可以不必再考虑一些老大难的问题,如程序运行时会面对什么样的显示卡,如何驱动不同的打印机,内存不够了如何用磁盘交换,等等。我们也可以在了解更少硬件知识的情况下就可以掌握Win32的汇编编程。而且,我们惊喜地发现,做了“好市民”以后,我们而拥有了和其他语言同样的权利——为了做图形和界面等方面的功能,汇编程序员在DOS时代连做梦都在羡慕C语言庞大的函数库,而现在,Windows为我们提供了比这还要多得多的函数,以至于其他大部分语言可以做出来的功能,汇编都可以做,而其他语言做不到的功能,汇编照样可以做!所以这就是理由之一:Win32汇编可以当做一种功能强大的开发语言使用,使用它完全可以开发出大型的软件来。 正因为Win32汇编看上去不再那样低级,于是有读者曾经提出:Win32汇编讲的都是用API来写程序,和高级语言差不多,以前在DOS下使用的中断什么的都不能用,所以没有什么新奇的了。还有读者认为本书只不过是MSDN的汇编版本而已。言下之意就是:学汇编就是为了了解高级语言底下一层的功能,但现在Win32汇编却使用和C++等语言相同的API接口,既然和高级语言处于同一个级别,我们为什么还要去和机器指令打交道呢,还不如去学Visual C++方便。 但是我们可以这样问一问自己: 问:在DOS汇编中我们为什么用中断功能? 答:为了使用DOS内核提供的功能。 问:在DOS中我们常常自己用操作I/O端口的方法读写硬盘或操作显卡吗? 答:不,我们用系统提供的int 13h和int 10h。 同样,在Win32汇编里使用API也是为了使用Windows内核提供的功能。只不过使用的方式不再是中断方式而已,这不是Win32汇编语言“高级化”了,而是高级语言因为使用Windows的API接口而“低级化”了,其代价就是无法移植到其他系统,用Visual C++写的程序是无法移植到其他操作系统平台上的,只有和平台无关的ANSI C++等才能算是真正意义上的高级语言。 其实,任何汇编语言都是和操作系统密切相关的,不管是DOS汇编、Win32汇编,还是Linux汇编,都是基于特定的操作系统的,如果一定要绕过操作系统,那么就不会有DOS汇编和Win32汇编区别了,但是这样的话我们不是在学汇编,而是在自己开发操作系统。高级语言在不同的操作系统上看起来都差不多,但作为一种低级语言,不同操作系统上的汇编就是不同的世界。所以,既然Windows和DOS是两个完全不同的操作系统,我们就必须抛弃DOS汇编中的大部分概念从头开始学习Win32汇编。这就是理由之二:Win32汇编是Windows环境下一种全新的编程语言。 Win32环境下的很多高级语言,如Visual C++和Visual Basic等,一如既往地对实现的细节进行了或深或浅的封装,就连最能表现Windows特征的部分,如消息循环和多线程的处理等内容也都被隐藏封装,使我们在使用它们进行可视化编程的同时,无法全面了解Win32程序运行的具体方式。在学习Win32汇编以后,这些隐藏在高级语言后面的细节就暴露出来了。 由于封装的关系,各种高级语言或多或少存在某种“缺陷”,比如VB不支持指针,结果很多需要使用指针的API用起来就很不方便,像多线程一类的特征在VB中就无法实现,PowerBuilder也是如此;C语言已经是最灵活的高级语言了,但还是无法在代码级别处理某些需求;而汇编语言见到的是一个最真实的操作系统,它可以用最灵活的方式使用各种系统功能,第13章中有关进程隐藏的内容就是最好的写照。所以理由之三就是:使用Win32汇编语言是了解操作系统运行细节的最佳方式。 最后的理由根本不是理由,而是必然的选择,当我们在Windows环境下进行加密解密、逆向工程,还有病毒、木马等有害代码的分析和防治工作时,Win32汇编是唯一的选择。在任何讨论这方面内容的书籍中,汇编代码的篇幅总是很大的。因此,要想深入了解这些内容的前提就是深入汇编编程。 How——如何学习Win32汇编 以往的汇编书籍往往把重点放在硬件结构和指令上,讲述了一大堆电路框图和指令列表,把大家搞得晕晕乎乎后,再举出一些重量级的例子,不是一些像数组、矩阵计算一类的复杂运算,就是开始图形模式画图,以至于大家看完以后就再也找不到北了!实际上,这些例子不是太难了,而是太枯燥了。有人说,学汇编就像考大学,千军万马过独木桥,太多的人中途放弃了,只有少数人坚持到最后。 笔者认为:学习汇编应该在轻松的环境下进行,在学习中使用的例子不一定太复杂,但一定要有吸引力。用汇编写复杂的运算程序固然会比C更有效率,但同样的事在C中用一个表达式就全部搞定了,从这里开始学汇编,给人的感觉就像从复杂的公式开始学算术,要知道,加法还没有学会呢!而对于高级语言封装起来的系统功能,用汇编解释起来就非常直接,非常自然,也更容易懂。以笔者自己学汇编的过程来说,那时候是1990年,刚好是中国第一次病毒大流行,大家的计算机上都是那个病毒的开山鼻祖——乒乓病毒,在流行DOS的时期,看着在屏幕上蹦的小球,心中就有一个问题:如何编出这样一个玩意来呢?要知道DOS是单任务的,而那个球在别的程序运行的时候照样蹦!这用当时流行的FORTRAN、C等课程中学到的任何知识都无法解释,因为这些课程中不可能有TSR、中断、引导区等内容。带着这样一个疑问学习汇编,在分析乒乓病毒的过程中啃一条条不懂的指令,病毒分析完了,汇编课也学完了,而且过来看那些复杂的计算程序都是那么顺理成章,不攻自破了。实际上,从一些实用的系统功能开始学习汇编远比学矩阵计算容易理解。 正如最经典的C程序就是那个“Hello,World!”一样,这个程序的有名并不是因为它用高深复杂的语句放倒了一大批人,而是它以最简单易懂的方式让人们走入C语言的大门。对于Win32汇编也是如此,从最简单的例子开始总是没错的,笔者建议读者跟随本书中从简到繁的例子,努力做到理解并灵活引用这些例子中的各种功能,正如“熟读唐诗三百首,不会写诗也会吟”,最后能够熟练地使用Win32汇编来解决各种编程需求就是最大的胜利。 另外,正如前面讲到的,汇编语言的学习必须和操作系统紧密结合。经过简单的调查,笔者发现很多高校使用的汇编教程还是停留在清华91版《IBM-PC汇编语言程序设计》之类的教材上,虽然这些教材中基础知识部分永远不会过时,但涉及操作系统的部分还是停留在DOS阶段。随着DOS操作系统的悄然引退,继续把精力花在上面是一种浪费,因为任何语言都必须有应用的平台,否则课程学完之后会尴尬地发现没有地方可以应用。笔者认为,在《IBM-PC汇编语言程序设计》之类传统教材中的基础部分学习完毕以后,重点就应该转向Win32汇编,以及保护模式方面的知识。 关于本书的内容 本书尝试从编写应用程序的角度,从“Hello,World”这个简单的例子开始到编写多线程、注册表和网络通信等复杂的程序,通过70多个从简单到复杂的例子,逐步深入Win32汇编编程的方方面面。笔者从事汇编编程已经有十几年的历史了,从8086时代的DOS汇编编程开始到当前的Win32汇编编程,从一个初学者到现在能利用Win32汇编来解决大部分编程需求,中间也经过了很长时间的摸索和大量的挫折,所以笔者很清楚初学者在哪些地方会遇到问题,但是涉及Win32汇编的书籍却实在太少了。正是因为如此,笔者决心把本书的目标定为:能让读者入门并在最后能熟练掌握Win32汇编编程,而不是那种深入系统奥秘一类的书籍。 从这个目标出发,本书的选材中尽量去掉已经有其他书籍详细讨论的部分,因为要一本书涉及全部方面是不现实的。内容全面就必然不精,内容深刻就必须围绕一个中心点,所以本书的内容并不详细讨论一般汇编教材的基础部分,如处理器结构和保护模式等,也不准备涉及Windows驱动程序、COM编程或者其他能够冠以“密技”头衔的内容。本书主要的内容将放在32位宏汇编对比DOS汇编所不同的部分,以及Win32应用程序的汇编实现上。不求全面,只求精也!(说句老实话,也不敢对自己不精通的地方妄加评论,以免破坏自己的良好形象。 指针引用形式上很好区别,但是他们似乎有相同的功能,都能够直接引用对象,对其进行直接的操作。但是什么时候使用指针?什么时候使用引用呢?这两者很容易混淆,在此我详细介绍一下指针引用,力争将最真实的一面展现给大家。如果我写得不够好,希望嘴下留情、手下留命,还请指点一二;如果感觉还不错,请大家鼓掌。 1、指针引用的定义 在深入介绍之前我们首先来看一下指针引用的定义、指针引用区别,然后分别针对指针引用展开讨论,深入细节为何有这些差异。 指针的权威定义: 引用的权威定义: 上面这些定义初看
逆向跨门槛学习详解 文章目录逆向跨门槛学习详解0x01 基础必懂知识0x02 通用寄存器寻址方式0x03 标志寄存器0x04 JCC0x05 结合C语言裸函数整数类型浮点数类型英文字符存储中文字符存储全局变量局部变量数组 0x01 基础必懂知识 进制一定是大家最先开始就了解的一部分耳熟能详的知识,比如十进制、二进制、八进制、十六进制,这里也不细讲,不过提一句各进制的转换,这里做了一张很简单的图,帮助记忆一下 其次需要记住二进制的逻辑运算,例如与(&)、或(|)、非(!)、异或(^)(大家都懂就一笔
引用必须被初始化,但是不分配存储空间。指针不声明时初始化,在初始化的时候需要分配存储空间。 引用初始化后不能被改变,指针可以改变所指的对象。 不存在指向空值的引用,但是存在指向空值的指针。 注意:引用作为函数参数时,会引发一定的问题,因为让引用作参数,目的就是想改变这个引用所指向地址的内容,而函数调用时传入的是实参,看不出函数的参数是正常变量,还是引用,因此可能引...
汇编中的数据类型在汇编中,无论是字符串类型,整数类型还是其他类型都只有2个关键单位,地址、长度【注意:LEA为取地址指令】引用指针汇编代码:引用并不会影响程序的执行效率,使用引用又比指针多了编译器的检测所以在编写C++程序时应该抛弃指针,拥抱引用。const为编译期检查下面先来看一个C++源码:#include <stdio.h> int main(){ char *p_s...
(1)指针是实体,引用是别名,没有空间。本质上的区别就是指针是一个新的变量,只是这个变量存放着另一个变量的地址,而引用是变量本身 (2)引用定义时必须初始化,指针不用。 (3)指针可以改变它指向的对象,而引用不可以。 (4)引用不能为空,不能有NULL引用引用必须与一块合法的存储单元关联。指针可以。 (5)Sizeof(引用)计算的是它引用的对象的大小,而sizeof(指针)计算的是指针本身的大小。 (6)给引用赋值修改的是该引用与对象所关联的值,而不是与引用关联的对象。 (7)如果返回的是动
引用指针的不同点 1.引用在初始化是引用一个实体后,就不能引用其他的实体,而指针可以在任何时候指向一个同类型的实体。 2.引用在定义时必须初始化,而指针不需要。 3.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数。 4.没有null引用,但是有null指针。 5.引用自加即引用的实体增加一,指针自加,即指针向后偏移一个类型的大小 6.引用指针更加安全。 7.访问实体方式不同,指针需要显式解引用,而引用是编译器自己处理。 8.指针有多级指针,但是引用没有多级引用。 答案:①一方面,每一种编程语言都使用指针。不止C/C++使用指针。 每一种编程语言都使用指针。C++将指针暴露给了用户(程序员),而Java和C#等语言则将指针隐藏起来了。 “Everything uses pointers. C++ just exposes them rather than hiding them,” It's easier t...