工具软件   办公软件   操作系统   网络安全   设计在线   程序开发   教程宝典   软件下载   软件论坛
您的位置:软件 > 开发者网络 > 开发工具 > 开发专栏 > VC > 正文
ATL布幔下的秘密之底层技术和汇编
[文章信息]
作者:李马编译
时间:2005-01-04
出处:VCKBASE
责任编辑:方舟
[文章导读]
到现在为止,我们还没有讨论过任何有关汇编语言的东西。但是如果我们真的要了解ATL底层内幕的话
advertisement
热点推荐
· 真没想到VB也可以这样用之指针技术
· 禁止QQ登录的方法
· 给你的XML文件做个数字签名
· ImageReady制作“焰火”小动画
· Java加密和数字签名编程快速入门
[正文]

1 2  下一页

  介绍

  到现在为止,我们还没有讨论过任何有关汇编语言的东西。但是如果我们真的要了解ATL底层内幕的话,就不能回避这一话题,因为ATL使用了一些底层的技术以及一些内联汇编语言来使它更小巧快速。在这里,我假设读者已经拥有了汇编语言的基础知识,所以我只会集中于我的主题,而不会再另外写一份汇编语言的教程。如果你尚未足够了解汇编语言,那么我建议你看一看Matt Pietrek于1998年2月发表在Microsoft System Journal的文章《Under The Hood》,这篇文章会给予你关于汇编语言足够的信息的。

   现在就要开始我们的旅行了,那么先以这个简单的程序作为热身吧:

  程序55.

void fun(int, int) {
}
int main() {
  fun(5, 10);
  return 0;
}

  在在命令行模式下,使用命令行编译器cl.exe来编译它。在编译的时候,使用-FAs开关,例如,如果程序的名字是prog55的话:  
Cl -FAs prog55.cpp
  这就会生成一个带有相同文件名,扩展名为.asm的文件,这个文件中包含有以下程序的汇编语言代码。现在看看生成的输出文件,让我们首先来讨论函数的调用吧。调用函数的汇编代码是类似这个样子:

push  10            ; 0000000aH
push  5
call  ?fun@@YAXHH@Z ; fun

  首先,函数的参数以自右而左的顺序入栈,然后再调用函数。但是,函数的名称和我们给定的有所不同,这是由于C++编译器会对函数的名称作一些修饰已完成函数的重载。让我们稍微修改一下程序,重载这个函数,再来看看代码的行为吧。

  程序56.

