指针和引用的区别

原贴地址:http://bbs.pfan.cn/post-173884.html 

Hello stylev,

如果你很想看,某段程序的汇编代码,那么,你第一步要做的是安装GCC Compiler. 我以下要涉及的
命令完全针对GCC Compiler. 

在这个网址下面可以下在GCC Compiler 
http://gcc.gnu.org/mirrors.html  

如果你不太清楚如何下载和安装(比如我就觉得很不直观), 那么你可以下载 DEV(IDE), 
DEV 本身就是使用 GCC Compiler. 

DEV 的下载地址为 http://www.bloodshed.net/dev/devcpp.html 
下载 9.0 MB 的那一个.

在下载以及安装完毕以后, 要在Windows 中设置一下路径, 找到Dev 的存放目录,一般为
C:/Dev-Cpp/bin, 你需要确认一下,是否在该目录下具有 gcc 以及 g++ 这两个可执行文件, 如果有
那么这个路径名便是我们要的. 将这个路径名 Copy 下来, 也就是将 "C:/Dev-Cpp/bin" Copy 下来,
现在 打开控制面板 -> 打开系统 -> 在高级中打开环境变量 -> 在用户变量以及系统变量的 Path 中
将 Copy 的内容添置进去, 也就是在末尾先加上 分号 ";" 然后在其后面添加 Copy 的内容, 
然后按确定键.

在路径设置完毕之后,你需要重新启动一下你的机器。 

现在你可以用 textEditor 来编程了. 比如你写了一个 Helloworld 的程序,
并将其命名为 Helloworld.cpp

那么,生成 exe 文件的命令为: g++ -o exefilename Helloworld.cpp  
其中的第一项g++ 表示调用g++ 程序, 
-o option for object code
第三项 exefilename 是你想要的可执行文件名,如果你输入命令 g++ Helloworld.cpp 会发生什么呢?
你将得到一个被命名为 a.exe 的可执行文件。
最后一项自然就是那个 cpp 文件了。 
如果你的程序为 c 程序,那么你将取代 g++ 而使用 gcc, 
具体来讲,就是 gcc -o exefilename Helloworld.c

通过以下命令将编译程序以及生成 Object 文件 :
g++ -O2 -c filename.cpp

通过以下命令你可以得到 汇编代码 文件:
g++ -O2 -S filename.cpp

试一下以下的命令,你看看会得到什么,objdump -d code.o

关于指令就说这些,在网上你可以找到gcc 指令集,如果你很想玩汇编,建议你安装linux. 
我的机器就安装了WinXp 和 Suse 9,0. 一个好的程序员应该是一个对于自由有着虔诚信仰的人.
所以,你会发现大多程序员都会使用 linux, 但我不否定 Microsoft 对人类文明做出的巨大贡献,
我甚至认为和期待, Microsoft 能做的更好. 最好的以及最人性化的IDE 就是Microsoft 的 VC

现在,我们来看你的问题. 首先从C/C++ 语言的角度出发. 一个指针变量表示盖变量用于存储地址值, 
那么首先该变量本身需要一个4个Byte 的存储空间,这个存储空间用于存放一个地址值。
比如: 
int a = 3; //静态区域内具有4个Byte的空间,并被赋初值 3
int *p = &a; //静态区域内具有4个Byte的空间,并被赋初值 a 的地址

int *pNew = new int; //静态区域内具有4个Byte的空间,并被赋初值
  //其初值为某个动态空间的一个地址,
  //也就是说在动态空间内开辟了一个4个Byte的空间
  //并将其首地址放入静态空间下存储空间, 其被变量 pNew 占用

希望到这里已经将指针的概念讲清楚了.那么下面来讲引用的概念.

首先,什么是引用? 引用是通过一个变量名来代替某一个确定的变量或对象. 这句话意味着
某一个变量或对象已经存在了, 另一个变量名只是它的一个名称替代而已. 所以从实现层面来看,
我们不需要为引用变量申请额外空间. 比如

