原贴地址: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;
}