void fun(int, int) {
}
void fun(int, int, int) {
}
int main() {
  fun(5, 10);
  fun(5, 10, 15);
  return 0;

  现在调用这两个函数的汇编代码是类似这个样子:

push  10             ; 0000000aH
push  5
call  ?fun@@YAXHH@Z  ; fun
push  15             ; 0000000fH
push  10             ; 0000000aH
push  5
call  ?fun@@YAXHHH@Z ; fun

  请看函数的名字,我们编写了两个名称相同的函数,但是编译器将函数名做了修饰完成了函数重载的工作。

  如果你不希望修饰函数的名称,那么你可以对函数使用extern "C"。让我们来对程序作少许修改。

  程序57.

extern "C" void fun(int, int) {
}
int main() {
  fun(5, 10);
  return 0;
}

  调用函数的汇编代码为

push  10   ; 0000000aH
push  5
call  _fun

  这就意味着现在你就不能对这个带有C链接方式的函数进行重载了。请看以下的程序

  程序58.

extern "C" void fun(int, int) {
}
extern "C" void fun(int, int, int) {
}
int main() {
  fun(5, 10);
  return 0;
}

  这个程序会给出一个编译错误,因为函数的重载在C语言中是不支持的,并且你给两个函数起同样的名称的同时还告诉编译器不要修饰它的名字,也就是使用C的链接方式,而不是C++的链接方式。

  现在来看看编译器为我们那个什么也不做的函数生成了什么,下面是编译器为我们的函数生成的代码。
push  ebp
mov   ebp, esp
pop   ebp
ret   0

  在我们进行详细地讲解之前,请看以下函数的最后一条语句,也就是ret 0。为什么是0?或者可以是别的非0数吗?正如我们所见,我们向函数传递的所有参数事实上都被压入了堆栈。在你或者编译器向堆栈中压入数据的时候,会对寄存器有什么影响吗?请看以下这个简单的程序来观察这一行为吧。我使用了printf而不是cout,这是为了避免cout的开销。

  程序59.
#include <cstdio>
int g_iTemp;
int main() {
  fun(5, 10); // 译注:这里的fun,应该是上文中的void fun(int, int)
  _asm mov g_iTemp, esp
  printf("Before push %d\n", g_iTemp);
  _asm push eax
  _asm mov g_iTemp, esp
  printf("After push %d\n", g_iTemp);
  _asm pop eax
  return 0;
}

  程序的输出为:
Before push 1244980
After push 1244976
  这个程序显示了压栈前后ESP寄存器中的值。下图清楚地说明了在你向堆栈中压入数据后,ESP的值会减少。


  现在就有一个问题了,当我们向函数中传递参数的时候,是谁来负责恢复堆栈指针的呢——函数本身还是函数的调用者?事实上,这两种情况都有可能,并且这就是标准调用约定和C调用约定的不同。请看调用函数的下一条语句:
push  10     ; 0000000aH
push  5
call  _fun
add   esp, 8
  在这里有两个参数传递给了函数,所以堆栈指针在两个参数入栈后会减去8个字节。现在在这个程序中,设置堆栈指针就是函数调用者的职责了。这就称作C调用约定。在这种调用约定中,你可以传递可变数目的参数,因为调用者知道有多少参数传递给了函数,所以它可以来设置堆栈指针。

  然而,如果你选择了标准调用约定,那么清楚堆栈就是被调用者的工作了。所以在这种情况下,可变数目的参数是不能传递给函数的,因为那样的话函数就没有办法知道到底有多少参数传给了函数,也就无法正常设置堆栈指针了。

  请看下面的程序来观察标准调用约定的行为。

  程序60.

extern "C" void _stdcall fun(int, int) {
}
int main() {
  fun(5, 10);
  return 0;
}

  现在来看看函数的调用。

push  10     ; 0000000aH
push  5
call  _fun@8

  在这里,函数名称中的@表示这是一个标准调用约定,8则表示被压入堆栈的字节数。所以,参数的数目可以由这个数目除以4得知。

  以下是我们这个什么也不做的函数的代码:

push  ebp
mov   ebp, esp
pop   ebp
ret   8

  这个函数通过“ret 8”指令在返回之前设置了堆栈指针。

  现在来探究一下编译器为我们产生的代码。编译器插入这个代码来创建堆栈帧,这样它就可以通过标准方式来存取参数和局部变量了。堆栈帧是一个为函数保留的区域,用来存储关于参数、局部变量和返回地址的信息。堆栈帧通常是在新的函数调用的时候创建,并在函数返回的时候销毁。在8086体系中,EBP寄存器就被用于存储堆栈帧的地址,有时叫做栈指针。(译注:ESP和EBP在本文中都被作者笼统地称为“Stack Pointer”,事实上ESP应称作“堆栈指针[Stack Pointer]寄存器”,它指示堆栈的栈顶便宜地址;EBP应称作“基址指针[Base Pointer]寄存器”,它用来作为基地址并和偏移量组合使用来访问堆栈中的信息。)

  这样,编译器首先保存前一个堆栈帧的地址,然后使用ESP的值创建新的堆栈帧。函数返回之前,先前的堆栈帧就恢复了。


1 2  下一页

天极社区邀请您:写博客日记  上传相片   论坛聊天  订阅电子杂志  推荐网摘   免费图铃工具
笔名:   请您注意:

 遵守国家有关法律、法规,尊重网上道德,承担一切因您的行为而直接或间接引起的法律责任。

 天极网拥有管理笔名和留言的一切权利。
评论:
 
发表评论推荐给朋友我想参加相关培训打印我对此感兴趣订阅电子杂志
相关内容焦点新闻
  • 民营家电商排队造手机 设备商全面杀入
  • 英特尔澄清杨旭任职传闻 官方没宣布此消息
  • 国资委河北密制联通拆分方案
  • 垃圾邮件害人害企害国 清除垃圾邮件不手软
  • 中兴携手阿尔卡特 全球逐鹿CDMA
  • 用友总裁王文京:誓将ERP变成“大众消费”
  • 香港消费者委员会:数码相机最贵未必最好
  • 外电称中兴正评估西门子手机业务 或能并购
  • Advertisement

    天极无线


    奇妙科幻|美好风光|清风车影|漫画卡通|星座生肖|明星写真|动物世界
    老鼠爱大米
    挥着翅膀的女孩
    女人味
    栀子花开
    白月光
    刚刚好
    江南
    快乐崇拜
    亲爱的你怎么不在我身边
    小薇
    2002年的第一场雪
    有多少爱可以重来
    我的地盘
    七里香
    情人
     
    老鼠爱大米 老板电话
    冲动的惩罚 七里香
    我不是黄蓉 女生撒娇
    盛夏的果实 坚持到底
    孤单北半球 眉飞色舞
    挪威的森林 可爱女人
    最浪漫的事 老板电话

    CSEEK搜索