首页 | 互联网 | IT动态 | IT培训 | Cisco | Windows | Linux | Java | .Net | Oracle | 软件测试 | C/C++ | 嵌入式开发 | 存储世界 | 服务器
网络设备 | IDC | 安全 | 求职招聘 | 数字网校 | 网页设计 | 平面设计 | 技术专题 | 电子书下载 | 教学视频 | 源码下载 | 搜索 | 博客 | 论坛
ASP | ASP.NET | JSP | PHP | AJAX | XML | Java script | HTML/CSS | 服务器类
各大城市软件开发培训、软件人才免费咨询热线:400-700-5807
 您现在的位置: 中国IT实验室 >> WEB开发 >> WEB开发 >> 正文
ATL布幔下的秘密之底层技术和汇编
ChinaItLab  2005-1-6  保存本文    收藏本站


  介绍
  到现在为止,我们还没有讨论过任何有关汇编语言的东西。但是如果我们真的要了解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的值创建新的堆栈帧。函数返回之前,先前的堆栈帧就恢复了。
  
  现在来看看堆栈帧中都有什么。在EBP的高地址一边存放所有参数,EBP的低地址一边则存放所有的局部变量。
  
  函数的返回地址保存在EBP中,前一个堆栈帧的地址保存在EBP + 4。现在看看下面的例子,它拥有两个参数和三个局部变量。
  
  程序61.
  
  extern "C" void fun(int a, int b) {
  int x = a;
  int y = b;
  int z = x + y;
  return;
  }
  int main() {
  fun(5, 10);
  return 0;
  }
  
  现在来看看编译器产生的函数代码。
  
  push ebp
  mov  ebp, esp
  sub  esp, 12         ; 0000000cH
  ; int x = a;
  mov  eax, DWORD PTR _a$[ebp]
  mov  DWORD PTR _x$[ebp], eax
  ; int y = b;
  mov  ecx, DWORD PTR _b$[ebp]
  mov  DWORD PTR _y$[ebp], ecx
  ; int z = x + y;
  mov  edx, DWORD PTR _x$[ebp]
  add  edx, DWORD PTR _y$[ebp]
  mov  DWORD PTR _z$[ebp], edx
  mov  esp, ebp
  pop  ebp
  ret  0
  
  现在来看看_x、_y这些东西都是什么。也就是定义在函数定义上方的这些东西:
  
  _a$ = 8
  _b$ = 12
  _x$ = -4
  _y$ = -8
  _z$ = -12
  
  这就意味着你可以像这样阅读代码:
  
  ; int x = a;
  mov eax, DWORD PTR [ebp + 8]
  mov DWORD PTR [ebp - 4], eax
  ; int y = b;
  mov ecx, DWORD PTR [ebp + 12]
  mov DWORD PTR [ebp - 8], ecx
  ; int z = x + y;
  mov edx, DWORD PTR [ebp - 4]
  add edx, DWORD PTR [ebp - 8]
  mov DWORD PTR [ebp - 12], edx
  
  这也就意味着参数a和b的地址分别为EBP + 8和EBP + 12。并且,x、y和z的值分别存储在内存中EBP - 4、EBP - 8、EBP - 12的位置上。
  
  在用这一知识武装起来之后,现在我们来玩一个函数参数的游戏,看以下这个简单的程序:
  
  程序62.
  
  #include <cstdio>
  extern "C" int fun(int a, int b) {
  return a + b;
  }
  int main() {
  printf("%d\n", fun(4, 5));
  return 0;
  }
  
中国IT教育热线咨询
相关文章
Flash+PHP+Mysql简单留言本制作实例教程
基础知识:Java Web三层架构的配置详解
使用AJAX技术构建更优秀的Web应用程序
ASP应用程序设计的Web状态管理分析
Ajax驱动的Web站点
最新文章
·Ajax光环背后的隐患
·Windows操作系统下JSP程序开发环
·如何在Java程序中实现FTP的上传下
·Java应用:编写高级JavaScript应
·初学者学习java第一步——JDK环境
 文章评论

 精彩友情推荐
·Asp源码 PHP源码
·CGI源码 JSP源码
·建站书籍教程
·服务器软件 .net源码
·建站工具软件
·IDC资讯大全
·机房品质万里行
·IDC托管必备知识
·全国IDC报价
·网站推广优化
ASP.NET ASP PHP JSP
·ASP.NET开发中的八个最佳实践09-21
·ASP.NET开发中的验证码技术09-18
·控件开发asp.net处理标签间内容09-18
·程序员成为成为编程高手的二十二条军规09-18
·VS 2005和ASP.NET 2.0中处理CSS样式表09-17
·如何有效监控.NET应用程序09-16
·浅析ASP.NET 2.0 Client Callback09-16
·探讨ASP.NETMVC框架内置AJAX支持编程技术09-15
·ASP.NET2.0的URL映射的实现方法09-13
·Java\.net\PHP比较 程序员眼中的.Net世界09-12
·asp.net中实现观察者设计模式09-12
·谈JSP与XML的交互09-01
·ASP连接11种数据库语法总结09-01
·如何利用ASP实现邮箱访问09-01
·ASP随机数的应用技术09-01
·定时使用DWRUtil.addRow生成表格08-27
·ASP申请单动态添加实现方法及代码08-27
·关于ASP中脚本执行顺序的讲解08-25
·ASP程序实现网页伪静态页源代码08-25
·ASP如何调用webservice08-22
·ASP实例:读取xml文件的程序08-14
·Asp组件检测函数08-11
·Linux系统下让PHP提高性能的工具APC05-06
·一个完整、安全的PHP用户登录系统11-14
·Apache+PHP+MySQL建立数据库驱动的动态网站08-24
·用SSH与PHP相连接 确保数据传输的安全性08-23
·PHP5手动最简安装方法08-03
·PHP程序加速探索之服务器负载测试07-11
·完全讲解PHP+MySQL的分页显示示例分析05-30
·用Suhosin加强PHP脚本语言安全性05-26
·初学入门 PHP 和 MySQL05-17
·传奇的诞生 PHP三位创始人简介05-10
·大型系统上PHP令人不爽的九大原因05-10
·JSTL中fn表达式的使用说明09-18
·JSP Web框架研究:Struts09-16
·JSF在GlassFish管理控制台中的应用09-12
·介绍JSP程序动态网站环境搭建的详细步骤09-12
·Jsp页面中文参数传递get和post方法分析09-12
·Java\.net\PHP比较 程序员眼中的.Net世界09-11
·JavaScript最常用的55个经典技巧09-08
·Windows操作系统下JSP程序开发环境配置09-03
·JSF点滴积累--权限验证09-03
·小小分页帮助程序08-29
·经验分享全过程JSP程序员完全蜕变手册08-27
  培训中心
人才交流中心 技术交流中心
  ITLab技术交流平台: