﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-清幽静谷-随笔分类-c/c++标准</title><link>http://www.cppblog.com/zjl-1026-2001/category/10405.html</link><description /><language>zh-cn</language><lastBuildDate>Tue, 19 May 2009 12:21:12 GMT</lastBuildDate><pubDate>Tue, 19 May 2009 12:21:12 GMT</pubDate><ttl>60</ttl><item><title>堆和栈的区别 (转贴)</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/19/83385.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Tue, 19 May 2009 08:53:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/19/83385.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/83385.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/19/83385.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/83385.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/83385.html</trackback:ping><description><![CDATA[<div class=postbody>
<h2>堆和栈的区别&nbsp;(转贴) </h2>
<p>非本人作也!因非常经典,所以收归旗下,与众人阅之!原作者不祥!</p>
<div class=postbody>堆和栈的区别<br>一、预备知识—程序的内存分配<br>一个由c/C++编译的程序占用的内存分为以下几个部分<br>1、栈区（stack）—&nbsp;由编译器自动分配释放&nbsp;，存放函数的参数值，局部变量的值等。其操作方式类似于数据结构中的栈。<br>2、堆区（heap）&nbsp;—&nbsp;一般由程序员分配释放，&nbsp;若程序员不释放，程序结束时可能由OS回收&nbsp;。注意它与数据结构中的堆是两回事，分配方式倒是类似于链表，呵呵。<br>3、全局区（静态区）（static）—，全局变量和静态变量的存储是放在一块的，初始化的全局变量和静态变量在一块区域，&nbsp;未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。&nbsp;-&nbsp;程序结束后有系统释放&nbsp;<br>4、文字常量区—常量字符串就是放在这里的。&nbsp;程序结束后由系统释放<br>5、程序代码区—存放函数体的二进制代码。<br>二、例子程序&nbsp;<br>这是一个前辈写的，非常详细&nbsp;<br>//main.cpp&nbsp;<br>int&nbsp;a&nbsp;=&nbsp;0;&nbsp;全局初始化区&nbsp;<br>char&nbsp;*p1;&nbsp;全局未初始化区&nbsp;<br>main()&nbsp;<br>{&nbsp;<br>int&nbsp;b;&nbsp;栈&nbsp;<br>char&nbsp;s[]&nbsp;=&nbsp;"abc";&nbsp;栈&nbsp;<br>char&nbsp;*p2;&nbsp;栈&nbsp;<br>char&nbsp;*p3&nbsp;=&nbsp;"123456";&nbsp;123456\0在常量区，p3在栈上。&nbsp;<br>static&nbsp;int&nbsp;c&nbsp;=0；&nbsp;全局（静态）初始化区&nbsp;<br>p1&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>p2&nbsp;=&nbsp;(char&nbsp;*)malloc(20);&nbsp;<br>分配得来得10和20字节的区域就在堆区。&nbsp;<br>strcpy(p1,&nbsp;"123456");&nbsp;123456\0放在常量区，编译器可能会将它与p3所指向的"123456"优化成一个地方。&nbsp;<br>}&nbsp;
<p>&#160;</p>
<p><br>二、堆和栈的理论知识&nbsp;<br>2.1申请方式&nbsp;<br>stack:&nbsp;<br>由系统自动分配。&nbsp;例如，声明在函数中一个局部变量&nbsp;int&nbsp;b;&nbsp;系统自动在栈中为b开辟空间&nbsp;<br>heap:&nbsp;<br>需要程序员自己申请，并指明大小，在c中malloc函数&nbsp;<br>如p1&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>在C++中用new运算符&nbsp;<br>如p2&nbsp;=&nbsp;(char&nbsp;*)malloc(10);&nbsp;<br>但是注意p1、p2本身是在栈中的。&nbsp;</p>
<p><br>2.2&nbsp;<br>申请后系统的响应&nbsp;<br>栈：只要栈的剩余空间大于所申请空间，系统将为程序提供内存，否则将报异常提示栈溢出。&nbsp;<br>堆：首先应该知道操作系统有一个记录空闲内存地址的链表，当系统收到程序的申请时，&nbsp;<br>会遍历该链表，寻找第一个空间大于所申请空间的堆结点，然后将该结点从空闲结点链表中删除，并将该结点的空间分配给程序，另外，对于大多数系统，会在这块内存空间中的首地址处记录本次分配的大小，这样，代码中的delete语句才能正确的释放本内存空间。另外，由于找到的堆结点的大小不一定正好等于申请的大小，系统会自动的将多余的那部分重新放入空闲链表中。&nbsp;</p>
<p>2.3申请大小的限制&nbsp;<br>栈：在Windows下,栈是向低地址扩展的数据结构，是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的，在WINDOWS下，栈的大小是2M（也有的说是1M，总之是一个编译时就确定的常数），如果申请的空间超过栈的剩余空间时，将提示overflow。因此，能从栈获得的空间较小。&nbsp;<br>堆：堆是向高地址扩展的数据结构，是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的，自然是不连续的，而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见，堆获得的空间比较灵活，也比较大。&nbsp;</p>
<p><br>2.4申请效率的比较：&nbsp;<br>栈由系统自动分配，速度较快。但程序员是无法控制的。&nbsp;<br>堆是由new分配的内存，一般速度比较慢，而且容易产生内存碎片,不过用起来最方便.&nbsp;<br>另外，在WINDOWS下，最好的方式是用VirtualAlloc分配内存，他不是在堆，也不是在栈是直接在进程的地址空间中保留一快内存，虽然用起来最不方便。但是速度快，也最灵活。&nbsp;</p>
<p>2.5堆和栈中的存储内容&nbsp;<br>栈：&nbsp;在函数调用时，第一个进栈的是主函数中后的下一条指令（函数调用语句的下一条可执行语句）的地址，然后是函数的各个参数，在大多数的C编译器中，参数是由右往左入栈的，然后是函数中的局部变量。注意静态变量是不入栈的。&nbsp;<br>当本次函数调用结束后，局部变量先出栈，然后是参数，最后栈顶指针指向最开始存的地址，也就是主函数中的下一条指令，程序由该点继续运行。&nbsp;<br>堆：一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。&nbsp;</p>
<p>2.6存取效率的比较&nbsp;</p>
<p>char&nbsp;s1[]&nbsp;=&nbsp;"aaaaaaaaaaaaaaa";&nbsp;<br>char&nbsp;*s2&nbsp;=&nbsp;"bbbbbbbbbbbbbbbbb";&nbsp;<br>aaaaaaaaaaa是在运行时刻赋值的；&nbsp;<br>而bbbbbbbbbbb是在编译时就确定的；&nbsp;<br>但是，在以后的存取中，在栈上的数组比指针所指向的字符串(例如堆)快。&nbsp;<br>比如：&nbsp;<br>#include&nbsp;<br>void&nbsp;main()&nbsp;<br>{&nbsp;<br>char&nbsp;a&nbsp;=&nbsp;1;&nbsp;<br>char&nbsp;c[]&nbsp;=&nbsp;"1234567890";&nbsp;<br>char&nbsp;*p&nbsp;="1234567890";&nbsp;<br>a&nbsp;=&nbsp;c[1];&nbsp;<br>a&nbsp;=&nbsp;p[1];&nbsp;<br>return;&nbsp;<br>}&nbsp;<br>对应的汇编代码&nbsp;<br>10:&nbsp;a&nbsp;=&nbsp;c[1];&nbsp;<br>00401067&nbsp;8A&nbsp;4D&nbsp;F1&nbsp;mov&nbsp;cl,byte&nbsp;ptr&nbsp;[ebp-0Fh]&nbsp;<br>0040106A&nbsp;88&nbsp;4D&nbsp;FC&nbsp;mov&nbsp;byte&nbsp;ptr&nbsp;[ebp-4],cl&nbsp;<br>11:&nbsp;a&nbsp;=&nbsp;p[1];&nbsp;<br>0040106D&nbsp;8B&nbsp;55&nbsp;EC&nbsp;mov&nbsp;edx,dword&nbsp;ptr&nbsp;[ebp-14h]&nbsp;<br>00401070&nbsp;8A&nbsp;42&nbsp;01&nbsp;mov&nbsp;al,byte&nbsp;ptr&nbsp;[edx+1]&nbsp;<br>00401073&nbsp;88&nbsp;45&nbsp;FC&nbsp;mov&nbsp;byte&nbsp;ptr&nbsp;[ebp-4],al&nbsp;<br>第一种在读取时直接就把字符串中的元素读到寄存器cl中，而第二种则要先把指针值读到edx中，在根据edx读取字符，显然慢了。&nbsp;</p>
<p><br>2.7小结：&nbsp;<br>堆和栈的区别可以用如下的比喻来看出：&nbsp;<br>使用栈就象我们去饭馆里吃饭，只管点菜（发出申请）、付钱、和吃（使用），吃饱了就走，不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作，他的好处是快捷，但是自由度小。&nbsp;<br>使用堆就象是自己动手做喜欢吃的菜肴，比较麻烦，但是比较符合自己的口味，而且自由度大。&nbsp;<br><br><br><br></p>
<p>windows进程中的内存结构</p>
<p><br>在阅读本文之前，如果你连堆栈是什么多不知道的话，请先阅读文章后面的基础知识。&nbsp;</p>
<p>接触过编程的人都知道，高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢？程序又是如何使用这些变量的呢？下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明，默认都使用VC编译的release版。&nbsp;</p>
<p>首先，来了解一下&nbsp;C&nbsp;语言的变量是如何在内存分部的。C&nbsp;语言有全局变量(Global)、本地变量(Local)，静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>int&nbsp;g1=0,&nbsp;g2=0,&nbsp;g3=0;&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>static&nbsp;int&nbsp;s1=0,&nbsp;s2=0,&nbsp;s3=0;&nbsp;<br>int&nbsp;v1=0,&nbsp;v2=0,&nbsp;v3=0;&nbsp;</p>
<p>//打印出各个变量的内存地址&nbsp;</p>
<p>printf("0x%08x\n",&amp;v1);&nbsp;//打印各本地变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;v2);&nbsp;<br>printf("0x%08x\n\n",&amp;v3);&nbsp;<br>printf("0x%08x\n",&amp;g1);&nbsp;//打印各全局变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;g2);&nbsp;<br>printf("0x%08x\n\n",&amp;g3);&nbsp;<br>printf("0x%08x\n",&amp;s1);&nbsp;//打印各静态变量的内存地址&nbsp;<br>printf("0x%08x\n",&amp;s2);&nbsp;<br>printf("0x%08x\n\n",&amp;s3);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后的执行结果是：&nbsp;</p>
<p>0x0012ff78&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff80&nbsp;</p>
<p>0x004068d0&nbsp;<br>0x004068d4&nbsp;<br>0x004068d8&nbsp;</p>
<p>0x004068dc&nbsp;<br>0x004068e0&nbsp;<br>0x004068e4&nbsp;</p>
<p>输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量，g1,g2,g3是全局变量，s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的，但是本地变量和全局变量分配的内存地址差了十万八千里，而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言，可以在逻辑上分成3个部份：代码区，静态数据区和动态数据区。动态数据区一般就是&#8220;堆栈&#8221;。&#8220;栈(stack)&#8221;和&#8220;堆(heap)&#8221;是两种不同的动态数据区，栈是一种线性结构，堆是一种链式结构。进程的每个线程都有私有的&#8220;栈&#8221;，所以每个线程虽然代码一样，但本地变量的数据都是互不干扰。一个堆栈可以通过&#8220;基地址&#8221;和&#8220;栈顶&#8221;地址来描述。全局变量和静态变量分配在静态数据区，本地变量分配在动态数据区，即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。&nbsp;</p>
<p><br>├———————┤低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;动态数据区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;代码区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;静态数据区&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤高端内存区域&nbsp;</p>
<p><br>堆栈是一个先进后出的数据结构，栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程，以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定，这些因素有参数的压入规则和堆栈的平衡。windows&nbsp;API的调用规则和ANSI&nbsp;C的函数调用规则是不一样的，前者由被调函数调整堆栈，后者由调用者调整堆栈。两者通过&#8220;__stdcall&#8221;和&#8220;__cdecl&#8221;前缀区分。先看下面这段代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>void&nbsp;__stdcall&nbsp;func(int&nbsp;param1,int&nbsp;param2,int&nbsp;param3)&nbsp;<br>{&nbsp;<br>int&nbsp;var1=param1;&nbsp;<br>int&nbsp;var2=param2;&nbsp;<br>int&nbsp;var3=param3;&nbsp;<br>printf("0x%08x\n",&#182;m1);&nbsp;//打印出各个变量的内存地址&nbsp;<br>printf("0x%08x\n",&#182;m2);&nbsp;<br>printf("0x%08x\n\n",&#182;m3);&nbsp;<br>printf("0x%08x\n",&amp;var1);&nbsp;<br>printf("0x%08x\n",&amp;var2);&nbsp;<br>printf("0x%08x\n\n",&amp;var3);&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>func(1,2,3);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后的执行结果是：&nbsp;</p>
<p>0x0012ff78&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff80&nbsp;</p>
<p>0x0012ff68&nbsp;<br>0x0012ff6c&nbsp;<br>0x0012ff70&nbsp;</p>
<p><br>├———————┤&lt;—函数执行时的栈顶（ESP）、低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;1&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;2&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;var&nbsp;3&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;RET&nbsp;│&nbsp;<br>├———————┤&lt;—&#8220;__cdecl&#8221;函数返回后的栈顶（ESP）&nbsp;<br>│&nbsp;parameter&nbsp;1&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;parameter&nbsp;2&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;parameter&nbsp;3&nbsp;│&nbsp;<br>├———————┤&lt;—&#8220;__stdcall&#8221;函数返回后的栈顶（ESP）&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—栈底（基地址&nbsp;EBP）、高端内存区域&nbsp;</p>
<p><br>上图就是函数调用过程中堆栈的样子了。首先，三个参数以从又到左的次序压入堆栈，先压&#8220;param3&#8221;，再压&#8220;param2&#8221;，最后压入&#8220;param1&#8221;；然后压入函数的返回地址(RET)，接着跳转到函数地址接着执行（这里要补充一点，介绍UNIX下的缓冲溢出原理的文章中都提到在压入RET后，继续压入当前EBP，然后用当前ESP代替EBP。然而，有一篇介绍windows下函数调用的文章中说，在windows下的函数调用也有这一步骤，但根据我的实际调试，并未发现这一步，这还可以从param3和var1之间只有4字节的间隙这点看出来）；第三步，将栈顶(ESP)减去一个数，为本地变量分配内存空间，上例中是减去12字节(ESP=ESP-3*4，每个int变量占用4个字节)；接着就初始化本地变量的内存空间。由于&#8220;__stdcall&#8221;调用由被调函数调整堆栈，所以在函数返回前要恢复堆栈，先回收本地变量占用的内存(ESP=ESP+3*4)，然后取出返回地址，填入EIP寄存器，回收先前压入参数占用的内存(ESP=ESP+3*4)，继续执行调用者的代码。参见下列汇编代码：&nbsp;</p>
<p>;--------------func&nbsp;函数的汇编代码-------------------&nbsp;</p>
<p>:00401000&nbsp;83EC0C&nbsp;sub&nbsp;esp,&nbsp;0000000C&nbsp;//创建本地变量的内存空间&nbsp;<br>:00401003&nbsp;8B442410&nbsp;mov&nbsp;eax,&nbsp;dword&nbsp;ptr&nbsp;[esp+10]&nbsp;<br>:00401007&nbsp;8B4C2414&nbsp;mov&nbsp;ecx,&nbsp;dword&nbsp;ptr&nbsp;[esp+14]&nbsp;<br>:0040100B&nbsp;8B542418&nbsp;mov&nbsp;edx,&nbsp;dword&nbsp;ptr&nbsp;[esp+18]&nbsp;<br>:0040100F&nbsp;89442400&nbsp;mov&nbsp;dword&nbsp;ptr&nbsp;[esp],&nbsp;eax&nbsp;<br>:00401013&nbsp;8D442410&nbsp;lea&nbsp;eax,&nbsp;dword&nbsp;ptr&nbsp;[esp+10]&nbsp;<br>:00401017&nbsp;894C2404&nbsp;mov&nbsp;dword&nbsp;ptr&nbsp;[esp+04],&nbsp;ecx&nbsp;</p>
<p>&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;&#8230;（省略若干代码）&nbsp;</p>
<p>:00401075&nbsp;83C43C&nbsp;add&nbsp;esp,&nbsp;0000003C&nbsp;;恢复堆栈，回收本地变量的内存空间&nbsp;<br>:00401078&nbsp;C3&nbsp;ret&nbsp;000C&nbsp;;函数返回，恢复参数占用的内存空间&nbsp;<br>;如果是&#8220;__cdecl&#8221;的话，这里是&#8220;ret&#8221;，堆栈将由调用者恢复&nbsp;</p>
<p>;-------------------函数结束-------------------------&nbsp;</p>
<p><br>;--------------主程序调用func函数的代码--------------&nbsp;</p>
<p>:00401080&nbsp;6A03&nbsp;push&nbsp;00000003&nbsp;//压入参数param3&nbsp;<br>:00401082&nbsp;6A02&nbsp;push&nbsp;00000002&nbsp;//压入参数param2&nbsp;<br>:00401084&nbsp;6A01&nbsp;push&nbsp;00000001&nbsp;//压入参数param1&nbsp;<br>:00401086&nbsp;E875FFFFFF&nbsp;call&nbsp;00401000&nbsp;//调用func函数&nbsp;<br>;如果是&#8220;__cdecl&#8221;的话，将在这里恢复堆栈，&#8220;add&nbsp;esp,&nbsp;0000000C&#8221;&nbsp;</p>
<p>聪明的读者看到这里，差不多就明白缓冲溢出的原理了。先来看下面的代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;<br>#include&nbsp;&lt;string.h&gt;&nbsp;</p>
<p>void&nbsp;__stdcall&nbsp;func()&nbsp;<br>{&nbsp;<br>char&nbsp;lpBuff[8]="\0";&nbsp;<br>strcat(lpBuff,"AAAAAAAAAAA");&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>func();&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>编译后执行一下回怎么样？哈，&#8220;"0x00414141"指令引用的"0x00000000"内存。该内存不能为"read"。&#8221;，&#8220;非法操作&#8221;喽！"41"就是"A"的16进制的ASCII码了，那明显就是strcat这句出的问题了。"lpBuff"的大小只有8字节，算进结尾的\0，那strcat最多只能写入7个"A"，但程序实际写入了11个"A"外加1个\0。再来看看上面那幅图，多出来的4个字节正好覆盖了RET的所在的内存空间，导致函数返回到一个错误的内存地址，执行了错误的指令。如果能精心构造这个字符串，使它分成三部分，前一部份仅仅是填充的无意义数据以达到溢出的目的，接着是一个覆盖RET的数据，紧接着是一段shellcode，那只要着个RET地址能指向这段shellcode的第一个指令，那函数返回时就能执行shellcode了。但是软件的不同版本和不同的运行环境都可能影响这段shellcode在内存中的位置，那么要构造这个RET是十分困难的。一般都在RET和shellcode之间填充大量的NOP指令，使得exploit有更强的通用性。&nbsp;</p>
<p><br>├———————┤&lt;—低端内存区域&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—由exploit填入数据的开始&nbsp;<br>│&nbsp;│&nbsp;<br>│&nbsp;buffer&nbsp;│&lt;—填入无用的数据&nbsp;<br>│&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;RET&nbsp;│&lt;—指向shellcode，或NOP指令的范围&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;NOP&nbsp;│&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&lt;—填入的NOP指令，是RET可指向的范围&nbsp;<br>│&nbsp;NOP&nbsp;│&nbsp;<br>├———————┤&nbsp;<br>│&nbsp;│&nbsp;<br>│&nbsp;shellcode&nbsp;│&nbsp;<br>│&nbsp;│&nbsp;<br>├———————┤&lt;—由exploit填入数据的结束&nbsp;<br>│&nbsp;&#8230;&#8230;&nbsp;│&nbsp;<br>├———————┤&lt;—高端内存区域&nbsp;</p>
<p><br>windows下的动态数据除了可存放在栈中，还可以存放在堆中。了解C++的朋友都知道，C++可以使用new关键字来动态分配内存。来看下面的C++代码：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;<br>#include&nbsp;&lt;iostream.h&gt;&nbsp;<br>#include&nbsp;&lt;windows.h&gt;&nbsp;</p>
<p>void&nbsp;func()&nbsp;<br>{&nbsp;<br>char&nbsp;*buffer=new&nbsp;char[128];&nbsp;<br>char&nbsp;bufflocal[128];&nbsp;<br>static&nbsp;char&nbsp;buffstatic[128];&nbsp;<br>printf("0x%08x\n",buffer);&nbsp;//打印堆中变量的内存地址&nbsp;<br>printf("0x%08x\n",bufflocal);&nbsp;//打印本地变量的内存地址&nbsp;<br>printf("0x%08x\n",buffstatic);&nbsp;//打印静态变量的内存地址&nbsp;<br>}&nbsp;</p>
<p>void&nbsp;main()&nbsp;<br>{&nbsp;<br>func();&nbsp;<br>return;&nbsp;<br>}&nbsp;</p>
<p>程序执行结果为：&nbsp;</p>
<p>0x004107d0&nbsp;<br>0x0012ff04&nbsp;<br>0x004068c0&nbsp;</p>
<p>可以发现用new关键字分配的内存即不在栈中，也不在静态数据区。VC编译器是通过windows下的&#8220;堆(heap)&#8221;来实现new关键字的内存动态分配。在讲&#8220;堆&#8221;之前，先来了解一下和&#8220;堆&#8221;有关的几个API函数：&nbsp;</p>
<p>HeapAlloc&nbsp;在堆中申请内存空间&nbsp;<br>HeapCreate&nbsp;创建一个新的堆对象&nbsp;<br>HeapDestroy&nbsp;销毁一个堆对象&nbsp;<br>HeapFree&nbsp;释放申请的内存&nbsp;<br>HeapWalk&nbsp;枚举堆对象的所有内存块&nbsp;<br>GetProcessHeap&nbsp;取得进程的默认堆对象&nbsp;<br>GetProcessHeaps&nbsp;取得进程所有的堆对象&nbsp;<br>LocalAlloc&nbsp;<br>GlobalAlloc&nbsp;</p>
<p>当进程初始化时，系统会自动为进程创建一个默认堆，这个堆默认所占内存的大小为1M。堆对象由系统进行管理，它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间：&nbsp;</p>
<p>HANDLE&nbsp;hHeap=GetProcessHeap();&nbsp;<br>char&nbsp;*buff=HeapAlloc(hHeap,0,8);&nbsp;</p>
<p>其中hHeap是堆对象的句柄，buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢？它的值有什么意义吗？看看下面这段代码吧：&nbsp;</p>
<p>#pragma&nbsp;comment(linker,"/entry:main")&nbsp;//定义程序的入口&nbsp;<br>#include&nbsp;&lt;windows.h&gt;&nbsp;</p>
<p>_CRTIMP&nbsp;int&nbsp;(__cdecl&nbsp;*printf)(const&nbsp;char&nbsp;*,&nbsp;...);&nbsp;//定义STL函数printf&nbsp;<br>/*---------------------------------------------------------------------------&nbsp;<br>写到这里，我们顺便来复习一下前面所讲的知识：&nbsp;<br>(*注)printf函数是C语言的标准函数库中函数，VC的标准函数库由msvcrt.dll模块实现。&nbsp;<br>由函数定义可见，printf的参数个数是可变的，函数内部无法预先知道调用者压入的参数个数，函数只能通过分析第一个参数字符串的格式来获得压入参数的信息，由于这里参数的个数是动态的，所以必须由调用者来平衡堆栈，这里便使用了__cdecl调用规则。BTW，Windows系统的API函数基本上是__stdcall调用形式，只有一个API例外，那就是wsprintf，它使用__cdecl调用规则，同printf函数一样，这是由于它的参数个数是可变的缘故。&nbsp;<br>---------------------------------------------------------------------------*/&nbsp;<br>void&nbsp;main()&nbsp;<br>{&nbsp;<br>HANDLE&nbsp;hHeap=GetProcessHeap();&nbsp;<br>char&nbsp;*buff=HeapAlloc(hHeap,0,0x10);&nbsp;<br>char&nbsp;*buff2=HeapAlloc(hHeap,0,0x10);&nbsp;<br>HMODULE&nbsp;hMsvcrt=LoadLibrary("msvcrt.dll");&nbsp;<br>printf=(void&nbsp;*)GetProcAddress(hMsvcrt,"printf");&nbsp;<br>printf("0x%08x\n",hHeap);&nbsp;<br>printf("0x%08x\n",buff);&nbsp;<br>printf("0x%08x\n\n",buff2);&nbsp;<br>}&nbsp;</p>
<p>执行结果为：&nbsp;</p>
<p>0x00130000&nbsp;<br>0x00133100&nbsp;<br>0x00133118&nbsp;</p>
<p>hHeap的值怎么和那个buff的值那么接近呢？其实hHeap这个句柄就是指向HEAP首部的地址。在进程的用户区存着一个叫PEB(进程环境块)的结构，这个结构中存放着一些有关进程的重要信息，其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址，而偏移0x90处存放了指向进程所有堆的地址列表的指针。windows有很多API都使用进程的默认堆来存放动态数据，如windows&nbsp;2000下的所有ANSI版本的函数都是在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的，同一时刻只能有一个线程访问堆中的数据，当多个线程同时有访问要求时，只能排队等待，这样便造成程序执行效率下降。&nbsp;</p>
<p>最后来说说内存中的数据对齐。所位数据对齐，是指数据所在的内存地址必须是该数据长度的整数倍，DWORD数据的内存起始地址能被4除尽，WORD数据的内存起始地址能被2除尽，x86&nbsp;CPU能直接访问对齐的数据，当他试图访问一个未对齐的数据时，会在内部进行一系列的调整，这些调整对于程序来说是透明的，但是会降低运行速度，所以编译器在编译程序时会尽量保证数据对齐。同样一段代码，我们来看看用VC、Dev-C++和lcc三个不同编译器编译出来的程序的执行结果：&nbsp;</p>
<p>#include&nbsp;&lt;stdio.h&gt;&nbsp;</p>
<p>int&nbsp;main()&nbsp;<br>{&nbsp;<br>int&nbsp;a;&nbsp;<br>char&nbsp;b;&nbsp;<br>int&nbsp;c;&nbsp;<br>printf("0x%08x\n",&amp;a);&nbsp;<br>printf("0x%08x\n",&amp;b);&nbsp;<br>printf("0x%08x\n",&amp;c);&nbsp;<br>return&nbsp;0;&nbsp;<br>}&nbsp;</p>
<p>这是用VC编译后的执行结果：&nbsp;<br>0x0012ff7c&nbsp;<br>0x0012ff7b&nbsp;<br>0x0012ff80&nbsp;<br>变量在内存中的顺序：b(1字节)-a(4字节)-c(4字节)。&nbsp;</p>
<p>这是用Dev-C++编译后的执行结果：&nbsp;<br>0x0022ff7c&nbsp;<br>0x0022ff7b&nbsp;<br>0x0022ff74&nbsp;<br>变量在内存中的顺序：c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。&nbsp;</p>
<p>这是用lcc编译后的执行结果：&nbsp;<br>0x0012ff6c&nbsp;<br>0x0012ff6b&nbsp;<br>0x0012ff64&nbsp;<br>变量在内存中的顺序：同上。&nbsp;</p>
<p>三个编译器都做到了数据对齐，但是后两个编译器显然没VC&#8220;聪明&#8221;，让一个char占了4字节，浪费内存哦。&nbsp;</p>
<p><br>基础知识：&nbsp;<br>堆栈是一种简单的数据结构，是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶，另一端称为栈底，对堆栈的插入和删除操作被称为入栈和出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中，POP指令实现出栈操作，PUSH指令实现入栈操作。CPU的ESP寄存器存放当前线程的栈顶指针，EBP寄存器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址，当CPU执行完当前的指令后，从EIP寄存器中读取下一条指令的内存地址，然后继续执行。&nbsp;</p>
<p><br>参考：《Windows下的HEAP溢出及其利用》by:&nbsp;isno&nbsp;<br>《windows核心编程》by:&nbsp;Jeffrey&nbsp;Richter&nbsp;<br><br><br><br></p>
<p>摘要：&nbsp;讨论常见的堆性能问题以及如何防范它们。（共&nbsp;9&nbsp;页）</p>
<p>前言<br>您是否是动态分配的&nbsp;C/C++&nbsp;对象忠实且幸运的用户？您是否在模块间的往返通信中频繁地使用了&#8220;自动化&#8221;？您的程序是否因堆分配而运行起来很慢？不仅仅您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说，&#8220;我的代码真正好，只是堆太慢&#8221;。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题，是很有用的。</p>
<p>什么是堆？<br>（如果您已经知道什么是堆，可以跳到&#8220;什么是常见的堆性能问题？&#8221;部分）</p>
<p>在程序中，使用堆来动态分配和释放对象。在下列情况下，调用堆操作：&nbsp;</p>
<p>事先不知道程序所需对象的数量和大小。</p>
<p><br>对象太大而不适合堆栈分配程序。<br>堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。<br><a href="http://club.5ivb.net/UploadFile/2005311144027byUID16686.gif" target=_blank><img alt="" src="" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" border=0 dypop="按此在新窗口浏览图片"></a></p>
<p>GlobalAlloc/GlobalFree：Microsoft&nbsp;Win32&nbsp;堆调用，这些调用直接与每个进程的默认堆进行对话。</p>
<p>LocalAlloc/LocalFree：Win32&nbsp;堆调用（为了与&nbsp;Microsoft&nbsp;Windows&nbsp;NT&nbsp;兼容），这些调用直接与每个进程的默认堆进行对话。</p>
<p>COM&nbsp;的&nbsp;IMalloc&nbsp;分配程序（或&nbsp;CoTaskMemAlloc&nbsp;/&nbsp;CoTaskMemFree）：函数使用每个进程的默认堆。自动化程序使用&#8220;组件对象模型&nbsp;(COM)&#8221;的分配程序，而申请的程序使用每个进程堆。</p>
<p>C/C++&nbsp;运行时&nbsp;(CRT)&nbsp;分配程序：提供了&nbsp;malloc()&nbsp;和&nbsp;free()&nbsp;以及&nbsp;new&nbsp;和&nbsp;delete&nbsp;操作符。如&nbsp;Microsoft&nbsp;Visual&nbsp;Basic&nbsp;和&nbsp;Java&nbsp;等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT&nbsp;创建自己的私有堆，驻留在&nbsp;Win32&nbsp;堆的顶部。</p>
<p>Windows&nbsp;NT&nbsp;中，Win32&nbsp;堆是&nbsp;Windows&nbsp;NT&nbsp;运行时分配程序周围的薄层。所有&nbsp;API&nbsp;转发它们的请求给&nbsp;NTDLL。</p>
<p>Windows&nbsp;NT&nbsp;运行时分配程序提供&nbsp;Windows&nbsp;NT&nbsp;内的核心堆分配程序。它由具有&nbsp;128&nbsp;个大小从&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。</p>
<p>在图表的底部是&#8220;虚拟内存分配程序&#8221;，操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。</p>
<p>分配和释放块不就那么简单吗？为何花费这么长时间？</p>
<p>堆实现的注意事项<br>传统上，操作系统和运行时库是与堆的实现共存的。在一个进程的开始，操作系统创建一个默认堆，叫做&#8220;进程堆&#8221;。如果没有其他堆可使用，则块的分配使用&#8220;进程堆&#8221;。语言运行时也能在进程内创建单独的堆。（例如，C&nbsp;运行时创建它自己的堆。）除这些专用的堆外，应用程序或许多已载入的动态链接库&nbsp;(DLL)&nbsp;之一可以创建和使用单独的堆。Win32&nbsp;提供一整套&nbsp;API&nbsp;来创建和使用私有堆。有关堆函数（英文）的详尽指导，请参见&nbsp;MSDN。</p>
<p>当应用程序或&nbsp;DLL&nbsp;创建私有堆时，这些堆存在于进程空间，并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。（不能从一个堆分配而在另一个堆释放。）</p>
<p>在所有虚拟内存系统中，堆驻留在操作系统的&#8220;虚拟内存管理器&#8221;的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下，这些堆是操作系统堆中的层，而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆，而使用虚拟内存函数更利于堆的分配和块的使用。</p>
<p>典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用，堆尝试从前端列表找到一个自由块。如果失败，堆被迫从后端（保留和提交虚拟内存）分配一个大块来满足请求。通用的实现有每块分配的开销，这将耗费执行周期，也减少了可使用的存储空间。</p>
<p>Knowledge&nbsp;Base&nbsp;文章&nbsp;Q10758，&#8220;用&nbsp;calloc()&nbsp;和&nbsp;malloc()&nbsp;管理内存&#8221;&nbsp;（搜索文章编号）,&nbsp;包含了有关这些主题的更多背景知识。另外，有关堆实现和设计的详细讨论也可在下列著作中找到：&#8220;Dynamic&nbsp;Storage&nbsp;Allocation:&nbsp;A&nbsp;Survey&nbsp;and&nbsp;Critical&nbsp;Review&#8221;，作者&nbsp;Paul&nbsp;R.&nbsp;Wilson、Mark&nbsp;S.&nbsp;Johnstone、Michael&nbsp;Neely&nbsp;和&nbsp;David&nbsp;Boles；&#8220;International&nbsp;Workshop&nbsp;on&nbsp;Memory&nbsp;Management&#8221;,&nbsp;作者&nbsp;Kinross,&nbsp;Scotland,&nbsp;UK,&nbsp;1995&nbsp;年&nbsp;9&nbsp;月(<img src="" align=absMiddle border=0><a href="http://www.cs.utexas.edu/users/oops/papers.html" target=_blank><font color=#000000><u>http://www.cs.utexas.edu/users/oops/papers.html</u></font></a>)（英文）。</p>
<p>Windows&nbsp;NT&nbsp;的实现（Windows&nbsp;NT&nbsp;版本&nbsp;4.0&nbsp;和更新版本）&nbsp;使用了&nbsp;127&nbsp;个大小从&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节的&nbsp;8&nbsp;字节对齐块空闲列表和一个&#8220;大块&#8221;列表。&#8220;大块&#8221;列表（空闲列表[0]）&nbsp;保存大于&nbsp;1,024&nbsp;字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下，&#8220;进程堆&#8221;执行收集操作。（收集是将相邻空闲块合并成一个大块的操作。）收集耗费了额外的周期，但减少了堆块的内部碎片。</p>
<p>单一全局锁保护堆，防止多线程式的使用。（请参见&#8220;Server&nbsp;Performance&nbsp;and&nbsp;Scalability&nbsp;Killers&#8221;中的第一个注意事项,&nbsp;George&nbsp;Reilly&nbsp;所著，在&nbsp;&#8220;MSDN&nbsp;Online&nbsp;Web&nbsp;Workshop&#8221;上（站点：<img src="" align=absMiddle border=0><a href="http://msdn.microsoft.com/workshop/server/iis/tencom.asp" target=_blank><font color=#000000><u>http://msdn.microsoft.com/workshop/server/iis/tencom.asp</u></font></a>（英文）。）单一全局锁本质上是用来保护堆数据结构，防止跨多线程的随机存取。若堆操作太频繁，单一全局锁会对性能有不利的影响。</p>
<p>什么是常见的堆性能问题？<br>以下是您使用堆时会遇到的最常见问题：&nbsp;</p>
<p>分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块，所以运行时分配程序代码会耗费周期寻找较大的空闲块，或从后端分配程序分配新块。</p>
<p><br>释放操作造成的速度减慢。释放操作耗费较多周期，主要是启用了收集操作。收集期间，每个释放操作&#8220;查找&#8221;它的相邻块，取出它们并构造成较大块，然后再把此较大块插入空闲列表。在查找期间，内存可能会随机碰到，从而导致高速缓存不能命中，性能降低。</p>
<p><br>堆竞争造成的速度减慢。当两个或多个线程同时访问数据，而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦；这也是目前多处理器系统遇到的最大问题。当大量使用内存块的应用程序或&nbsp;DLL&nbsp;以多线程方式运行（或运行于多处理器系统上）时将导致速度减慢。单一锁定的使用—常用的解决方案—意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。&nbsp;<br>竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的，但开销更大的是数据从处理器高速缓存中丢失，以及后来线程复活时的数据重建。</p>
<p>堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块，以及块的越界重写等明显问题。（破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节，请参见&nbsp;Microsoft&nbsp;Visual&nbsp;C++(R)&nbsp;调试文档&nbsp;。）</p>
<p><br>频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配，随重分配增长和释放。不要这样做，如果可能，尽量分配大字符串和使用缓冲区。另一种方法就是尽量少用连接操作。<br>竞争是在分配和释放操作中导致速度减慢的问题。理想情况下，希望使用没有竞争和快速分配/释放的堆。可惜，现在还没有这样的通用堆，也许将来会有。</p>
<p>在所有的服务器系统中（如&nbsp;IIS、MSProxy、DatabaseStacks、网络服务器、&nbsp;Exchange&nbsp;和其他）,&nbsp;堆锁定实在是个大瓶颈。处理器数越多，竞争就越会恶化。</p>
<p>尽量减少堆的使用<br>现在您明白使用堆时存在的问题了，难道您不想拥有能解决这些问题的超级魔棒吗？我可希望有。但没有魔法能使堆运行加快—因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略，情况将会大大好转。调整使用堆的方法，减少对堆的操作是提高性能的良方。</p>
<p>如何减少使用堆操作？通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例：</p>
<p>struct&nbsp;ObjectA&nbsp;{<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectA&nbsp;的数据&nbsp;<br>}</p>
<p>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//&nbsp;同时使用&nbsp;objectA&nbsp;和&nbsp;objectB</p>
<p>//<br>//&nbsp;使用指针&nbsp;<br>//<br>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;*&nbsp;pObjA;<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//<br>//&nbsp;使用嵌入<br>//<br>struct&nbsp;ObjectB&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;pObjA;<br>&nbsp;&nbsp;&nbsp;//&nbsp;objectB&nbsp;的数据&nbsp;<br>}</p>
<p>//<br>//&nbsp;集合&nbsp;&#8211;&nbsp;在另一对象内使用&nbsp;objectA&nbsp;和&nbsp;objectB<br>//</p>
<p>struct&nbsp;ObjectX&nbsp;{<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectA&nbsp;&nbsp;objA;<br>&nbsp;&nbsp;&nbsp;struct&nbsp;ObjectB&nbsp;&nbsp;objB;<br>}</p>
<p>避免使用指针关联两个数据结构。如果使用指针关联两个数据结构，前面实例中的对象&nbsp;A&nbsp;和&nbsp;B&nbsp;将被分别分配和释放。这会增加额外开销—我们要避免这种做法。</p>
<p><br>把带指针的子对象嵌入父对象。当对象中有指针时，则意味着对象中有动态元素（百分之八十）和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。</p>
<p><br>合并小对象形成大对象（聚合）。聚合减少分配和释放的块的数量。如果有几个开发者，各自开发设计的不同部分，则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。</p>
<p><br>内联缓冲区能够满足百分之八十的需要（aka&nbsp;80-20&nbsp;规则）。个别情况下，需要内存缓冲区来保存字符串/二进制数据，但事先不知道总字节数。估计并内联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十，可以分配一个新的缓冲区和指向这个缓冲区的指针。这样，就减少分配和释放调用并增加数据的位置空间，从根本上提高代码的性能。</p>
<p><br>在块中分配对象（块化）。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪，例如对一个&nbsp;{名称，值}&nbsp;对的列表，有两种选择：选择一是为每一个&#8220;名称-值&#8221;对分配一个节点；选择二是分配一个能容纳（如五个）&#8220;名称-值&#8221;对的结构。例如，一般情况下，如果存储四对，就可减少节点的数量，如果需要额外的空间数量，则使用附加的链表指针。&nbsp;<br>块化是友好的处理器高速缓存，特别是对于&nbsp;L1-高速缓存，因为它提供了增加的位置&nbsp;—不用说对于块分配，很多数据块会在同一个虚拟页中。</p>
<p>正确使用&nbsp;_amblksiz。C&nbsp;运行时&nbsp;(CRT)&nbsp;有它的自定义前端分配程序，该分配程序从后端（Win32&nbsp;堆）分配大小为&nbsp;_amblksiz&nbsp;的块。将&nbsp;_amblksiz&nbsp;设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用&nbsp;CRT&nbsp;的程序适用。<br>使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面，代码会有点特殊，但如果经过深思熟虑，代码还是很容易管理的。</p>
<p>其他提高性能的技术<br>下面是一些提高速度的技术：&nbsp;</p>
<p>使用&nbsp;Windows&nbsp;NT5&nbsp;堆&nbsp;<br>由于几个同事的努力和辛勤工作，1998&nbsp;年初&nbsp;Microsoft&nbsp;Windows(R)&nbsp;2000&nbsp;中有了几个重大改进：</p>
<p>改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构，防止多线程式的使用。但不幸的是，在高通信量的情况下，堆仍受困于全局锁，导致高竞争和低性能。Windows&nbsp;2000&nbsp;中，锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。</p>
<p><br>使用&nbsp;&#8220;Lookaside&#8221;列表。堆数据结构对块的所有空闲项使用了大小在&nbsp;8&nbsp;到&nbsp;1,024&nbsp;字节（以&nbsp;8-字节递增）的快速高速缓存。快速高速缓存最初保护在全局锁内。现在，使用&nbsp;lookaside&nbsp;列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定，而是使用&nbsp;64&nbsp;位的互锁操作，因此提高了性能。</p>
<p><br>内部数据结构算法也得到改进。<br>这些改进避免了对分配高速缓存的需求，但不排除其他的优化。使用&nbsp;Windows&nbsp;NT5&nbsp;堆评估您的代码；它对小于&nbsp;1,024&nbsp;字节&nbsp;(1&nbsp;KB)&nbsp;的块（来自前端分配程序的块）是最佳的。GlobalAlloc()&nbsp;和&nbsp;LocalAlloc()&nbsp;建立在同一堆上，是存取每个进程堆的通用机制。如果希望获得高的局部性能，则使用&nbsp;Heap(R)&nbsp;API&nbsp;来存取每个进程堆，或为分配操作创建自己的堆。如果需要对大块操作，也可以直接使用&nbsp;VirtualAlloc()&nbsp;/&nbsp;VirtualFree()&nbsp;操作。</p>
<p>上述改进已在&nbsp;Windows&nbsp;2000&nbsp;beta&nbsp;2&nbsp;和&nbsp;Windows&nbsp;NT&nbsp;4.0&nbsp;SP4&nbsp;中使用。改进后，堆锁的竞争率显著降低。这使所有&nbsp;Win32&nbsp;堆的直接用户受益。CRT&nbsp;堆建立于&nbsp;Win32&nbsp;堆的顶部，但它使用自己的小块堆，因而不能从&nbsp;Windows&nbsp;NT&nbsp;改进中受益。（Visual&nbsp;C++&nbsp;版本&nbsp;6.0&nbsp;也有改进的堆分配程序。）</p>
<p>使用分配高速缓存&nbsp;<br>分配高速缓存允许高速缓存分配的块，以便将来重用。这能够减少对进程堆（或全局堆）的分配/释放调用的次数，也允许最大限度的重用曾经分配的块。另外，分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。</p>
<p>典型地，自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套固定大小（如&nbsp;32&nbsp;字节、64&nbsp;字节、128&nbsp;字节等）。这一个很好的策略，但这种自定义堆分配程序丢失与分配和释放的对象相关的&#8220;语义信息&#8221;。&nbsp;</p>
<p>与自定义堆分配程序相反，&#8220;分配高速缓存&#8221;作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外，它们还能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化，这些参数表示并发级别、对象大小和保持在空闲列表中的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池（不超过指定的阀值）并使用私有保护锁。合在一起，分配高速缓存和私有锁减少了与主系统堆的通信量，因而提供了增加的并发、最大限度的重用和较高的可伸缩性。</p>
<p>需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动，将释放分配对象的池，从而提高性能。</p>
<p>可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一，这种关系可以用来减少内存分配。</p>
<p>分配高速缓存也起到了调试助手的作用，帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名，甚至能够找到确切的失败的调用者。</p>
<p>MP&nbsp;堆&nbsp;<br>MP&nbsp;堆是对多处理器友好的分布式分配的程序包，在&nbsp;Win32&nbsp;SDK（Windows&nbsp;NT&nbsp;4.0&nbsp;和更新版本）中可以得到。最初由&nbsp;JVert&nbsp;实现，此处堆抽象建立在&nbsp;Win32&nbsp;堆程序包的顶部。MP&nbsp;堆创建多个&nbsp;Win32&nbsp;堆，并试图将分配调用分布到不同堆，以减少在所有单一锁上的竞争。</p>
<p>本程序包是好的步骤&nbsp;—一种改进的&nbsp;MP-友好的自定义堆分配程序。但是，它不提供语义信息和缺乏统计功能。通常将&nbsp;MP&nbsp;堆作为&nbsp;SDK&nbsp;库来使用。如果使用这个&nbsp;SDK&nbsp;创建可重用组件，您将大大受益。但是，如果在每个&nbsp;DLL&nbsp;中建立这个&nbsp;SDK&nbsp;库，将增加工作设置。</p>
<p>重新思考算法和数据结构&nbsp;<br>要在多处理器机器上伸缩，则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问，&#8220;我能用不同的数据结构完成此工作吗？&#8221;例如，如果在应用程序初始化时加载了只读项的列表，这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片，从而增强性能。</p>
<p>减少需要的小对象的数量减少堆分配程序的负载。例如，我们在服务器的关键处理路径上使用五个不同的对象，每个对象单独分配和释放。一起高速缓存这些对象，把堆调用从五个减少到一个，显著减少了堆的负载，特别当每秒钟处理&nbsp;1,000&nbsp;个以上的请求时。</p>
<p>如果大量使用&#8220;Automation&#8221;结构，请考虑从主线代码中删除&#8220;Automation&nbsp;BSTR&#8221;，或至少避免重复的&nbsp;BSTR&nbsp;操作。（BSTR&nbsp;连接导致过多的重分配和分配/释放操作。）</p>
<p>摘要<br>对所有平台往往都存在堆实现，因此有巨大的开销。每个单独代码都有特定的要求，但设计能采用本文讨论的基本理论来减少堆之间的相互作用。&nbsp;</p>
<p>评价您的代码中堆的使用。</p>
<p><br>改进您的代码，以使用较少的堆调用：分析关键路径和固定数据结构。</p>
<p><br>在实现自定义的包装程序之前使用量化堆调用成本的方法。</p>
<p><br>如果对性能不满意，请要求&nbsp;OS&nbsp;组改进堆。更多这类请求意味着对改进堆的更多关注。</p>
<p><br>要求&nbsp;C&nbsp;运行时组针对&nbsp;OS&nbsp;所提供的堆制作小巧的分配包装程序。随着&nbsp;OS&nbsp;堆的改进，C&nbsp;运行时堆调用的成本将减小。</p>
<p><br>操作系统（Windows&nbsp;NT&nbsp;家族）正在不断改进堆。请随时关注和利用这些改进。<br>Murali&nbsp;Krishnan&nbsp;是&nbsp;Internet&nbsp;Information&nbsp;Server&nbsp;(IIS)&nbsp;组的首席软件设计工程师。从&nbsp;1.0&nbsp;版本开始他就设计&nbsp;IIS，并成功发行了&nbsp;1.0&nbsp;版本到&nbsp;4.0&nbsp;版本。Murali&nbsp;组织并领导&nbsp;IIS&nbsp;性能组三年&nbsp;(1995-1998),&nbsp;从一开始就影响&nbsp;IIS&nbsp;性能。他拥有威斯康星州&nbsp;Madison&nbsp;大学的&nbsp;M.S.和印度&nbsp;Anna&nbsp;大学的&nbsp;B.S.。工作之外，他喜欢阅读、打排球和家庭烹饪。<br><br><br><br><img src="" align=absMiddle border=0><a href="http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835" target=_blank><font color=#000000><u>http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835</u></font></a><br>我在学习对象的生存方式的时候见到一种是在堆栈(stack)之中，如下&nbsp;&nbsp;<br>CObject&nbsp;&nbsp;object;&nbsp;&nbsp;<br>还有一种是在堆(heap)中&nbsp;&nbsp;如下&nbsp;&nbsp;<br>CObject*&nbsp;&nbsp;pobject=new&nbsp;&nbsp;CObject();&nbsp;&nbsp;<br>&nbsp;<br>请问&nbsp;&nbsp;<br>（1）这两种方式有什么区别？&nbsp;&nbsp;<br>（2）堆栈与堆有什么区别？？&nbsp;&nbsp;<br>&nbsp;<br>&nbsp;<br>---------------------------------------------------------------&nbsp;&nbsp;<br>&nbsp;<br>1)&nbsp;&nbsp;about&nbsp;&nbsp;stack,&nbsp;&nbsp;system&nbsp;&nbsp;will&nbsp;&nbsp;allocate&nbsp;&nbsp;memory&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;instance&nbsp;&nbsp;of&nbsp;&nbsp;object&nbsp;&nbsp;automatically,&nbsp;&nbsp;and&nbsp;&nbsp;to&nbsp;&nbsp;the <br>&nbsp;heap,&nbsp;&nbsp;you&nbsp;&nbsp;must&nbsp;&nbsp;allocate&nbsp;&nbsp;memory&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp;&nbsp;instance&nbsp;&nbsp;of&nbsp;&nbsp;object&nbsp;&nbsp;with&nbsp;&nbsp;new&nbsp;&nbsp;or&nbsp;&nbsp;malloc&nbsp;&nbsp;manually.&nbsp;&nbsp;<br>2)&nbsp;&nbsp;when&nbsp;&nbsp;function&nbsp;&nbsp;ends,&nbsp;&nbsp;system&nbsp;&nbsp;will&nbsp;&nbsp;automatically&nbsp;&nbsp;free&nbsp;&nbsp;the&nbsp;&nbsp;memory&nbsp;&nbsp;area&nbsp;&nbsp;of&nbsp;&nbsp;stack,&nbsp;&nbsp;but&nbsp;&nbsp;to&nbsp;&nbsp;the&nbsp; <br>heap,&nbsp;&nbsp;you&nbsp;&nbsp;must&nbsp;&nbsp;free&nbsp;&nbsp;the&nbsp;&nbsp;memory&nbsp;&nbsp;area&nbsp;&nbsp;manually&nbsp;&nbsp;with&nbsp;&nbsp;free&nbsp;&nbsp;or&nbsp;&nbsp;delete,&nbsp;&nbsp;else&nbsp;&nbsp;it&nbsp;&nbsp;will&nbsp;&nbsp;result&nbsp;&nbsp;in&nbsp;&nbsp;memory <br>leak.&nbsp;&nbsp;<br>3)栈内存分配运算内置于处理器的指令集中，效率很高，但是分配的内存容量有限。&nbsp;&nbsp;<br>4）堆上分配的内存可以有我们自己决定，使用非常灵活。&nbsp;&nbsp;<br>---------------------------------------------------------------&nbsp;&nbsp;</p>
</div>
</div>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/83385.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-05-19 16:53 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/05/19/83385.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>详解函数调用约定</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/15/83062.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Fri, 15 May 2009 09:30:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/15/83062.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/83062.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/05/15/83062.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/83062.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/83062.html</trackback:ping><description><![CDATA[<br>在编写windows程序时，我们经常发现一些函数的前面带有WINAPI等的关键字(不知道这样描述是否准确，请明白的读者联系本人更正<img height=20 src="http://www.cppblog.com/Emoticons/QQ/13.gif" width=20 border=0>)，如windows的消息响应函数定义如下：<br><br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 10pt; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">1</span>&nbsp;<span style="COLOR: #000000">LRESULT&nbsp;CALLBACK&nbsp;WndProc&nbsp;(HWND,&nbsp;UINT,&nbsp;WPARAM,&nbsp;LPARAM)&nbsp;;</span></div>
<br>这里的LRESULT在windows中被定义为long型，而CALLBACK则被定义成了__stacall，仔细看了一下，在WINDEF.H中还包含如下定义：<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">1</span><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;CALLBACK&nbsp;&nbsp;&nbsp;&nbsp;__stdcall</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">2</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;WINAPI&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__stdcall</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">3</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;WINAPIV&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__cdecl</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">4</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;APIENTRY&nbsp;&nbsp;&nbsp;&nbsp;WINAPI</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">5</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;APIPRIVATE&nbsp;&nbsp;__stdcall</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">6</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;PASCAL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__stdcall</span></div>
<br>那么，这里的__stacall、__cdecl到底是什么意思呢，又有什么作用呢？我经过查找相关资料对其有了些许浅显的了解，这里与大家一起分享。<br><br>我们知道，在C语言中假设我们有这样一个函数定义：<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;function_add(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a,&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b);</span></div>
那么只要用<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">1</span><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;x&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">3</span><span style="COLOR: #000000">,&nbsp;y&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">5</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">2</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;result&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;function_add(x,&nbsp;y);</span></div>
这样的方式就可以对函数进行调用了。但是，在计算机中，当高级语言程序被编译成计算机可以识别的机器码时，有一个问题就凸现出来：在CPU中，计算机没有办法知道一个函数调用需要多少个参数、这些参数是什么样的，也没有硬件可以保存这些参数。也就是说，计算机并不知道应该怎么给这个函数传递参数，传递参数的工作必须由函数调用者和函数本身来协调。为此，计算机提供了一种被称为栈的数据结构来支持函数的参数传递。<br><br>栈是一种先进后出的数据结构，栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶的上方向堆栈中加入数据，这个操作被称为压栈(Push)，压栈以后，栈顶自动变成新加入数据项的位置，栈顶指针也随之修改。用户也可以从堆栈中取出栈顶元素，这个操作被称为弹出栈(pop)，弹出栈以后，栈顶的下一个元素变成栈顶，栈顶指针随之修改。<br><br>函数调用时，调用者依次把参数压栈，然后调用函数，函数被调用以后，在堆栈中取出数据，并进行计算。函数计算结束以后，或者调用者、或者函数本身修改堆栈，使堆栈恢复原状。问题的关键就在这里，到底应该如何清除栈呢？<br><br>函数调用需要进行参数传递，在参数传递过程中有两个很重要的问题必须得到明确说明：<br>&nbsp;&nbsp;&nbsp; 1. 当参数个数多于一个时，按照什么样的顺序把参数压入栈中<br>&nbsp;&nbsp;&nbsp; 2. 函数调用后，由谁来负责把堆栈恢复原状<br><br>在高级语言中，函数调用约定就是用来说明这两个问题的。常见的函数调用约定有：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stdcall<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cdecl<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fastcall<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; thiscall<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; naked call<br><br>下面一一进行介绍。<br><br>一、stdcall调用约定<br><br>stdcall，也可写作__stdcall，很多时候被称为pascal调用约定，因为pascal是早期很常见的一种教学用计算机程序设计语言，其语法严谨，使用的函数调用约定就是stdcall。几乎我们写的每一个WINDOWS API函数都是__stdcall类型的。在Microsoft C++系列的C/C++编译器中，常常用PASCAL宏来声明这个调用约定，类似的宏还有WINAPI和CALLBACK(如文章开头引用的在WINDEF.H头文件中的定义)。<br><br>stdcall调用约定声明的语法为（以前面的function_add函数为例）：<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;__stdcall&nbsp;function_add(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a,</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b);</span></div>
<br>stdcall调用约定意味着：<br>(1) 参数从右向左压入堆栈<br>(2) 函数自身修改堆栈<br>(3) 函数名自动加前导的下划线，后面紧跟一个@符号，其后紧跟着参数的大小<br><br>以上述这个函数为例，参数b首先被压栈，然后是参数a，函数调用function_add(1, 2)调用处翻译成汇编语言将变成：<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;// 第二个参数入栈<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 第一个参数入栈<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; call&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; function_add&nbsp;&nbsp;&nbsp;&nbsp;// 调用参数，注意此时自动把cs:eip入栈<br><br>而对于函数自身，则可翻译为：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;push&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ebp　　　　　　&nbsp;&nbsp;&nbsp;// 保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov　&nbsp;&nbsp;&nbsp;ebp, esp　　　　&nbsp;&nbsp;&nbsp; // 保存堆栈指针<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov　&nbsp;&nbsp;&nbsp;eax,[ebp + 8H]　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 堆栈中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;add　&nbsp;&nbsp;&nbsp; eax,[ebp + 0CH]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 堆栈中ebp + 12处保存了b<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mov　&nbsp;&nbsp;&nbsp;esp, ebp　　　　&nbsp;&nbsp; //&nbsp; 恢复esp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pop　&nbsp;&nbsp;&nbsp; ebp<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ret　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;8<br><br>而在编译时，这个函数的名字被翻译成<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#64;&#56;"><font color=#336699>_function@8</font></a>
<p>　　注意不同编译器会插入自己的汇编代码以提供编译的通用性，但是大体代码如此。其中在函数开始处保留esp到ebp中，在函数结束恢复是编译器常用的方法。</p>
<p>　　从函数调用看，2和1依次被push进堆栈，而在函数中又通过相对于ebp(即刚进函数时的堆栈指针）的偏移量存取参数。函数结束后，ret 8表示清理8个字节的堆栈，函数自己恢复了堆栈。<br><br>由于不同的编译器产生栈的方式不尽相同，调用者就不一定能够正常的完成堆栈的清除工作，但函数本身自己可以解决清除工作，所以，在跨平台的程序开发中的函数调用，我们通常都使用__stdcall约定，windows下的绝大多数函数也都是stdcall调用。既然如此，为什么还需要__cdecl呢？别着急，接着往下看。<br><br>二、cdecl调用约定<br><br>cdecl，也可写作__cdecl，又称为C调用约定，是C/C++语言和MFC程序默认缺省的调用约定，它的定义语法是：<br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;function&nbsp;(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a&nbsp;,</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b)　&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">不加修饰就是C调用约定</span><span style="COLOR: #008000"><br></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;__cdecl&nbsp;function(</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a,</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b)&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">明确指出C调用约定<br></span></div>
<p><br>采用__cdecl约定时，函数参数按照从右到左的顺序入栈，并且由调用函数者把参数弹出栈以清理堆栈。因此，实现可变参数的函数只能使用该调用约定。由于这种变化，C调用约定允许函数的参数的个数是不固定的，这也是C语言的一大特色。同时，由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码，所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。 <br><br>对于前面的function函数，使用cdecl后的汇编码变成： <br><br>调用处<br>　　push&nbsp;&nbsp; 1<br>　　push&nbsp;&nbsp; 2<br>　　call&nbsp;&nbsp;&nbsp;&nbsp; function<br>　　add　esp, 8　　　　　 // 注意：这里调用者在恢复堆栈</p>
<p>　　被调用函数_function处<br>　　push&nbsp;&nbsp;&nbsp; ebp　　　　　　// 保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复<br>　　mov&nbsp;&nbsp;&nbsp; ebp,esp　　　　 // 保存堆栈指针<br>　　mov　eax,[ebp + 8H]　 // 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a<br>　　add　eax,[ebp + 0CH]&nbsp;&nbsp;&nbsp;&nbsp;// 堆栈中ebp + 12处保存了b<br>　　mov　esp,ebp　　　　 // 恢复esp<br>　　pop　ebp<br>　　ret　　　　　　　　　//&nbsp; 注意，这里没有修改堆栈</p>
<p><br>不写了，累得慌，呵呵 转载两篇文章吧<br><br></p>
<p>__stdcall,__cdecl,_cdecl,_stdcall,。__fastcall,_fastcall 区别简介&nbsp;</p>
<p>1.</p>
<p>今天写线程函数时，发现msdn中对ThreadProc的定义有要求：DWORD WINAPI ThreadProc(LPVOID lpParameter); </p>
<p>不解为什么要用WINAPI宏定义，查了后发现下面的定义。于是乎需要区别__stdcall和__cdecl两者的区别； #define CALLBACK __stdcall<br>#define WINAPI __stdcall<br>#define WINAPIV __cdecl<br>#define APIENTRY WINAPI<br>#define APIPRIVATE __stdcall<br>#define PASCAL __stdcall<br>#define cdecl _cdecl<br>#ifndef CDECL<br>#define CDECL _cdecl<br>#endif </p>
<p>几乎我们写的每一个WINDOWS API函数都是__stdcall类型的，首先，需要了解两者之间的区别： WINDOWS的函数调用时需要用到栈（STACK，一种先入后出的存储结构）。当函数调用完成后，栈需要清楚，这里就是问题的关键，如何清除？？ 如果我们的函数使用了_cdecl，那么栈的清除工作是由调用者，用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题，不同的编译器产生栈的方式不尽相同，那么调用者能否正常的完成清除工作呢？答案是不能。 如果使用__stdcall，上面的问题就解决了，函数自己解决清除工作。所以，在跨（开发）平台的调用中，我们都使用__stdcall（虽然有时是以WINAPI的样子出现）。那么为什么还需要_cdecl呢？当我们遇到这样的函数如fprintf()它的参数是可变的，不定长的，被调用者事先无法知道参数的长度，事后的清除工作也无法正常的进行，因此，这种情况我们只能使用_cdecl。到这里我们有一个结论，如果你的程序中没有涉及可变参数，最好使用__stdcall关键字。</p>
<p>2.</p>
<p>__cdecl,__stdcall是声明的函数调用协议.主要是传参和弹栈方面的不同.一般c++用的是__cdecl,windows里大都用的是__stdcall(API) <br><br>__cdecl是C/C++和MFC程序默认使用的调用约定，也可以在函数声明时加上__cdecl关键字来手工指定。采用__cdecl约定时，函数参数按照从右到左的顺序入栈，并且由调用函数者把参数弹出栈以清理堆栈。因此，实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码，所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。 <br>__stdcall调用约定用于调用Win32 API函数。采用__stdcall约定时，函数参数按照从右到左的顺序入栈，被调用的函数在返回前清理传送参数的栈，函数参数个数固定。由于函数体本身知道传进来的参数个数，因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。 <br>__fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节（DWORD）的参数分别放在ECX和EDX寄存器，其余的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall</p>
<p>3.</p>
<p style="TEXT-INDENT: 2em">__stdcall:</p>
<p style="TEXT-INDENT: 2em">_stdcall 调用约定相当于16位动态库中经常使用的PASCAL调用约定。</p>
<p style="TEXT-INDENT: 2em">
<table cellSpacing=0 cellPadding=0 width=160 align=left border=0>
    <tbody>
        <tr>
            <td>&nbsp;</td>
        </tr>
    </tbody>
</table>
在32位的VC++5.0中PASCAL调用约定不再被支持（实际上它已被定义为__stdcall。除了__pascal外，__fortran和__syscall也不被支持），取而代之的是__stdcall调用约定。两者实质上是一致的，即函数的参数自右向左通过栈传递，被调用的函数在返回前清理传送参数的内存栈，但不同的是函数名的修饰部分（关于函数名的修饰部分在后面将详细说明）。</p>
<p style="TEXT-INDENT: 2em">_stdcall是Pascal程序的缺省调用方式，通常用于Win32 Api中，函数采用从右到左的压栈方式，自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀，在函数名后加上"@"和参数的字节数。</p>
<p style="TEXT-INDENT: 2em">_cdecl:</p>
<p style="TEXT-INDENT: 2em">_cdecl c调用约定, 按从右至左的顺序压参数入栈，由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的（正因为如此，实现可变参数的函数只能使用该调用约定）。另外，在函数名修饰约定方面也有所不同。</p>
<p style="TEXT-INDENT: 2em">_cdecl是C和C＋＋程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码，所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。</p>
<p style="TEXT-INDENT: 2em">__fastcall:</p>
<p style="TEXT-INDENT: 2em">__fastcall调用约定是"人"如其名，它的主要特点就是快，因为它是通过寄存器来传送参数的（实际上，它用ECX和EDX传送前两个双字（DWORD）或更小的参数，剩下的参数仍旧自右向左压栈传送，被调用的函数在返回前清理传送参数的内存栈），在函数名修饰约定方面，它和前两者均不同。</p>
<p style="TEXT-INDENT: 2em">_fastcall方式的函数采用寄存器传递参数，VC将函数编译后会在函数名前面加上"@"前缀，在函数名后加上"@"和参数的字节数。</p>
<p style="TEXT-INDENT: 2em">thiscall:</p>
<p style="TEXT-INDENT: 2em">thiscall仅仅应用于"C++"成员函数。this指针存放于CX寄存器，参数从右到左压。thiscall不是关键词，因此不能被程序员指定。</p>
<p style="TEXT-INDENT: 2em">naked call:</p>
<p style="TEXT-INDENT: 2em">采用1-4的调用约定时，如果必要的话，进入函数时编译器会产生代码来保存ESI，EDI，EBX，EBP寄存器，退出函数时则产生代码恢复这些寄存器的内容。</p>
<p style="TEXT-INDENT: 2em">naked call不产生这样的代码。naked call不是类型修饰符，故必须和_declspec共同使用。</p>
<p style="TEXT-INDENT: 2em">另附:</p>
<p style="TEXT-INDENT: 2em">关键字 __stdcall、__cdecl和__fastcall可以直接加在要输出的函数前，也可以在编译环境的Setting...\C/C++ \Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时，直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd，即__cdecl。</p>
<p style="TEXT-INDENT: 2em">要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定，至于函数名修饰约定，可以通过其它方法模仿。还有一个值得一提的是WINAPI宏，Windows.h支持该宏，它可以将出函数翻译成适当的调用约定，在WIN32中，它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。</p>
<p style="TEXT-INDENT: 2em">名字修饰约定 <br><br>1、修饰名(Decoration name) <br>&#8220;C&#8221;或者&#8220;C++&#8221;函数在内部（编译和链接）通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的，如在模块定义文件里头指定输出&#8220;C++&#8221;重载函数、构造函数、析构函数，又如在汇编代码里调用&#8220;C&#8221;&#8221;或&#8220;C++&#8221;函数等。 <br><br>修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。 <br><br>2、名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译种类和调用约定的不同而不同，下面分别说明。 <br><br>a、C编译时函数名修饰约定规则： <br><br>__stdcall调用约定在输出函数名前加上一个下划线前缀，后面加上一个&#8220;@&#8221;符号和其参数的字节数，格式为_functionname@number。 <br><br>__cdecl调用约定仅在输出函数名前加上一个下划线前缀，格式为_functionname。</p>
<p style="TEXT-INDENT: 2em">__fastcall调用约定在输出函数名前加上一个&#8220;@&#8221;符号，后面也是一个&#8220;@&#8221;符号和其参数的字节数，格式为@functionname@number。 <br><br>它们均不改变输出函数名中的字符大小写，这和PASCAL调用约定不同，PASCAL约定输出的函数名无任何修饰且全部大写。 <br><br>b、C++编译时函数名修饰约定规则： <br><br>__stdcall调用约定： <br>1、以&#8220;?&#8221;标识函数名的开始，后跟函数名； <br>2、函数名后面以&#8220;@@YG&#8221;标识参数表的开始，后跟参数表； <br>3、参数表以代号表示： <br>X--void ， <br>D--char， <br>E--unsigned char， <br>F--short， <br>H--int， <br>I--unsigned int， <br>J--long， <br>K--unsigned long， <br>M--float， <br>N--double， <br>_N--bool， <br>.... <br>PA--表示指针，后面的代号表明指针类型，如果相同类型的指针连续出现，以&#8220;0&#8221;代替，一个&#8220;0&#8221;代表一次重复； <br>4、参数表的第一项为该函数的返回值类型，其后依次为参数的数据类型,指针标识在其所指数据类型前； <br>5、参数表后以&#8220;@Z&#8221;标识整个名字的结束，如果该函数无参数，则以&#8220;Z&#8221;标识结束。 <br><br>其格式为&#8220;?functionname@@YG*****@Z&#8221;或&#8220;?functionname@@YG*XZ&#8221;，例如 <br>int Test1（char *var1,unsigned long）-----&#8220;?Test1@@YGHPADK@Z&#8221; <br>void Test2（） -----&#8220;?Test2@@YGXXZ&#8221; <br><br>__cdecl调用约定： <br>规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的&#8220;@@YG&#8221;变为&#8220;@@YA&#8221;。 <br><br>__fastcall调用约定： <br>规则同上面的_stdcall调用约定，只是参数表的开始标识由上面的&#8220;@@YG&#8221;变为&#8220;@@YI&#8221;。 <br>VC++对函数的省缺声明是&#8220;__cedcl&#8220;,将只能被C/C++调用. <br><br>CB在输出函数声明时使用4种修饰符号 <br>//__cdecl <br>cb的默认值，它会在输出函数名前加_，并保留此函数名不变，参数按照从右到左的顺序依次传递给栈，也可以写成_cdecl和cdecl形式。 <br>//__fastcall <br>她修饰的函数的参数将尽肯呢感地使用寄存器来处理，其函数名前加@，参数按照从左到右的顺序压栈； <br>//__pascal <br>它说明的函数名使用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的顺序压栈； <br>//__stdcall <br>使用标准约定的函数名。函数名不会改变。使用__stdcall修饰时。参数按照由右到左的顺序压栈，也可以是_stdcall；</p>
<p style="TEXT-INDENT: 2em">VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用. </p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">&nbsp;</p>
<p style="TEXT-INDENT: 2em">注意：</p>
<p style="TEXT-INDENT: 2em">1、_beginthread需要__cdecl的线程函数地址，_beginthreadex和CreateThread需要__stdcall的线程函数地址。</p>
<p style="TEXT-INDENT: 2em">2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义：</p>
<p style="TEXT-INDENT: 2em">&nbsp;#define CALLBACK __stdcall</p>
<p style="TEXT-INDENT: 2em">&nbsp;#define WINAPI　 __stdcall</p>
<p style="TEXT-INDENT: 2em">3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);</p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp; typedef int (__cdecl*FunPointer)(int a, int b);</p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp; 修饰符的书写顺序如上。</p>
<p style="TEXT-INDENT: 2em">4、extern "C"的作用：如果Add(int a, int b)是在c语言编译器编译，而在c++文件使用，则需要在c++文件中声明：extern "C" Add(int a, int b)，因为c编译器和c++编译器对函数名的解释不一样（c++编译器解释函数名的时候要考虑函数参数，这样是了方便函数重载，而在c语言中不存在函数重载的问题），使用extern "C"，实质就是告诉c++编译器，该函数是c库里面的函数。如果不使用extern "C"则会出现链接错误。</p>
<p style="TEXT-INDENT: 2em">一般象如下使用：</p>
<p style="TEXT-INDENT: 2em">#ifdef _cplusplus </p>
<p style="TEXT-INDENT: 2em">#define EXTERN_C extern "C"</p>
<p style="TEXT-INDENT: 2em">#else</p>
<p style="TEXT-INDENT: 2em">#define EXTERN_C extern</p>
<p style="TEXT-INDENT: 2em">#endif</p>
<p style="TEXT-INDENT: 2em">#ifdef _cplusplus </p>
<p style="TEXT-INDENT: 2em">extern "C"{</p>
<p style="TEXT-INDENT: 2em">#endif </p>
<p style="TEXT-INDENT: 2em">&nbsp;EXTERN_C int func(int a, int b); </p>
<p style="TEXT-INDENT: 2em">#ifdef _cplusplus </p>
<p style="TEXT-INDENT: 2em">} </p>
<p style="TEXT-INDENT: 2em">#endif</p>
<p style="TEXT-INDENT: 2em">5、MFC提供了一些宏，可以使用AFX_EXT_CLASS来代替__declspec(DLLexport)，并修饰类名，从而导出类，AFX_API_EXPORT来修饰函数，AFX_DATA_EXPORT来修饰变量</p>
<p style="TEXT-INDENT: 2em">AFX_CLASS_IMPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_API_IMPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_DATA_IMPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_CLASS_EXPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_API_EXPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_DATA_EXPORT：__declspec(DLLexport)</p>
<p style="TEXT-INDENT: 2em">AFX_EXT_CLASS：#ifdef _AFXEXT </p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;AFX_CLASS_EXPORT</p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; #else</p>
<p style="TEXT-INDENT: 2em">&nbsp;&nbsp;&nbsp;AFX_CLASS_IMPORT</p>
<p style="TEXT-INDENT: 2em">6、DLLMain负责初始化(Initialization)和结束(Termination)工作，每当一个新的进程或者该进程的新的线程访问DLL时，或者访问DLL的每一个进程或者线程不再使用DLL或者结束时，都会调用DLLMain。但是，使用TerminateProcess或TerminateThread结束进程或者线程，不会调用DLLMain。</p>
<p style="TEXT-INDENT: 2em">7、一个DLL在内存中只有一个实例</p>
<p style="TEXT-INDENT: 2em">DLL程序和调用其输出函数的程序的关系：</p>
<p style="TEXT-INDENT: 2em">1)、DLL与进程、线程之间的关系</p>
<p style="TEXT-INDENT: 2em">DLL模块被映射到调用它的进程的虚拟地址空间。</p>
<p style="TEXT-INDENT: 2em">DLL使用的内存从调用进程的虚拟地址空间分配，只能被该进程的线程所访问。</p>
<p style="TEXT-INDENT: 2em">DLL的句柄可以被调用进程使用；调用进程的句柄可以被DLL使用。</p>
<p style="TEXT-INDENT: 2em">DLLDLL可以有自己的数据段，但没有自己的堆栈，使用调用进程的栈，与调用它的应用程序相同的堆栈模式。</p>
<p style="TEXT-INDENT: 2em">2)、关于共享数据段</p>
<p style="TEXT-INDENT: 2em">DLL定义的全局变量可以被调用进程访问；DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量，则需要使用同步机制；对一个DLL的变量，如果希望每个使用DLL的线程都有自己的值，则应该使用线程局部存储(TLS，Thread Local Strorage)。<br></p>
<p><br><br></p>
<h2><strong>论函数调用约定</strong></h2>
<p><br>在C语言中，假设我们有这样的一个函数：<br>　　<br>　　int function(int a,int b)<br>　　<br>　　调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是，当高级语言被编译成计算机可以识别的机器码时，有一个问题就凸现出来：在CPU中，计算机没有办法知道一个函数调用需要多少个、什么样的参数，也没有硬件可以保存这些参数。也就是说，计算机不知道怎么给这个函数传递参数，传递参数的工作必须由函数调用者和函数本身来协调。为此，计算机提供了一种被称为栈的数据结构来支持参数传递。</p>
<p>　　栈是一种先进后出的数据结构，栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项（被称为栈顶）。用户可以在栈顶上方向栈中加入数据，这个操作被称为压栈(Push)，压栈以后，栈顶自动变成新加入数据项的位置，栈顶指针也随之修改。用户也可以从堆栈中取走栈顶，称为弹出栈(pop)，弹出栈后，栈顶下的一个元素变成栈顶，栈顶指针随之修改。</p>
<p>　　函数调用时，调用者依次把参数压栈，然后调用函数，函数被调用以后，在堆栈中取得数据，并进行计算。函数计算结束以后，或者调用者、或者函数本身修改堆栈，使堆栈恢复原装。</p>
<p>　　在参数传递中，有两个很重要的问题必须得到明确说明：<br>　　<br>　　当参数个数多于一个时，按照什么顺序把参数压入堆栈 <br>　　函数调用后，由谁来把堆栈恢复原装 <br>　　在高级语言中，通过函数调用约定来说明这两个问题。常见的调用约定有： </p>
<p>　　stdcall <br>　　cdecl <br>　　fastcall <br>　　thiscall <br>　　naked call</p>
<p>&#160;</p>
<p>　　stdcall调用约定<br>　　stdcall很多时候被称为pascal调用约定，因为pascal是早期很常见的一种教学用计算机程序设计语言，其语法严谨，使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中，常常用PASCAL宏来声明这个调用约定，类似的宏还有WINAPI和CALLBACK。</p>
<p>　　stdcall调用约定声明的语法为(以前文的那个函数为例）：<br>　　<br>　　int __stdcall function(int a,int b)<br>　　<br>　　stdcall的调用约定意味着：1）参数从右向左压入堆栈，2）函数自身修改堆栈 3)函数名自动加前导的下划线，后面紧跟一个@符号，其后紧跟着参数的尺寸</p>
<p>　　以上述这个函数为例，参数b首先被压栈，然后是参数a，函数调用function(1,2)调用处翻译成汇编语言将变成：</p>
<p>　　push 2　　　　　　　 第二个参数入栈<br>　　push 1　　　　　　　 第一个参数入栈<br>　　call function　　　　调用参数，注意此时自动把cs:eip入栈</p>
<p>　　而对于函数自身，则可以翻译为： <br>　　push ebp　　　　　　 保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复<br>　　mov　ebp, esp　　　　保存堆栈指针<br>　　mov　eax,[ebp + 8H]　堆栈中ebp指向位置之前依次保存有ebp, cs:eip, a, b, ebp +8指向a<br>　　add　eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b<br>　　mov　esp, ebp　　　　恢复esp<br>　　pop　ebp<br>　　ret　8</p>
<p>　　而在编译时，这个函数的名字被翻译成<a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#95;&#102;&#117;&#110;&#99;&#116;&#105;&#111;&#110;&#64;&#56;"><font color=#336699>_function@8</font></a> </p>
<p>　　注意不同编译器会插入自己的汇编代码以提供编译的通用性，但是大体代码如此。其中在函数开始处保留esp到ebp中，在函数结束恢复是编译器常用的方法。</p>
<p>　　从函数调用看，2和1依次被push进堆栈，而在函数中又通过相对于ebp(即刚进函数时的堆栈指针）的偏移量存取参数。函数结束后，ret 8表示清理8个字节的堆栈，函数自己恢复了堆栈。</p>
<p>　　<br>　　cdecl调用约定<br>　　cdecl调用约定又称为C调用约定，是C语言缺省的调用约定，它的定义语法是：</p>
<p>　　int function (int a ,int b)　//不加修饰就是C调用约定<br>　　int __cdecl function(int a,int b)//明确指出C调用约定</p>
<p>　　在写本文时，出乎我的意料，发现cdecl调用约定的参数压栈顺序是和stdcall是一样的，参数首先由右向左压入堆栈。所不同的是，函数本身不清理堆栈，调用者负责清理堆栈。由于这种变化，C调用约定允许函数的参数的个数是不固定的，这也是C语言的一大特色。对于前面的function函数，使用cdecl后的汇编码变成： </p>
<p>　　调用处<br>　　push 1<br>　　push 2<br>　　call function<br>　　add　esp, 8　　　　　注意：这里调用者在恢复堆栈</p>
<p>　　被调用函数_function处<br>　　push ebp　　　　　　 保存ebp寄存器，该寄存器将用来保存堆栈的栈顶指针，可以在函数退出时恢复<br>　　mov　ebp,esp　　　　 保存堆栈指针<br>　　mov　eax,[ebp + 8H]　堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a<br>　　add　eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b<br>　　mov　esp,ebp　　　　 恢复esp<br>　　pop　ebp<br>　　ret　　　　　　　　　注意，这里没有修改堆栈</p>
<p>　　MSDN中说，该修饰自动在函数名前加前导的下划线，因此函数名在符号表中被记录为_function，但是我在编译时似乎没有看到这种变化。</p>
<p>　　由于参数按照从右向左顺序压栈，因此最开始的参数在最接近栈顶的位置，因此当采用不定个数参数时，第一个参数在栈中的位置肯定能知道，只要不定的参数个数能够根据第一个后者后续的明确的参数确定下来，就可以使用不定参数，例如对于CRT中的sprintf函数，定义为： <br>　　int sprintf(char* buffer,const char* format,...)<br>　　由于所有的不定参数都可以通过format确定，因此使用不定个数的参数是没有问题的。</p>
<p>　　fastcall<br>　　fastcall调用约定和stdcall类似，它意味着： <br>　　<br>　　函数的第一个和第二个DWORD参数（或者尺寸更小的）通过ecx和edx传递，其他参数通过从右向左的顺序压栈 <br>　　被调用函数清理堆栈 <br>　　函数名修改规则同stdcall <br>　　其声明语法为：int fastcall function(int a, int b)</p>
<p>　　thiscall<br>　　thiscall是唯一一个不能明确指明的函数修饰，因为thiscall不是关键字。它是C++类成员函数缺省的调用约定。由于成员函数调用还有一个this指针，因此必须特殊处理，thiscall意味着： </p>
<p>　　参数从右向左入栈 <br>　　如果参数个数确定，this指针通过ecx传递给被调用者；如果参数个数不确定，this指针在所有参数压栈后被压入堆栈。对参数个数不定的，调用者清理堆栈，否则函数自己清理堆栈为了说明这个调用约定，定义如下类和使用代码： </p>
<p>　　class A<br>　　{<br>　　public:<br>　　　 int function1(int a,int b);<br>　　　 int function2(int a,...);<br>　　};</p>
<p>　　int A::function1 (int a,int b)<br>　　{<br>　　　 return a+b;<br>　　}</p>
<p>　　#include &lt;stdarg.h&gt;<br>　　int A::function2(int a,...)<br>　　{<br>　　　 va_list ap;<br>　　　 va_start(ap,a);<br>　　　 int i;<br>　　　 int result = 0;<br>　　　 for(i = 0 ; i &lt; a ; i ++)<br>　　　 {<br>　　　　　result += va_arg(ap,int);<br>　　　 }<br>　　　 return result;<br>　　}</p>
<p>　　void callee()<br>　　{<br>　　　 A a;<br>　　　 a.function1(1, 2);<br>　　　 a.function2(3, 1, 2, 3);<br>　　}</p>
<p>callee函数被翻译成汇编后就变成： <br>　　//函数function1调用<br>　　00401C1D　 push　　　　2<br>　　00401C1F　 push　　　　1<br>　　00401C21　 lea　　　　 ecx,[ebp-8]<br>　　00401C24　 call　　　　function1　　　　　注意，这里this没有被入栈</p>
<p>　　//函数function2调用<br>　　00401C29　 push　　　　3<br>　　00401C2B　 push　　　　2<br>　　00401C2D　 push　　　　1<br>　　00401C2F　 push　　　　3<br>　　00401C31　 lea　　　　 eax, [ebp-8]　　　 这里引入this指针<br>　　00401C34　 push　　　　eax<br>　　00401C35　 call　　　　function2<br>　　00401C3A　 add　　　　 esp, 14h<br>　　<br>　　可见，对于参数个数固定情况下，它类似于stdcall，不定时则类似cdecl</p>
<p>　　naked call<br>　　这是一个很少见的调用约定，一般程序设计者建议不要使用。编译器不会给这种函数增加初始化和清理代码，更特殊的是，你不能用return返回返回值，只能用插入汇编返回结果。这一般用于实模式驱动程序设计，假设定义一个求和的加法程序，可以定义为： </p>
<p>　　__declspec(naked) int　add(int a,int b)<br>　　{<br>　　　 __asm mov eax,a<br>　　　 __asm add eax,b<br>　　　 __asm ret <br>　　}</p>
<p>　　注意，这个函数没有显式的return返回值，返回通过修改eax寄存器实现，而且连退出函数的ret指令都必须显式插入。上面代码被翻译成汇编以后变成： </p>
<p>　　mov eax,[ebp+8]<br>　　add eax,[ebp+12]<br>　　ret 8</p>
<p>　 注意这个修饰是和__stdcall及cdecl结合使用的，前面是它和cdecl结合使用的代码，对于和stdcall结合的代码，则变成： </p>
<p>　　__declspec(naked) int __stdcall function(int a,int b)<br>　 {<br>　　　　__asm mov eax,a<br>　　　　__asm add eax,b<br>　　　　__asm ret 8　　　　//注意后面的8<br>　　}</p>
<p>　　至于这种函数被调用，则和普通的cdecl及stdcall调用函数一致。</p>
<p>　　函数调用约定导致的常见问题<br>　　如果定义的约定和使用的约定不一致，则将导致堆栈被破坏，导致严重问题，下面是两种常见的问题： </p>
<p>　　函数原型声明和函数体定义不一致 <br>　　DLL导入函数时声明了不同的函数约定 <br>　　以后者为例，假设我们在dll种声明了一种函数为： </p>
<p>　　__declspec(dllexport) int func(int a,int b);//注意，这里没有stdcall，使用的是cdecl<br>　　使用时代码为： </p>
<p>　　typedef int (*WINAPI DLLFUNC)func(int a,int b);<br>　　hLib = LoadLibrary(...);</p>
<p>　　DLLFUNC func = (DLLFUNC)GetProcAddress(...)//这里修改了调用约定<br>　　result = func(1,2);//导致错误</p>
<p>　　由于调用者没有理解WINAPI的含义错误的增加了这个修饰，上述代码必然导致堆栈被破坏，MFC在编译时插入的checkesp函数将告诉你，堆栈被破坏</p>
<p><br><br><br><br><br><br><br><br>&nbsp;</p>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/83062.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-05-15 17:30 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/05/15/83062.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>如何创建自己的dll</title><link>http://www.cppblog.com/zjl-1026-2001/archive/2009/04/28/81304.html</link><dc:creator>沙漠里的海豚</dc:creator><author>沙漠里的海豚</author><pubDate>Tue, 28 Apr 2009 04:10:00 GMT</pubDate><guid>http://www.cppblog.com/zjl-1026-2001/archive/2009/04/28/81304.html</guid><wfw:comment>http://www.cppblog.com/zjl-1026-2001/comments/81304.html</wfw:comment><comments>http://www.cppblog.com/zjl-1026-2001/archive/2009/04/28/81304.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/zjl-1026-2001/comments/commentRss/81304.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/zjl-1026-2001/services/trackbacks/81304.html</trackback:ping><description><![CDATA[<pre>前几天有个朋友问道这个问题，结果因为以前从没搞过这个，对vs2005也不熟悉，竟花了2个小时才搞定，<img height=20 src="http://www.cppblog.com/Emoticons/QQ/07.gif" width=20 border=0>。</pre>
<pre>特地拿来与大家分享，希望能给像我这样的菜鸟们一些帮助，O(&#8745;_&#8745;)O</pre>
<pre><span style="COLOR: #ff0000">【第一步】创建自己的dll</span></pre>
<pre>1.打开vs2005，选择菜单【File-New-Project】，在弹出对话框中选择[Visual C++]下的[Win32]-[Win32 Console Application]，输入工程名后确认。</pre>
<pre>2.在弹出的对话框中选择[next]，在Application Settiongs中选择Application type为Dll，Additional options选择Empty project，然后点Finish。</pre>
<pre>这时就创建了一个空的可以生成dll文件的工程。</pre>
<pre>3.在工程中添加一个头文件(这里为dll_test.h)，在头文件中写入如下内容：</pre>
<pre>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">&nbsp;1</span>&nbsp;<span style="COLOR: #000000">#ifndef&nbsp;_DLL_TUTORIAL_H<br></span><span style="COLOR: #008080">&nbsp;2</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;_DLL-TUTORIAL_H</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;3</span>&nbsp;<span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;4</span>&nbsp;<span style="COLOR: #000000">#include</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">iostream</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;5</span>&nbsp;<span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;6</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#if</span><span style="COLOR: #000000">&nbsp;defined&nbsp;DLL_EXPORT</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;7</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;</span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DECLDIR&nbsp;_declspec(dllexport)</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;8</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#else</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;9</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;</span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DECLDIR&nbsp;_declspec(dllimport)</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">10</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#endif</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">11</span>&nbsp;<span style="COLOR: #000000"><br></span><span style="COLOR: #008080">12</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">extern</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">C</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">13</span>&nbsp;<span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">14</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;DECLDIR int Add(int a, int b);<br>15&nbsp;&nbsp;&nbsp;DECLDIR void Function(void);<br></span><span style="COLOR: #008080">16</span>&nbsp;<span style="COLOR: #000000">}<br>17<br>18 #endif</span></div>
</pre>
<pre>这里要说明的是：</pre>
<pre>在VC中有两个方法来导出dll中定义的函数：</pre>
<pre>  (1) 使用__declspec,这是一个Microsoft定义的关键字。</pre>
<pre>  (2) 创建一个模板定义文件(Module-Definition File，即.DEF)。</pre>
<pre>  第一种方法稍稍比第二种方法简单，在这里我们使用的是第一种方法。</pre>
<pre>    __declspec(dllexport)函数的作用是导出函数符号到在你的Dll中的一个存储类里去。</pre>
<pre>当下面一行被定义时我定义DECLDIR宏来运行这个函数。</pre>
<pre>    #define DLL_EXPORT</pre>
<pre>在此情况下你将导出函数Add(int a,int b)和Function().</pre>
<pre>4.创建一个源文件(名字为dll_test.cpp)，内容如下：</pre>
<pre>&nbsp;
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">&nbsp;1</span>&nbsp;<span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">&lt;</span><span style="COLOR: #000000">iostream</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;2</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">#define</span><span style="COLOR: #000000">&nbsp;DLL_EXPORT</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;3</span>&nbsp;<span style="COLOR: #000000">#include&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">dll_test.h</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;4</span>&nbsp;<span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;5</span>&nbsp;<span style="COLOR: #000000"></span><span style="COLOR: #0000ff">extern</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">C</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;6</span>&nbsp;<span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">&nbsp;7</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">&nbsp;定义了（DLL中的）所有函数</span><span style="COLOR: #008000"><br></span><span style="COLOR: #008080">&nbsp;8</span>&nbsp;<span style="COLOR: #008000"></span><span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;DECLDIR&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;Add(&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;a,&nbsp;</span><span style="COLOR: #0000ff">int</span><span style="COLOR: #000000">&nbsp;b&nbsp;)<br></span><span style="COLOR: #008080">&nbsp;9</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br></span><span style="COLOR: #008080">10</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">(&nbsp;a&nbsp;</span><span style="COLOR: #000000">+</span><span style="COLOR: #000000">&nbsp;b&nbsp;);<br></span><span style="COLOR: #008080">11</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br></span><span style="COLOR: #008080">12</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;<br></span><span style="COLOR: #008080">13</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;DECLDIR&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;Function(&nbsp;</span><span style="COLOR: #0000ff">void</span><span style="COLOR: #000000">&nbsp;)<br></span><span style="COLOR: #008080">14</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;{<br></span><span style="COLOR: #008080">15</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;std::cout&nbsp;</span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">DLL&nbsp;Called!</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">&lt;&lt;</span><span style="COLOR: #000000">&nbsp;std::endl;<br></span><span style="COLOR: #008080">16</span>&nbsp;<span style="COLOR: #000000">&nbsp;&nbsp;&nbsp;&nbsp;}<br></span><span style="COLOR: #008080">17</span>&nbsp;<span style="COLOR: #000000">}<br></span><span style="COLOR: #008080">18</span>&nbsp;<span style="COLOR: #000000"></span></div>
</pre>
<pre><span style="COLOR: #ff0000">【第二步】使用创建好的DLL</span></pre>
<pre>现在已经创建了DLL，那么如何在一个应用程序中使用它呢？</pre>
<pre>当DLL被生成后，它创建了一个.dll文件和一个.lib，这两个都是使用dll时需要用到的。</pre>
<pre>在具体介绍之前先看一下dll的链接方式。</pre>
<pre>(1)隐式连接</pre>
<pre>这里有两个方法来载入一个DLL，一个方法是只链接到.lib文件，并将.dll文件放到要使用这个DLL的项目路径中。</pre>
<pre>因此，创建一个新的空的Win32控制台项目并添加一个源文件。将我们创建好的DLL放入与新项目相同的目录下。同时我们还必须链接到dll_test.lib文件。</pre>
<pre>可以在项目属性中设置，也可以在源程序中用下面的语句来链接：
#pragma comment(lib, "dll_test.lib")</pre>
<pre>最后，我们还要在新的win32控制台项目中包含前面的dll_test.h头文件。可以把这个头文件放到新建win32控制台项目的目录中然后在程序中加入语句：
#include "dll_test.h"</pre>
<pre>新项目代码如下：</pre>
<pre>#include&lt;iostream&gt;</pre>
<pre>#include "DLLTutorial.h"</pre>
<pre>int main()</pre>
<pre>{</pre>
<pre>  Function();</pre>
<pre>  std::cout&lt;&lt; Add(32, 56)&lt;&lt; endl;</pre>
<pre>  return 0;</pre>
<pre>}</pre>
<pre>(2)显示链接</pre>
<pre>稍微复杂一点的加载DLL的方法需要用到函数指针和一些Windows函数。但是，通过这种载入DLL的方法，不需要DLL的.lib文件或头文件，而只需要DLL即可。</pre>
<pre>下面列出一些代码：</pre>
<pre>/****************************************************************/
#include &lt;iostream&gt;
#include &lt;windows.h&gt;
typedef int (*AddFunc)(int,int);
typedef void (*FunctionFunc)();</pre>
<pre>int main()</pre>
<pre>{
AddFunc _AddFunc;</pre>
<pre>   FunctionFunc _FunctionFunc;</pre>
<pre>   HINSTANCE hInstLibrary = LoadLibrary("DLL_Tutorial.dll");</pre>
<pre>   if (hInstLibrary == NULL)
{
FreeLibrary(hInstLibrary);
}</pre>
<pre>   _AddFunc = (AddFunc)GetProcAddress(hInstLibrary, "Add");</pre>
<pre>   _FunctionFunc = (FunctionFunc)GetProcAddress(hInstLibrary, "Function");</pre>
<pre>   if ((_AddFunc == NULL) || (_FunctionFunc == NULL))
{
FreeLibrary(hInstLibrary);
}</pre>
<pre>   std::cout &lt;&lt; _AddFunc(23, 43) &lt;&lt; std::endl;</pre>
<pre>   _FunctionFunc();</pre>
<pre>   std::cin.get();</pre>
<pre>   FreeLibrary(hInstLibrary);</pre>
<pre>   return(1);
}
/*******************************************************************/</pre>
<pre>首先可以看到，这里包括进了windows.h头文件，同时去掉了对dll_test.h头文件的包含。原因很简单：因为windows.h包含了一些Windows函数，</pre>
<pre>它也包含了一些将会用到的Windows特定变量。可以去掉DLL的头文件，因为当使用这个方法载入DLL时并不需要其头文件。</pre>
<pre>下面你会看到：以下面形式的一小块古灵精怪的代码:</pre>
<pre>    typedef int (*AddFunc)(int,int);
typedef void (*FunctionFunc)();</pre>
<pre>    这是函数指针。因为这是一个关于DLL的自学指南，深入探究函数指针超出了本指南的范围；因此，现在我们只把它们当作DLL包含的函数的别名。</pre>
<pre>    我喜欢在尾部用&#8220;Func&#8221;命名之。(int,int)部分是这个函数的参数部分，比如，Add函数要获得两个整数；因此，你需要它们</pre>
<pre>（译注：指(int,int)部分）作为函数指针的参数。Function函数没有参数，因此你让它为空。main()部分中的前面两行是声明函数指针以使得你可</pre>
<pre>以认为它们等同于DLL内部的函数。我只是喜欢预先定义它们。</pre>
<pre>      一个HINSTANCE是一个Windows数据类型：是一个实例的句柄；在此情况下，这个实例将是这个DLL。你可以通过使用函数LoadLibrary()获得DLL的</pre>
<pre>实例，它获得一个名称作为参数。</pre>
<pre>     在调用LoadLibrary函数后，你必需查看一下函数返回是否成功。你可以通过检查HINSTANCE是否等于NULL（在Windows.h中定义为0或Windows.h包</pre>
<pre>含的一个头文件）来查看其是否成功。如果其等于NULL，该句柄将是无效的，并且你必需释放这个库。换句话说，你必需释放DLL获得的内存。</pre>
<pre>      如果函数返回成功，你的HINSTANCE就包含了指向DLL的句柄。一旦你获得了指向DLL的句柄，你现在可以从DLL中重新获得函数。</pre>
<pre>     为了这样作，你必须使用函数GetProcAddress()，它将DLL的句柄（你可以使用HINSTANCE）和函数的名称作为参数。你可以让函数指针获得由</pre>
<pre>GetProcAddress()返回的值，同时你必需将GetProcAddress()转换为那个函数定义的函数指针。举个例子，对于Add()函数，你必需将GetProcAddress()</pre>
<pre>转换为AddFunc；这就是它知道参数及返回值的原因。现在，最好先确定函数指针是否等于NULL以及它们拥有DLL的函数。</pre>
<pre>     这只是一个简单的if语句；如果其中一个等于NULL，你必需如前所述释放库。一旦函数指针拥有DLL的函数，你现在就可以使用它们了，但是这里有一个</pre>
<pre>需要注意的地方：你不能使用函数的实际名称；你必需使用函数指针来调用它们。在那以后，所有你需要做的是释放库如此而已。</pre>
<pre>     现在你知道了DLL的一些基本知识。你知道如何创建它们，你也知道如何用两种不同的方法链接它们。这里仍然有更多的东西需要我们学习，但我把它们留给你们自己探索了和更棒的作者来写了。
</pre>
<img src ="http://www.cppblog.com/zjl-1026-2001/aggbug/81304.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/zjl-1026-2001/" target="_blank">沙漠里的海豚</a> 2009-04-28 12:10 <a href="http://www.cppblog.com/zjl-1026-2001/archive/2009/04/28/81304.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>