int a = 3; //静态空间内变量 a 占据了4个Byte 的空间, 并被赋初值 3
int & ra = a; //这里 ra 被称为 引用类型的变量, 是对 a 的一个引用.
  //请注意, ra 自身并不占据空间. 编译器能够理解 ra 是对 r 的一个引用
  //换句话说, ra 就是 r.

所以到这里, 你们可以看出来了, 引用和指针是有区别的. 你可以将引用理解为一个隐含的指针,
但是没有必要这样去理解. 所谓隐含的指针, 这只是编译器实现层面的事情, 我们没有必要去关心.
我们只需要知道, ra 是 a 的一个引用, 即 ra 就是 a 本身.


下面的问题是, 我们为什么需要引用, 或者说引用的作用是什么? 
之所以使用引用, 是因为我们想避免浅copy, 同时也想避免深copy. 也就是说, 当一个变量传递给某一个
函数时, 不希望发生copy 的动作, 而是该变量自身直接参与该函数的运算. 这样将提高运算的效率.

让我们来看一个具体的函数: 

void func(int a)
{
  // do something with a
}

当我们调用了函数, 比如 

int a = 3;
func(a); // 这个时候,发生了什么? 是不是 a 这个变量参与了运算呢? 
  // 回答是: NO a 仅仅被传递给了 函数 func, 所谓传递, 就是一个copy 的动作
  // 也就是真正参与预算的是函数的一个内部变量. 
  // 形象一点来讲, 你想象一下, 你是一个外部变量, 你跑向一个 blackbox, blackbox
  // 前站着一个门卫, 你将某些信息告诉那个门卫, 然后那个门卫走进 blackbox 去做你想做的事情.

我们再来看另外一个函数: 

void func(int & a)
{
  // do something with a
}

当你调用这个函数时发生什么了呢? 比如: 

int a = 3;
func(a); // 这个时候, a 自身参与了函数的运算, 也就是说, 没有copy 这个动作了.

当然我们可以通过指针来实现同样的功能. 比如上面的函数可以写成这样: 

void func(int * p)
{
  // do something with p
}

调用的时候, 就这样: 

int a = 3;
func( &a ); // 请注意, 这里有一个copy的动作, 我们copy 了 a 的地址. 假设 a 的地址为 1000
  // 那么现在, 当调用这个函数后, 存在一个函数内部指针变量 p, 这个 p 被赋予了初值 1000,
  // 随后, 在该函数中, 参与运算的是这个函数内的指针变量, 我们可以将这样的做法理解为
  // 对传递变量通过访问其地址的间接操作, 而相比较引用, 则是的的确确的直接操作.

上面所说的都是从语言这个角度来阐述的, 那么在实现层面是不是这样的呢? 

从实现层面来讲, 第二个函数和第三个函数根本没有区别, 也就是说, 不存在一个地址变量 p, 然后对p 来进行地址访问.
因为这样的做法,根本没有必要,编译器会将你的代码优化, 所以优化后的代码, 其实就是引用操作. 请注意, 如果没有这个
优化, 而是根据你的代码一是一, 二是二的去做, 那么在实现层面也是有区别的.

关于指针和引用的区别就讨论到这里. 

下面来谈汇编代码的问题. 其实这个话题很大. 

首先, 我们必须清楚这么一点, 一个汇编程序员的视角和 C/C++ 程序员的视角是完全两样的. 那么汇编程序员的视角是怎样的呢?
一个汇编程序员,他所看到的不仅仅是某个函数, 还要看到计算机内部各个变量的状态变化. 一个汇编程序员, 将计算机看成三个部分,
文件部分,也就是硬盘部分, Memory 部分, 以及 register 部分. 所以程序中不存在 C/C++ 代码中的 变量, 取而代之的是具体的存储
空间, 也就是说在汇编程序中将明确到某个数值位于哪个空间. 

比如下面一段C/C++ 代码: 

int exchange(int * xp, int y)
{
  int x = *xp;
  *xp = y;
  return x; 
}

其相应的汇编代码为: ( 注解见旁边)

