﻿<?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++博客-堂吉诃德-文章分类-assembly/disassembly</title><link>http://www.cppblog.com/tdweng/category/14700.html</link><description /><language>zh-cn</language><lastBuildDate>Mon, 11 Oct 2010 08:31:56 GMT</lastBuildDate><pubDate>Mon, 11 Oct 2010 08:31:56 GMT</pubDate><ttl>60</ttl><item><title>堆和栈的区别 (转贴)</title><link>http://www.cppblog.com/tdweng/articles/129415.html</link><dc:creator>心羽</dc:creator><author>心羽</author><pubDate>Mon, 11 Oct 2010 02:45:00 GMT</pubDate><guid>http://www.cppblog.com/tdweng/articles/129415.html</guid><wfw:comment>http://www.cppblog.com/tdweng/comments/129415.html</wfw:comment><comments>http://www.cppblog.com/tdweng/articles/129415.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tdweng/comments/commentRss/129415.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tdweng/services/trackbacks/129415.html</trackback:ping><description><![CDATA[<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 border=0 alt="" src="http://club.5ivb.net/UploadFile/2005311144027byUID16686.gif" onload="javascript:if(this.width>screen.width-333)this.width=screen.width-333" 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 border=0 align=absMiddle src="http://club.5ivb.net/pic/url.gif"><a href="http://www.cs.utexas.edu/users/oops/papers.html" target=_blank><font color=#000000>http://www.cs.utexas.edu/users/oops/papers.html</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 border=0 align=absMiddle src="http://club.5ivb.net/pic/url.gif"><a href="http://msdn.microsoft.com/workshop/server/iis/tencom.asp" target=_blank><font color=#000000>http://msdn.microsoft.com/workshop/server/iis/tencom.asp</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 border=0 align=absMiddle src="http://club.5ivb.net/pic/url.gif"><a href="http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835" target=_blank><font color=#000000>http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835</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>
<img src ="http://www.cppblog.com/tdweng/aggbug/129415.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tdweng/" target="_blank">心羽</a> 2010-10-11 10:45 <a href="http://www.cppblog.com/tdweng/articles/129415.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>汇编常用总结(总结)</title><link>http://www.cppblog.com/tdweng/articles/124507.html</link><dc:creator>心羽</dc:creator><author>心羽</author><pubDate>Tue, 24 Aug 2010 01:02:00 GMT</pubDate><guid>http://www.cppblog.com/tdweng/articles/124507.html</guid><wfw:comment>http://www.cppblog.com/tdweng/comments/124507.html</wfw:comment><comments>http://www.cppblog.com/tdweng/articles/124507.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tdweng/comments/commentRss/124507.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tdweng/services/trackbacks/124507.html</trackback:ping><description><![CDATA[<strong><span style="COLOR: #ff0000">1.mov&nbsp;eax&nbsp;[esi+4]与lea&nbsp;eax&nbsp;&nbsp;[esi+4]</span><br></strong>&nbsp;&nbsp;&nbsp;mov是把esi+4所在地址的值传给eax，也就是把esi+4看成指针，然后eax&nbsp;=&nbsp;*(esi+4)；而lea是把esi+4的值传给eax，也就是eax&nbsp;=&nbsp;esi&nbsp;+&nbsp;4<span style="COLOR: #000000"> </span>
<img src ="http://www.cppblog.com/tdweng/aggbug/124507.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tdweng/" target="_blank">心羽</a> 2010-08-24 09:02 <a href="http://www.cppblog.com/tdweng/articles/124507.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>汇编中各寄存器的作用</title><link>http://www.cppblog.com/tdweng/articles/120852.html</link><dc:creator>心羽</dc:creator><author>心羽</author><pubDate>Tue, 20 Jul 2010 02:16:00 GMT</pubDate><guid>http://www.cppblog.com/tdweng/articles/120852.html</guid><wfw:comment>http://www.cppblog.com/tdweng/comments/120852.html</wfw:comment><comments>http://www.cppblog.com/tdweng/articles/120852.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tdweng/comments/commentRss/120852.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tdweng/services/trackbacks/120852.html</trackback:ping><description><![CDATA[寄存器
<div style="FLOAT: right"><a href="http://imgsrc.baidu.com/baike/pic/item/73ca5910d7918defc3ce79b3.jpg" rel=nofollow target=_blank><img title="" src="http://imgsrc.baidu.com/baike/abpic/item/73ca5910d7918defc3ce79b3.jpg"></a></div>
<div>
<div></div>
　　<strong>英文名称：Register</strong>
<div></div>
<h2><a name=SafeHtmlFilter_1 rel=nofollow></a>寄存器定义</h2>
　　寄存器是<a href="http://baike.baidu.com/view/14045.htm" rel=nofollow target=_blank><font color=#3366cc><u>中央处理器</u></font></a>内的组成部份。<wbr>寄存器是有限存贮容量的高速存贮部件，它们可用来暂存指令、<wbr>数据和位址。在中央处理器的控制部件中，包含的寄存器有<a href="http://baike.baidu.com/view/178142.htm" rel=nofollow target=_blank><font color=#3366cc><u>指令寄存<wbr>器</u></font></a>(IR)和<a href="http://baike.baidu.com/view/178145.htm" rel=nofollow target=_blank><font color=#3366cc><u>程序计数器</u></font></a>(PC)。<wbr>在中央处理器的算术及逻辑部件中，包含的寄存器有<a href="http://baike.baidu.com/view/471649.htm" rel=nofollow target=_blank><font color=#3366cc><u>累加器</u></font></a>(<a href="http://baike.baidu.com/view/331459.htm" rel=nofollow target=_blank><font color=#3366cc><u>ACC</u></font></a><wbr>)。<br>
<div></div>
　　寄存器是内存阶层中的最顶端，<wbr>也是系统获得操作资料的最快速途径。<wbr>寄存器通常都是以他们可以保存的位元数量来估量，举例来说，一个 &#8220;8 位元寄存器&#8221;或 &#8220;32 位元寄存器&#8221;。寄存器现在都以寄存器档案的方式来实作，<wbr>但是他们也可能使用单独的正反器、高速的核心内存、<wbr>薄膜内存以及在数种机器上的其他方式来实作出来。 <br>
<div></div>
　　寄存器通常都用来意指由一个指令之输出或输入可以直接索引到的暂<wbr>存器群组。更适当的是称他们为 &#8220;架构寄存器&#8221;。 <br>
<div></div>
　　例如，x86 指令及定义八个 32 位元寄存器的集合，但一个实作 x86 指令集的 CPU 可以包含比八个更多的寄存器。<br>
<div></div>
　　寄存器是CPU内部的元件，寄存器拥有非常高的读写速度，<wbr>所以在寄存器之间的数据传送非常快。
<div></div>
<h2><a name=SafeHtmlFilter_2 rel=nofollow></a>寄存器用途</h2>
　　1.可将寄存器内的数据执行算术及逻辑运算；<br>
<div></div>
　　2.存于寄存器内的地址可用来指向内存的某个位置，即寻址；<br>
<div></div>
　　3.可以用来读写数据到电脑的周边设备。
<div></div>
<h2><a name=SafeHtmlFilter_3 rel=nofollow></a>数据寄存器</h2>
　　8086 有14个16位寄存器，这14个寄存器按其用途可分为(1)<wbr>通用寄存器、(2)指令指针、(3)标志寄存器和(4)<wbr>段寄存器等4类。<br>
<div></div>
　　<strong>(1)通用寄存器</strong>有8个, 又可以分成2组,一组是数据寄存器(4个),<wbr>另一组是指针寄存器及变址寄存器(4个).<br>
<div></div>
　　数据寄存器分为:<br>
<div></div>
　　<strong>AH&amp;AL＝AX(accumulator)：</strong>累加寄存器，<wbr>常用于运算;在乘除等指令中指定用来存放操作数,另外,<wbr>所有的I/O指令都使用这一寄存器与外界设备传送数据.<br>
<div></div>
　　<strong>BH&amp;BL＝BX(base)：</strong>基址寄存器，常用于地址索引；<br>
<div></div>
　　<strong>CH&amp;CL＝CX(count)：</strong>计数寄存器，常用于计数；<wbr>常用于保存计算值,如在移位指令,循环(loop)<wbr>和串处理指令中用作隐含的计数器.<br>
<div></div>
　　<strong>DH&amp;DL＝DX(data)：</strong>数据寄存器，常用于数据传递。<br>
<div></div>
　　他们的特点是,这4个16位的寄存器可以分为高8位: AH, BH, CH, DH.以及低八位：AL,BL,CL,DL。<wbr>这2组8位寄存器可以分别寻址，并单独使用。<br>
<div></div>
　　另一组是指针寄存器和变址寄存器，包括：<br>
<div></div>
　　<strong>SP（Stack Pointer）：</strong>堆栈指针，与SS配合使用，<wbr>可指向目前的堆栈位置；<br>
<div></div>
　　<strong>BP（Base Pointer）：</strong>基址指针寄存器，<wbr>可用作SS的一个相对基址位置；<br>
<div></div>
　　<strong>SI（Source Index）：</strong>源变址寄存器可用来存放相对于DS段之源变址指针<wbr>；<br>
<div></div>
　　<strong>DI（Destination Index）：</strong>目的变址寄存器，可用来存放相对于 ES 段之目的变址指针。<br>
<div></div>
　　这4个16位寄存器只能按16位进行存取操作，<wbr>主要用来形成操作数的地址，<wbr>用于堆栈操作和变址运算中计算操作数的有效地址。<br>
<div></div>
　　(<strong>2) 指令指针IP(Instruction Pointer)</strong><br>
<div></div>
　　指令指针IP是一个16位专用寄存器，<wbr>它指向当前需要取出的指令字节，<wbr>当BIU从内存中取出一个指令字节后，IP就自动加1，<wbr>指向下一个指令字节。注意，<wbr>IP指向的是指令地址的段内地址偏移量，又称偏移地址(<wbr>Offset Address)或有效地址(EA，Effective Address)。<br>
<div></div>
　　<strong>(3)标志寄存器FR(Flag Register)<br>
<div></div>
　　</strong>8086有一个18位的标志寄存器FR，<wbr>在FR中有意义的有9位，其中6位是状态位，3位是控制位。<br>
<div></div>
　　<strong>OF：</strong> 溢出标志位OF用于反映有符号数加减运算所得结果是否溢出。<wbr>如果运算结果超过当前运算位数所能表示的范围，则称为溢出，<wbr>OF的值被置为1，否则，OF的值被清为0。<br>
<div></div>
　　<strong>DF：</strong>方向标志DF位用来决定在串操作指令执行时有关指针寄存器<wbr>发生调整的方向。 <br>
<div></div>
　　<strong>IF：</strong>中断允许标志IF位用来决定CPU是否响应CPU外部的可<wbr>屏蔽中断发出的中断请求。但不管该标志为何值，<wbr>CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求，<wbr>以及CPU内部产生的中断请求。具体规定如下： <br>
<div></div>
　　(1)、当IF=1时，<wbr>CPU可以响应CPU外部的可屏蔽中断发出的中断请求； <br>
<div></div>
　　(2)、当IF=0时，<wbr>CPU不响应CPU外部的可屏蔽中断发出的中断请求。 <br>
<div></div>
　　<strong>TF：</strong>跟踪标志TF。该标志可用于程序调试。<wbr>TF标志没有专门的指令来设置或清楚。<br>
<div></div>
　　（1）如果TF=1，则CPU处于单步执行指令的工作方式，<wbr>此时每执行完一条指令，<wbr>就显示CPU内各个寄存器的当前值及CPU将要执行的下一条指令<wbr>。<br>
<div></div>
　　（2）如果TF=0，则处于连续工作模式。<br>
<div></div>
　　<strong>SF：</strong>符号标志SF用来反映运算结果的符号位，<wbr>它与运算结果的最高位相同。在微机系统中，<wbr>有符号数采用补码表示法，所以，SF也就反映运算结果的正负号。<wbr>运算结果为正数时，SF的值为0，否则其值为1。 <br>
<div></div>
　　<strong>ZF：</strong> 零标志ZF用来反映运算结果是否为0。如果运算结果为0，<wbr>则其值为1，否则其值为0。在判断运算结果是否为0时，<wbr>可使用此标志位。 <br>
<div></div>
　　<strong>AF：</strong>下列情况下，辅助进位标志AF的值被置为1，<wbr>否则其值为0： <br>
<div></div>
　　(1)、在字操作时，发生低字节向高字节进位或借位时； <br>
<div></div>
　　(2)、在字节操作时，发生低4位向高4位进位或借位时。 <br>
<div></div>
　　<strong>PF：</strong>奇偶标志PF用于反映运算结果中&#8220;1&#8221;的个数的奇偶性。<wbr>如果&#8220;1&#8221;的个数为偶数，则PF的值为1，否则其值为0。 <br>
<div></div>
　　<strong>CF：</strong>进位标志CF主要用来反映运算是否产生进位或借位。<wbr>如果运算结果的最高位产生了一个进位或借位，那么，其值为1，<wbr>否则其值为0。) <br>
<div></div>
　　<strong>4)段寄存器(Segment Register)</strong><br>
<div></div>
　　为了运用所有的内存空间，8086设定了四个段寄存器，<wbr>专门用来保存段地址：<br>
<div></div>
　　<strong>CS（Code Segment）：</strong>代码段寄存器；<br>
<div></div>
　　<strong>DS（Data Segment）：</strong>数据段寄存器；<br>
<div></div>
　　<strong>SS（Stack Segment）：</strong>堆栈段寄存器；<br>
<div></div>
　　<strong>ES（Extra Segment）：</strong>附加段寄存器。<br>
<div></div>
　　当一个程序要执行时，就要决定程序代码、<wbr>数据和堆栈各要用到内存的哪些位置，通过设定段寄存器 CS，DS，SS 来指向这些起始位置。通常是将DS固定，而根据需要修改CS。<wbr>所以，程序可以在可寻址空间小于64K的情况下被写成任意大小。 所以，程序和其数据组合起来的大小，限制在DS 所指的64K内，这就是COM文件不得大于64K的原因。<wbr>8086以内存做为战场，用寄存器做为军事基地，以加速工作。<br>
<div></div>
　　以上是8086寄存器的整体概况, 自80386开始，PC进入32bit时代，其寻址方式，<wbr>寄存器大小, 功能等都发生了变化：<br>
<div></div>
　　=============================<wbr>以下是80386的寄存器的一些资料=============<wbr>=========================<br>
<div></div>
　　寄存器都是32-bits宽。<br>
<div></div>
　　A、通用寄存器 <br>
<div></div>
　　下面介绍通用寄存器及其习惯用法。顾名思义，<wbr>通用寄存器是那些你可以根据自己的意愿使用的寄存器，<wbr>修改他们的值通常不会对计算机的运行造成很大的影响。<wbr>通用寄存器最多的用途是计算。 <br>
<div></div>
　　EAX：通用寄存器。相对其他寄存器，在进行运算方面比较常用。<wbr>在保护模式中，也可以作为内存偏移指针（此时，DS作为段 寄存器或选择器） <br>
<div></div>
　　EBX：通用寄存器。通常作为内存偏移指针使用（相对于EAX、<wbr>ECX、EDX），DS是默认的段寄存器或选择器。<wbr>在保护模式中，同样可以起这个作用。 <br>
<div></div>
　　ECX：通用寄存器。通常用于特定指令的计数。在保护模式中，<wbr>也可以作为内存偏移指针（此时，DS作为 寄存器或段选择器）。<br>
<div></div>
　　EDX：通用寄存器。在某些运算中作为EAX的溢出寄存器（<wbr>例如乘、除）。在保护模式中，也可以作为内存偏移指针（此时，<wbr>DS作为段 寄存器或选择器）。 <br>
<div></div>
　　同AX分为AH&amp;AL一样，上述寄存器包括对应的16-<wbr>bit分组和8-bit分组。 <br>
<div></div>
　　B、用作内存指针的特殊寄存器<br>
<div></div>
　　ESI：通常在内存操作指令中作为&#8220;源地址指针&#8221;使用。当然，<wbr>ESI可以被装入任意的数值，<wbr>但通常没有人把它当作通用寄存器来用。<wbr>DS是默认段寄存器或选择器。 <br>
<div></div>
　　EDI：通常在内存操作指令中作为&#8220;目的地址指针&#8221;使用。当然，<wbr>EDI也可以被装入任意的数值，<wbr>但通常没有人把它当作通用寄存器来用。<wbr>DS是默认段寄存器或选择器。 <br>
<div></div>
　　EBP：这也是一个作为指针的寄存器。通常，<wbr>它被高级语言编译器用以建造&#8216;堆栈帧'<wbr>来保存函数或过程的局部变量，不过，还是那句话，<wbr>你可以在其中保存你希望的任何数据。<wbr>SS是它的默认段寄存器或选择器。 <br>
<div></div>
　　注意，这三个寄存器没有对应的8-bit分组。换言之，<wbr>你可以通过SI、DI、BP作为别名访问他们的低16位，<wbr>却没有办法直接访问他们的低8位。 <br>
<div></div>
　　C、段选择器：<br>
<div></div>
　　实模式下的段寄存器到保护模式下摇身一变就成了选择器。<wbr>不同的是，实模式下的&#8220;段寄存器&#8221;是16-bit的，<wbr>而保护模式下的选择器是32-bit的。 <br>
<div></div>
　　CS 代码段，或代码选择器。同IP寄存器(稍后介绍)<wbr>一同指向当前正在执行的那个地址。<wbr>处理器执行时从这个寄存器指向的段（实模式）或内存（保护模式）<wbr>中获取指令。除了跳转或其他分支指令之外，<wbr>你无法修改这个寄存器的内容。 <br>
<div></div>
　　DS 数据段，或数据选择器。这个寄存器的低16 bit连同ESI一同指向的指令将要处理的内存。同时，<wbr>所有的内存操作指令 默认情况下都用它指定操作段(实模式)或内存(作为选择器，<wbr>在保护模式。这个寄存器可以被装入任意数值，<wbr>然而在这么做的时候需要小心一些。方法是，首先把数据送给AX，<wbr>然后再把它从AX传送给DS(当然，也可以通过堆栈来做). <br>
<div></div>
　　ES 附加段，或附加选择器。这个寄存器的低16 bit连同EDI一同指向的指令将要处理的内存。同样的，<wbr>这个寄存器可以被装入任意数值，方法和DS类似。 <br>
<div></div>
　　FS F段或F选择器(推测F可能是Free?)。<wbr>可以用这个寄存器作为默认段寄存器或选择器的一个替代品。<wbr>它可以被装入任何数值，方法和DS类似。 <br>
<div></div>
　　GS G段或G选择器(G的意义和F一样，<wbr>没有在Intel的文档中解释)。它和FS几乎完全一样。 <br>
<div></div>
　　SS 堆栈段或堆栈选择器。这个寄存器的低16 bit连同ESP一同指向下一次堆栈操作(push和pop)<wbr>所要使用的堆栈地址。这个寄存器也可以被装入任意数值，<wbr>你可以通过入栈和出栈操作来给他赋值，<wbr>不过由于堆栈对于很多操作有很重要的意义，因此，<wbr>不正确的修改有可能造成对堆栈的破坏。 <br>
<div></div>
　　* 注意 一定不要在初学汇编的阶段把这些寄存器弄混。他们非常重要，<wbr>而一旦你掌握了他们，你就可以对他们做任意的操作了。段寄存器，<wbr>或选择器，在没有指定的情况下都是使用默认的那个。<wbr>这句话在现在看来可能有点稀里糊涂，<wbr>不过你很快就会在后面知道如何去做。 <br>
<div></div>
　　指令指针寄存器：<br>
<div></div>
　　EIP 这个寄存器非常的重要。这是一个32位宽的寄存器 ，同CS一同指向即将执行的那条指令的地址。<wbr>不能够直接修改这个寄存器的值，<wbr>修改它的唯一方法是跳转或分支指令。(CS是默认的段或选择器) <br>
<div></div>
　　上面是最基本的寄存器。下面是一些其他的寄存器，<wbr>你甚至可能没有听说过它们。(都是32位宽)：<br>
<div></div>
　　CR0, CR2, CR3(控制寄存器)。举一个例子，<wbr>CR0的作用是切换实模式和保护模式。 <br>
<div></div>
　　还有其他一些寄存器，D0, D1, D2, D3, D6和D7(调试寄存器)。<wbr>他们可以作为调试器的硬件支持来设置条件断点。 <br>
<div></div>
　　TR3, TR4, TR5, TR6 和 TR? 寄存器(测试寄存器)用于某些条件测试</div>
<p>最近在学汇编，看到这篇文章，文章的原出处已经查不出来了，但觉得不错，所以转出来，当作备份学习。</p>
<p>4个数据寄存器(EAX、EBX、ECX和EDX)<br>2个变址和指针寄存器(ESI和EDI) 2个指针寄存器(ESP和EBP)<br>6个段寄存器(ES、CS、SS、DS、FS和GS)<br>1个指令指针寄存器(EIP) 1个标志寄存器(EFlags)</p>
<p>1、数据寄存器<br>数据寄存器主要用来保存操作数和运算结果等信息，从而节省读取操作数所需占用总线和访问存储器的时间。32位CPU有4个32位的通用寄存器EAX、EBX、ECX和EDX。对低16位数据的存取，不会影响高16位的数据。这些低16位寄存器分别命名为：AX、BX、CX和DX，它和先前的CPU中的寄存器相一致。</p>
<p>4个16位寄存器又可分割成8个独立的8位寄存器(AX：AH-AL、BX：BH-BL、CX：CH-CL、DX：DH-DL)，每个寄存器都有自己的名称，可独立存取。程序员可利用数据寄存器的这种&#8221;可分可合&#8221;的特性，灵活地处理字/字节的信息。<br>寄存器AX和AL通常称为累加器(Accumulator)，用累加器进行的操作可能需要更少时间。累加器可用于乘、 除、输入/输出等操作，它们的使用频率很高； 寄存器BX称为基地址寄存器(Base Register)。它可作为存储器指针来使用； 寄存器CX称为计数寄存器(Count Register)。在循环和字符串操作时，要用它来控制循环次数；在位操作 中，当移多位时，要用CL来指明移位的位数；<br>寄存器DX称为数据寄存器(Data Register)。在进行乘、除运算时，它可作为默认的操作数参与运算，也可用于存放I/O的端口地址。在16位CPU中，AX、BX、CX和DX不能作为基址和变址寄存器来存放存储单元的地址，但在32位CPU中，其32位寄存器EAX、EBX、ECX和EDX不仅可传送数据、暂存数据保存算术逻辑运算结果，而且也可作为指针寄存器，所以，这些32位寄存器更具有通用性。<br>2、变址寄存器<br>32位CPU有2个32位通用寄存器ESI和EDI。其低16位对应先前CPU中的SI和DI，对低16位数据的存取，不影响高16位的数据。<br>寄存器ESI、EDI、SI和DI称为变址寄存器(Index Register)，它们主要用于存放存储单元在段内的偏移量，用它们可实现多种存储器操作数的寻址方式，为以不同的地址形式访问存储单元提供方便。变址寄存器不可分割成8位寄存器。作为通用寄存器，也可存储算术逻辑运算的操作数和运算结果。它们可作一般的存储器指针使用。在字符串操作指令的执行过程中，对它们有特定的要求，而且还具有特殊的功能。<br>3、指针寄存器<br>32位CPU有2个32位通用寄存器EBP和ESP。其低16位对应先前CPU中的SBP和SP，对低16位数据的存取，不影响高16位的数据。<br>寄存器EBP、ESP、BP和SP称为指针寄存器(Pointer Register)，主要用于存放堆栈内存储单元的偏移量，用它们可实现多种存储器操作数的寻址方式，为以不同的地址形式访问存储单元提供方便。指针寄存器不可分割成8位寄存器。作为通用寄存器，也可存储算术逻辑运算的操作数和运算结果。<br>它们主要用于访问堆栈内的存储单元，并且规定：<br>BP为基指针(Base Pointer)寄存器，用它可直接存取堆栈中的数据；<br>SP为堆栈指针(Stack Pointer)寄存器，用它只可访问栈顶。<br>4、段寄存器<br>段寄存器是根据内存分段的管理模式而设置的。内存单元的物理地址由段寄存器的值和一个偏移量组合而成<br>的，这样可用两个较少位数的值组合成一个可访问较大物理空间的内存地址。<br>CPU内部的段寄存器：<br>CS——代码段寄存器(Code Segment Register)，其值为代码段的段值；<br>DS——数据段寄存器(Data Segment Register)，其值为数据段的段值；<br>ES——附加段寄存器(Extra Segment Register)，其值为附加数据段的段值；<br>SS——堆栈段寄存器(Stack Segment Register)，其值为堆栈段的段值；<br>FS——附加段寄存器(Extra Segment Register)，其值为附加数据段的段值；<br>GS——附加段寄存器(Extra Segment Register)，其值为附加数据段的段值。</p>
<p>在16位CPU系统中，它只有4个段寄存器，所以，程序在任何时刻至多有4个正在使用的段可直接访问；在32位微机系统中，它有6个段寄存器，所以，在此环境下开发的程序最多可同时访问6个段。32位CPU有两个不同的工作方式：实方式和保护方式。在每种方式下，段寄存器的作用是不同的。有关规定简单描述如下：<br>实方式： 前4个段寄存器CS、DS、ES和SS与先前CPU中的所对应的段寄存器的含义完全一致，内存单元的逻辑地址仍为&#8221;段值：偏移量&#8221;的形式。为访问某内存段内的数据，必须使用该段寄存器和存储单元的偏移量。<br>保护方式： 在此方式下，情况要复杂得多，装入段寄存器的不再是段值，而是称为&#8221;选择子&#8221;(Selector)的某个值。<br>5、指令指针寄存器<br>32位CPU把指令指针扩展到32位，并记作EIP，EIP的低16位与先前CPU中的IP作用相同。<br>指令指针EIP、IP(Instruction Pointer)是存放下次将要执行的指令在代码段的偏移量。在具有预取指令功能的系统中，下次要执行的指令通常已被预取到指令队列中，除非发生转移情况。所以，在理解它们的功能时，不考虑存在指令队列的情况。<br>在实方式下，由于每个段的最大范围为64K，所以，EIP中的高16位肯定都为0，此时，相当于只用其低16位的IP来反映程序中指令的执行次序。<br>6、标志寄存器<br>一、运算结果标志位<br>1、进位标志CF(Carry Flag)<br>进位标志CF主要用来反映运算是否产生进位或借位。如果运算结果的最高位产生了一个进位或借位，那么，其值为1，否则其值为0。使用该标志位的情况有：多字(字节)数的加减运算，无符号数的大小比较运算，移位操作，字(字节)之间移位，专门改变CF值的指令等。<br>2、奇偶标志PF(Parity Flag)<br>奇偶标志PF用于反映运算结果中&#8221;1&#8243;的个数的奇偶性。如果&#8221;1&#8243;的个数为偶数，则PF的值为1，否则其值为0。<br>利用PF可进行奇偶校验检查，或产生奇偶校验位。在数据传送过程中，为了提供传送的可靠性，如果采用奇偶校验的方法，就可使用该标志位。<br>3、辅助进位标志AF(Auxiliary Carry Flag)<br>在发生下列情况时，辅助进位标志AF的值被置为1，否则其值为0：<br>(1)、在字操作时，发生低字节向高字节进位或借位时；<br>(2)、在字节操作时，发生低4位向高4位进位或借位时。<br>对以上6个运算结果标志位，在一般编程情况下，标志位CF、ZF、SF和OF的使用频率较高，而标志位PF和AF的使用频率较低。<br>4、零标志ZF(Zero Flag)<br>零标志ZF用来反映运算结果是否为0。如果运算结果为0，则其值为1，否则其值为0。在判断运算结果是否为0时，可使用此标志位。<br>5、符号标志SF(Sign Flag)<br>符号标志SF用来反映运算结果的符号位，它与运算结果的最高位相同。在微机系统中，有符号数采用补码表示法，所以，SF也就反映运算结果的正负号。运算结果为正数时，SF的值为0，否则其值为1。<br>6、溢出标志OF(Overflow Flag)<br>溢出标志OF用于反映有符号数加减运算所得结果是否溢出。如果运算结果超过当前运算位数所能表示的范围，则称为溢出，OF的值被置为1，否则，OF的值被清为0。&#8221;溢出&#8221;和&#8221;进位&#8221;是两个不同含义的概念，不要混淆。如果不太清楚的话，请查阅《计算机组成原理》课程中的有关章节。<br>二、状态控制标志位<br>状态控制标志位是用来控制CPU操作的，它们要通过专门的指令才能使之发生改变。<br>1、追踪标志TF(Trap Flag)<br>当追踪标志TF被置为1时，CPU进入单步执行方式，即每执行一条指令，产生一个单步中断请求。这种方式主要用于程序的调试。指令系统中没有专门的指令来改变标志位TF的值，但程序员可用其它办法来改变其值。<br>2、中断允许标志IF(Interrupt-enable Flag)<br>中断允许标志IF是用来决定CPU是否响应CPU外部的可屏蔽中断发出的中断请求。但不管该标志为何值，CPU都必须响应CPU外部的不可屏蔽中断所发出的中断请求，以及CPU内部产生的中断请求。具体规定如下：<br>(1)、当IF=1时，CPU可以响应CPU外部的可屏蔽中断发出的中断请求；<br>(2)、当IF=0时，CPU不响应CPU外部的可屏蔽中断发出的中断请求。<br>CPU的指令系统中也有专门的指令来改变标志位IF的值。<br>3、方向标志DF(Direction Flag)<br>方向标志DF用来决定在串操作指令执行时有关指针寄存器发生调整的方向。具体规定在第5.2.11节——字符串操作指令——中给出。在微机的指令系统中，还提供了专门的指令来改变标志位DF的值。<br>三、32位标志寄存器增加的标志位<br>1、I/O特权标志IOPL(I/O Privilege Level)<br>I/O特权标志用两位二进制位来表示，也称为I/O特权级字段。该字段指定了要求执行I/O指令的特权级。如果当前的特权级别在数值上小于等于IOPL的值，那么，该I/O指令可执行，否则将发生一个保护异常。<br>2、嵌套任务标志NT(Nested Task)<br>嵌套任务标志NT用来控制中断返回指令IRET的执行。具体规定如下：<br>(1)、当NT=0，用堆栈中保存的值恢复EFLAGS、CS和EIP，执行常规的中断返回操作；<br>(2)、当NT=1，通过任务转换实现中断返回。<br>3、重启动标志RF(Restart Flag)<br>重启动标志RF用来控制是否接受调试故障。规定：RF=0时，表示&#8221;接受&#8221;调试故障，否则拒绝之。在成功执行完一条指令后，处理机把RF置为0，当接受到一个非调试故障时，处理机就把它置为1。<br>4、虚拟8086方式标志VM(Virtual 8086 Mode)<br>如果该标志的值为1，则表示处理机处于虚拟的8086方式下的工作状态，否则，处理机处于一般保护方式下的工作状态</p>
<p><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/liguodong86/archive/2009/03/02/3949721.aspx">http://blog.csdn.net/liguodong86/archive/2009/03/02/3949721.aspx</a></p>
<img src ="http://www.cppblog.com/tdweng/aggbug/120852.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tdweng/" target="_blank">心羽</a> 2010-07-20 10:16 <a href="http://www.cppblog.com/tdweng/articles/120852.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>汇编中的寄存器说明</title><link>http://www.cppblog.com/tdweng/articles/118903.html</link><dc:creator>心羽</dc:creator><author>心羽</author><pubDate>Tue, 29 Jun 2010 02:24:00 GMT</pubDate><guid>http://www.cppblog.com/tdweng/articles/118903.html</guid><wfw:comment>http://www.cppblog.com/tdweng/comments/118903.html</wfw:comment><comments>http://www.cppblog.com/tdweng/articles/118903.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tdweng/comments/commentRss/118903.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tdweng/services/trackbacks/118903.html</trackback:ping><description><![CDATA[<p>汇编中的寄存器说明<br>汇编语言和CPU以及内存,端口等硬件知识是连在一起的. 这也是为什么汇编语言没有通用性的原因. 下面简单讲讲基本知识(针对INTEL x86及其兼容机) ============================ <br>x86汇编语言的指令,其操作对象是CPU上的寄存器,系统内存,或者立即数. 有些指令表面上没有操作数, 或者看上去缺少操作数, 其实该指令有内定的操作对象, 比如push指令, 一定是对SS:ESP指定的内存操作, 而cdq的操作对象一定是eax / edx. 在汇编语言中,寄存器用名字来访问. <br>CPU 寄存器有好几类, 分别有不同的用处: <br>&nbsp;&nbsp;<span style="COLOR: red"> <strong>1. 通用寄存器: EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP(这个虽然通用,但很少被用做除了堆栈指针外的用途) 这些32位可以被用作多种用途,但每一个都有"专长".</strong></span> <br>&nbsp;&nbsp; EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器.<br>&nbsp;&nbsp; EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址. <br>&nbsp;&nbsp; ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器.<br>&nbsp;&nbsp; EDX是...(忘了..哈哈)但它总是被用来放整数除法产生的余数. 这4个寄存器的低16位可以被单独访问,分别用AX,BX,CX和DX. AX又可以单独访问低8位(AL)和高8位(AH), BX,CX,DX也类似. 函数的返回值经常被放在EAX中.<br>&nbsp;&nbsp; ESI/EDI分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, <br>&nbsp;&nbsp; DS:ESI指向源串,而ES:EDI指向目标串. EBP是"基址指针"(BASE POINTER), 它最经常被用作高级语言函数调用的"框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码: push ebp ;保存当前ebp mov ebp,esp ;EBP设为当前堆栈指针 sub esp, xxx ;预留xxx字节给函数临时变量. ... 这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可. ESP 专门用作堆栈指针. <br><span style="COLOR: red"><strong>2. 段寄存器: CS(Code Segment，代码段) 指定当前执行的代码段</strong></span><br>EIP (Instruction pointer, 指令指针)则指向该段中一个具体的指令. CS:EIP指向哪个指令, CPU 就执行它. 一般只能用jmp, ret, jnz, call 等指令来改变程序流程,而不能直接对它们赋值. DS(DATA SEGMENT, 数据段) 指定一个数据段. 注意:在当前的计算机系统中, 代码和数据没有本质差别, 都是一串二进制数, 区别只在于你如何用它. 例如, CS 制定的段总是被用作代码, 一般不能通过CS指定的地址去修改该段. 然而,你可以为同一个段申请一个数据段描述符"别名"而通过DS来访问/修改. 自修改代码的程序常如此做. ES,FS,GS 是辅助的段寄存器, 指定附加的数据段. SS(STACK SEGMENT)指定当前堆栈段. ESP 则指出该段中当前的堆栈顶. 所有push/pop 系列指令都只对SS:ESP指出的地址进行操作.<br>&nbsp;<span style="COLOR: red"><strong>3. 标志寄存器(EFLAGS): 该寄存器有32位,组合了各个系统标志.</strong></span><br>&nbsp;EFLAGS一般不作为整体访问, 而只对单一的标志位感兴趣. 常用的标志有: 进位标志C(CARRY), 在加法产生进位或减法有借位时置1, 否则为0. 零标志Z(ZERO), 若运算结果为0则置1, 否则为0 符号位S(SIGN), 若运算结果的最高位置1, 则该位也置1. 溢出标志O(OVERFLOW), 若(带符号)运算结果超出可表示范围, 则置1. JXX 系列指令就是根据这些标志来决定是否要跳转, 从而实现条件分枝. 要注意,很多JXX 指令是等价的, 对应相同的机器码. 例如, JE 和JZ 是一样的,都是当Z=1是跳转. 只有JMP 是无条件跳转. JXX 指令分为两组, 分别用于无符号操作和带符号操作. JXX 后面的"XX" 有如下字母: 无符号操作: 带符号操作: A = "ABOVE", 表示"高于" G = "GREATER", 表示"大于" B = "BELOW", 表示"低于" L = "LESS", 表示"小于" C = "CARRY", 表示"进位"或"借位" O = "OVERFLOW", 表示"溢出" S = "SIGN", 表示"负" 通用符号: E = "EQUAL" 表示"等于", 等价于Z (ZERO) N = "NOT" 表示"非", 即标志没有置位. 如JNZ "如果Z没有置位则跳转" Z = "ZERO", 与E同. 如果仔细想一想,就会发现 JA = JNBE, JAE = JNB, JBE = JNA, JG = JNLE, JGE= JNL, JL= JNGE, .... <br><span style="COLOR: red"><strong>4. 端口 端口是直接和外部设备通讯的地方。</strong></span><br>外设接入系统后，系统就会把外设的数据接口映射到特定的端口地址空间，这样，从该端口读入数据就是从外设读入数据，而向外设写入数据就是向端口写入数据。当然这一切都必须遵循外设的工作方式。端口的地址空间与内存地址空间无关，系统总共提供对64K个8位端口的访问，编号0-65535. 相邻的8位端口可以组成成一个16位端口，相邻的16位端口可以组成一个32位端口。端口输入输出由指令IN,OUT,INS和OUTS实现，具体可参考汇编语言书籍</p>
<p><br>&nbsp;</p>
<img src ="http://www.cppblog.com/tdweng/aggbug/118903.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tdweng/" target="_blank">心羽</a> 2010-06-29 10:24 <a href="http://www.cppblog.com/tdweng/articles/118903.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>