// 首先, 汇编代码中没有变量名了, 取而代之的是各个寄存器名以及memory
// 这里的 ebp, eax, 等等, 都是 register name
// 如果你看到一个 register 用括号括起来, 表示对 memory 的操作

movl 8(%ebp), %edx memory -> register 将当前ebp 的地址加8, 并将其中的值移动到 edx 中, 相当于 get xp
movl 12(%ebp), %ecx memory -> register 将当前ebp 的地址加12, 并将其中的值移动到 ecx 中, 相当于 get y
movl (%edx), %eax memory -> register  
  由于edx 中存放的是地址值, 所以用括号括起来, 表示该地址下的值, 
  将该值移动到 eax, 相当于 *xp -> eax
movl %ecx, (%edx) register -> memory 将 ecx 中的值 移动到 memory(%edx) 
  相当于 y -> *xp, 即y 的值放入 xp 这个地址下 

popl %ebp 程序结束, return  
ret



movl 8(%ebp), %eax 将当前ebp 的地址加8, 并将其中的值移动到 eax 中, 相当于 get xp
movl 12(%ebp), %ebx 将当前ebp 的地址加12, 并将其中的值移动到 ebx 中, 相当于 get y
movl (%eax), %ecx 由于eax 中存放的是地址值, 所以用括号括起来, 表示该地址下的值, 将该值移动到 ecx, 相当于 *xp -> ecx
movl %edx, (%eax)  
movl %ecx, %eax  

如果要介绍汇编编程, 不是这么一个篇幅可以说的清楚的. 我建议你看一下 <<Computer Systems A Programmer's Perspective>> 我估计书店里不会有, 你可以用 emule 搜一下, 然后下载. 

以上为我的个解
 

 

从中我也学到了不少东西.与大家分享一下.

1,指针和引用的确从c/c++语言角度来讲,的确是有区别的.
指针分配4byte内存空间,而引用不分配,只是另取名字罢了...
(我想这个大多数C/C++爱好者们从书上都已经知道了,只是给sarrow看下 :)

2,的确,指针和引用在现实层次也是没区别的.毕竟汇编中没有引用没有指针,因为没有变量,都直接引用的地址..
更准切的说.
------/1/---------------
int sort(int *p)
{
  ...;
}

a = 3;
sort(&a);
--------/2/-----------------
---------------------
int sort(int &p)
{
  ...;
}

a = 3;
sort(a);
-------------------------
/*如果是c/c++,当然第一个是指针,第二个引用,具体的kai已经解释很清楚了.*/
/*而如果是compiler 帮你优化了。(既汇编),不管是1,还是2中,sort中的p直接用的a的地址.所以根本是看不出引用和指针区别的.也就是从指针和引用最后compiler优化后实现层面看是没区别的.但sarrow,这里我们在学c/c++,所以要用c/c++语言层面来看,是有区别的*/


好了.就到这里,如果还有不解请回贴.
 

 

 

哈哈,这种问题确实是专牛角尖。知道的不代表你全都知道,不知道的不代表你全都不会(废话)。

下面证明“引用是二级指针”。如有错误,请指出,我也是菜鸟啊。。。。 

#include <iostream>
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
  int a = 10;
  int &b = a;

  __asm
  {
  mov eax, b; // eax = a变量地址。
  inc dword ptr [eax]; // ++a; a = 11
  }

  cout << a << endl;

  int **refaddreB; // 用于储存“引用变量”b 地址,既然 b 有地址那么 b 肯定占用空间。(实际上是二级指针)
  __asm
  {
  lea ebx, b; // 获得 b 地址。
  mov refaddreB, ebx; // 储存到变量。

  mov eax, [ebx] // [ebx]寻址操作,把ebx指向的数据传给 eax,即 eax = a变量地址。
  inc dword ptr [eax];// ++a; a = 12
  }

  cout << a << endl;

  (*(*refaddreB))++; // a++; a = 13

  cout << a << endl; 

  system("pause");
  return 0;
}



 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值