﻿<?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++博客-时间的痕迹-文章分类-基础知识</title><link>http://www.cppblog.com/ivenher/category/243.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 22 May 2008 21:08:16 GMT</lastBuildDate><pubDate>Thu, 22 May 2008 21:08:16 GMT</pubDate><ttl>60</ttl><item><title>深入getmessage和peekmessage</title><link>http://www.cppblog.com/ivenher/articles/12473.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Thu, 14 Sep 2006 10:18:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/12473.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/12473.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/12473.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/12473.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/12473.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: bob gunderson																		msdn						技术组																		作于						:1992						年						11						月						11						日																																 					...&nbsp;&nbsp;<a href='http://www.cppblog.com/ivenher/articles/12473.html'>阅读全文</a><img src ="http://www.cppblog.com/ivenher/aggbug/12473.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-09-14 18:18 <a href="http://www.cppblog.com/ivenher/articles/12473.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>windows网络编程九</title><link>http://www.cppblog.com/ivenher/articles/12130.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Thu, 07 Sep 2006 14:32:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/12130.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/12130.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/12130.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/12130.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/12130.html</trackback:ping><description><![CDATA[
		<p> 3. recv和W S A R e c v<br />对在已连接套接字上接受接入数据来说， r e c v函数是最基本的方式。它的定义如下：</p>
		<p>int recv(<br />     SOCKET s,<br />     char FAR * buf,<br />     int len,<br />     int flags<br />    );<br />第一个参数s，是准备接收数据的那个套接字。第二个参数b u f，是即将收到数据的字符缓冲，而l e n则是准备接收的字节数或b u f缓冲的长度。最后， f l a g s参数可以是下面的值： 0、M S G _ P E E K或M S G _ O O B。另外，还可对这些标志中的每一个进行按位和运算。当然， 0表示无特殊行为。M S G _ P E E K会使有用的数据复制到所提供的接收端缓冲内，但是没有从系统缓<br />冲中将它删除。另外，还返回了待发字节数。<br />消息取数不太好。它不仅导致性能下降（因为需要进行两次系统调用，一次是取数，另一次是无M S G _ P E E K标志的真正删除数据的调用），在某些情况下还可能不可靠。返回的数据可能没有反射出真正有用的数量。与此同时，把数据留在系统缓冲，可容纳接入数据的系统空间就会越来越少。其结果便是，系统减少各发送端的T C P窗口容量。由此，你的应用就不能获得最大的流通。最好是把所有数据都复制到自己的缓冲中，并在那里计算数据。前面曾介绍过M S G _ O O B标志。有关详情，参见前面“带外数据”的内容。</p>
		<p>在面向消息或面向数据报的套接字上使用r e c v时，这几点应该注意。在待发数据大于所提供的缓冲这一事件中，缓冲内会尽量地填充数据。这时， r e c v调用就会产生W S A E M S G S I Z E错误。注意，消息长错误是在使用面向消息的协议时发生的。流协议把接入的数据缓存下来，<br />并尽量地返回应用所要求的数据，即使待发数据的数量比缓冲大。因此，对流式传输协议来说，就不会碰到W S A E M S G S I Z E这个错误。<br />W S A R e c v函数在r e c v的基础上增加了一些新特性。比如说重叠I / O和部分数据报通知。<br />W S A R e c v的定义如下：</p>
		<p>int WSARecv(<br />       SOCKET s,<br />       LPWSABUF lpBuffers,<br />       DWORD   dwBufferCount,<br />       LPWORD  lpNumberOfBytesRecved,<br />       LPWSAOVERLAPPED lpOverlapped,<br />       LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE<br />      );<br />      <br />参数s，是已建立连接的套接字。第二和第三个参数是接收数据的缓冲。l p B u ff e r s参数是一个W S A B U F结构组成的数组，而d w B u ff e r C o u n t则表明前一个数组中W S A B U F结构的数目。<br />如果接收操作立即完成， l p N u m b e r O f B y t e s R e c e i v e d参数就会指向执行这个函数调用所收到的字节数。l p F l a g s参数可以是下面任何一个值： M S G _ P E E K、M S G _ O O B、M S G _ PA RT I A L或者对这些值进行按位和运算之后的结果。M S G _ PA RT I A L标志使用和出现的地方不同，其含<br />义也不同。对面向消息的协议来说，这个标志是W S A R e c v调用返回后设置的（如果因为缓冲空间不够导致整条消息未能在这次调用中返回的话）。这时，后面的W S A R e c v调用就会设置这个标志M A S G _ PA RT I A L，直到整条消息返回，才把这个标志清除。如果这个标志当作一个输入参数投递，接收操作应该在一收到数据就结束，即使它收到的只是整条消息中的一部分。<br />M S G _ PA RT I A L标志只随面向消息的协议一起使用。每个协议的协议条目都包含一个标志，表明是否支持这一特性。有关详情，参见第5章。l p O v e r l a p p e d和l p C o m p l e t i o n R O U T I N E参数用于重叠I / O操作</p>
		<p>4. WSARecvDisconnect<br />这函数与W S A S e n d D i s c o n n e c t函数对应，其定义如下：<br />int WSARecvDisconnect( <br />            SOCKET s,<br />            LPWSABUF lpOUTboundDisconnectData<br />           );<br />和W S A S e n d D i s c o n n e c t函数的参数一样，该函数的参数也是已建立连接的套接字句柄和<br />一个有效的W S A B U F结构（带有收到的数据）。收到的数据可以只是断开数据。这个断开数据是另一端执行W S A S e n d D i s c o n n e c t调用发出的，它不能用于接收普通数据。另外，一旦收到这个数据， W S A R e c v D i s c o n n e c t函数就会取消接收远程通信方的数据，其作用和调用带有S D _ R E C V的s h u t d o w n函数相同。<br />5. WSARecvEx<br />W S A R e c v E x函数是微软专有的Winsock 1扩展，除了f l a g s参数是按值引用外，其余和r e c v函数是一样的。它允许基层的提供者设置M S G _ PA RT I A L标志。该函数的原型如下：</p>
		<p>int PASCAL FAR WSARecvEx(<br />             SOCKET s,<br />             char FAR * buf,<br />             int len,<br />             int * flags<br />            );<br />            </p>
		<p>
				<br />如果收到的数据不是一条完整的消息， f l a g s参数中就会返回M S G _ PA RT I A L标志。对面向消息的协议（即非流协议）来说，这个标志比较有用（即非流协议）。在M S G _ PA RT I A L标志被当作f l a g s参数的一部分投递，而且收到的消息又不完整时，调用W S A R e c v E x，就会立即<br />返回收到的那个数据。如果提供的接收缓冲容纳不下整条消息， W S A R e c v E x就会失败，并出现W S A E M S G S I Z E 错误，剩下的数据也会被截掉。注意， M S G _ PA RT I A L 标志和W S A E M S G S I Z E错误之间的确区别是：有了这个错误，即使整条消息到达接收端，但由于提<br />供的数据缓冲太少，也不能对它进行接收。M S G _ P E E K 和M S G _ O O B标志还可以和W S A R e c v E x一起使用。</p>
<img src ="http://www.cppblog.com/ivenher/aggbug/12130.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-09-07 22:32 <a href="http://www.cppblog.com/ivenher/articles/12130.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>windows网络编程二</title><link>http://www.cppblog.com/ivenher/articles/12055.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Tue, 05 Sep 2006 11:31:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/12055.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/12055.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/12055.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/12055.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/12055.html</trackback:ping><description><![CDATA[
		<p> 第2章重定向器<br /> 首先，我们打算解释如<br />何通过网络，使用“多U N C提供者”（ Multiple UNC Provider, MUP）资源定位符，通过“通用命名规范”（Universal Naming Convention, UNC）来引用远程文件。</p>
		<p>随后，我们讲解了M U P如何调用一个网络提供者，从而揭示出怎样通过一个重定向器，在“服务器消息块”（Server Message Block, SMB）协议的帮助下，在不同的计算机之间建立数据通信。</p>
		<p>最后，我们探讨了网络安全方面的一些问题。使用基本的文件I / O操作，通过网络来访问文件时，这些安全问题是必须考虑到的。</p>
		<p>2.1 通用命名规范</p>
		<p>“U N C路径”  为网络文件及设备的访问建立了一套统一的规范。它最大的特点便是不必指定或引用一个已映射到远程文件系统的本地驱动器字母。</p>
		<p>U N C名字完全解决了这些问题，它的格式如下：<br />\ \ [服务器] \ [共享名] \ [路径]<br />第一部分是\ \ [服务器]，必须以两个反斜杠开头，紧跟着一个服务器名字。<br />第二部分是\ [共享名]，它对应着远程服务器上的一个“共享入口”或者“共享位置”。<br />。而第三部分\ [路径]  对应的是共享位置下的某个具体目录（或子目录）</p>
		<p>
				<br />第3章邮槽</p>
		<p>一种简单的单向“进程间通信”（interprocess communication,I P C）机制。这个机制的名字非常古怪，叫作“邮槽”（M a i l s l o t）。用最简单的话来说，通过<br />邮槽，客户机进程可将消息传送或广播给一个或多个服务器进程。在同一台计算机的不同进程之间，或在跨越整个网络的不同计算机的进程之间，协助进行消息的传输。用邮槽来开发应用程序是一件非常简单的事情，不要求对T C P / I P或I P X这样的基层网络传送协议有着非常深入的了解。由于邮槽是围绕一个广播通信体系设计出来的，所以当然不能指望能通过它实现数据的“可靠”传输。</p>
		<p>邮槽最大的一个缺点便是只允许从客户机到服务器，建立一种不可靠的单向数据通信。<br />而另一方面，邮槽最大的一个优点在于，它们使客户机应用能够非常容易地将广播消息发送给一个或多个服务器应用。</p>
		<p>3.1 邮槽实施细节<br />邮槽是围绕Wi n d o w s文件系统接口设计出来的。客户机和服务器应用需要使用标准的Wi n 3 2文件系统I / O（输入／输出）函数，比如R e a d F i l e和Wr i t e F i l e等等，以便在邮槽上收发数据，同时利用Wi n 3 2文件系统的命名规则。邮槽必须依赖Wi n d o w s重定向器，通过一个“邮槽文件系统”（Mailslot File System, MSFS），来创建及标识邮槽。</p>
		<p>3.1.1 邮槽的名字<br />对邮槽进行标识时，需遵守下述命名规则：<br />\ \ s e r v e r \ M a i l s l o t \ [ p a t h ] n a m e<br />请将上述字串分为三段来看： \ \ s e r v e r、\ M a i l s l o t和\ [ p a t h ] n a m e。第一部分\ \ s e r v e r对应于服务器的名字，我们要在上面创建邮槽，并在在上面运行服务器程序。第二部分\ M a i l s l o t是一个“硬编码”的固定字串，用于告诉系统这个文件名从属于M S F S。而第三部分\ [ p a t h ] n a m e则<br />允许应用程序独一无二地定义及标识一个邮槽名。其中，“p a t h”代表路径，可指定多级目录。<br />举个例子来说，对一个邮槽进行标识时，下面这些形式的名字都是合法的（注意M a i l s l o t不得变化，必须原文照输，亦即所谓的“硬编码”）：<br />由于邮槽要依赖Wi n d o w s文件系统服务在网上来创建和传输数据，所以接口是“与协议无关”的。<br />要想保证各种Wi n d o w s平台之间能够完全正常地通信，强烈建议将消息长度限制在4 2 4字节，或者更短。如果进行面向连接的传输，可考虑使用命名管道，而不是简单的邮槽。</p>
		<p>3.5 小结<br />本章讲解了邮槽（ M a i l s l o t）网络编程技术。利用这一技术，应用程序可以在Wi n d o w s重定向器的帮助下，实现简单的单向进程间数据通信。对邮槽来说，它最有价值的一项功能便是通过网络，将一条消息广播给一台或多台计算机。然而，邮槽并未提供对数据可靠传输的保障。假如希望用Wi n d o w s重定向器实现“可靠”的数据通信，请考虑使用命名管道，这是下一章的主题！</p>
<img src ="http://www.cppblog.com/ivenher/aggbug/12055.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-09-05 19:31 <a href="http://www.cppblog.com/ivenher/articles/12055.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>windows 网络编程</title><link>http://www.cppblog.com/ivenher/articles/12040.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Tue, 05 Sep 2006 08:46:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/12040.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/12040.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/12040.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/12040.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/12040.html</trackback:ping><description><![CDATA[
		<p>1、netBIOS<br />非可路由协议<br />LAN适配器（LAN  adapter）编号很重要.<br />每个LANA编号对应于网卡和传输协议的唯一组合。</p>
		<p>netBIOS名字<br />在Wi n 3 2环境中，针对每个可用的L A N A编号，每<br />个进程都会为其维持一张N e t B I O S名字表。若为LANA 0增添一个名字，意味着你的应用程序<br />只能在LANA 0上同客户机建立连接。对每个L A N A来说，能够添加的名字的最大数量是2 5 4，<br />编号从1到2 5 4（0和2 5 5由系统保留）<br />，N e t B I O S名字共有两个性质：唯一名字和组名<br />微软网络中的机器命名就是NetBIOS命名。<br />“组名”的作用是将数据同时发给多个接收者；或者相反，接收发给多个<br />接收者的数据。组名并非一定要“独一无二”，它主要用于多播（多点发送）数据通信<br />若有“windows互联网命名服务器”即wins，则有它管理，若无则发广播探测是否重名。</p>
		<p>1.1.3 NetBIOS特性</p>
		<p>N e t B I O S同时提供了“面向连接”服务以及“无连接”服务。</p>
		<p>1.2 NetBIOS编程基础<br />NetBIOS API的设置，只有一个函数：<br />UCHAR Netbios(PNCB pNCB);</p>
		<p>用于N e t B I O S的所有函数声明、常数等等均是在头文件N b 3 0 . h内定义的。若想连接<br />N e t B I O S应用，唯一需要的库是N e t a p i 3 2 . l i b。<br />调用N e t b i o s函数时，可选择进行同步调用，还是进行异步调用。所有N e t B I O S命令本身均是同步的。要想异步调用一个命令，需要让N e t B I O S命令同A S Y N C H标志进行一次逻辑O R（或）运算。如指定了A S Y N C H标志，那么必须在n c b _ p o s t字段中指定一个后例程（Post Routine），或必须在n c b _e v e n t字段中指定一个事件句柄。执行一个异步命令时，从N e t b i o s返回的值是N R C _ G O O D R E T( 0 x 0 0 )，但n c b _ c m d _ c p l t字段会设为N R C _ P E N D I N G ( 0 x F F )。除此以外， N e t b i o s函数还会将N C B结构的n c b _ c m d _ c p l t字段设为N R C _ P E N D I N G（待决），直到命令完成为止。命令完成后，n c b _ c m d _ c p l t字段会设为该命令的返回值。N e t b i o s也会在完成后将n c b _ r e t c o d e字段设为命令的返回值。<br /><br /><br />1.4 数据报的工作原理</p>
		<p>“数据报”（D a t a g r a m）属于一种“无连接”的通信方法。作为发送方，只需用目标<br />N e t B I O S名字为发出的每个包定址，然后简单地送出了事。此时，不会执行任何检查，以确<br />保数据的完整性、抵达顺序或者传输的可靠性等等。</p>
		<p>发出一个数据报共有三种方式。<br />第一种是指挥数据报抵达一个特定的（或唯一的）组名。这意味着只能有一个进程负责数据报的接收—亦即注册了目标名字的那个进程。<br />第二种是将数据报发给一个组名。只有注册了指定组名的那些进程才有权接收消息。<br />最后，第三种方式是将数据报广播到整个网络。</p>
<img src ="http://www.cppblog.com/ivenher/aggbug/12040.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-09-05 16:46 <a href="http://www.cppblog.com/ivenher/articles/12040.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Rich Client技术简介 4</title><link>http://www.cppblog.com/ivenher/articles/5814.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Tue, 18 Apr 2006 04:52:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/5814.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/5814.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/5814.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/5814.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/5814.html</trackback:ping><description><![CDATA[ Flex官方说法如下（摘自网络上相关文章）:<?XML:NAMESPACE PREFIX = O /?><o:p></o:p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">    原代号为“Royale”的MacromediaFlex软件将把服务器软件、开发指南和其他工具组合在一起，使传统的网络应用开发人员能够用Macromedia公司的Flash格式创作软件单元。如从前报道的那样，该产品的重点是让那些使用Sun微系统公司的Java2企业版(J2EE)的开发人员能够创作出更有吸引力、更容易导航的J2EE应用软件接口。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">    Flex将使J2EE开发人员使用标准的文本式开发工具来制作Flash应用程序，而不必使用Macromedia公司以前出售的复杂的设计工具。Macromedia公司从今年年初开始，努力扩大Flash格式对于主流开发商的吸引力，其目标是扩大Flash的用途，使其成为提供互联网应用和建立交互式网站的基础。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">    Macromedia公司计划在2004年上半年推出Flex服务器软件，该软件的价格目前还没有确定。它的初级版本将运行于J2EE中，并计划随后推出支持微软的.Net格式的版本。最初的支持者包括IBM公司，它将随自己的WebSphere软件一起推广Flex的应用。 <o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">    需要了解更多Flex技术的朋友可以访问Flex的主页： <o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">    http://www.macromedia.com/software/flex/  <o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none"><o:p> </o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none; mso-outline-level: 1"><b>Thinlet</b><o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">    Thinlet是一个采用Applet解析XUL并提供相应界面的解析器，在事件发生时，调用用户自己的事件处理程序(java 程序)，需要客户端浏览器支持Applet。更多信息可以参考　http://www.thinlet.com/<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none"><o:p> </o:p></p><img src ="http://www.cppblog.com/ivenher/aggbug/5814.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-04-18 12:52 <a href="http://www.cppblog.com/ivenher/articles/5814.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Rich Client技术简介 3</title><link>http://www.cppblog.com/ivenher/articles/5813.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Tue, 18 Apr 2006 04:50:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/5813.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/5813.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/5813.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/5813.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/5813.html</trackback:ping><description><![CDATA[
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">Hessian（http://www.caucho.com）是由Resin应用服务器的开发商Caucho公司制定的一个RPC协议，虽然它也是通过HTTP协议传输RPC封包，但是它的RPC封包却是以二进制形式编码的，而且能够表现对象模型和异常体系，这就使得Hessian比<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>-RPC具有更高的效率。<?XML:NAMESPACE PREFIX = O /?><o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">具体通信机制资料请读者参考网上内容和透明于2004年5期《程序员》杂志中《王朝复辟还是浴火重生》一文。<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 13pt 0cm; LINE-HEIGHT: 170%; mso-layout-grid-align: none; mso-pagination: lines-together; mso-outline-level: 1">
				<b>Rich Client</b>
				<b>开源开发平台</b>
				<b>Laszlo<o:p></o:p></b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">Laszlo是一个开源的Rich client开发环境。使用Laszlo平台时，开发者只需编写名为LZX的描述语言（其中整合了<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>和JavaScript），运行在J2EE应用服务器上的Laszlo平台会将其编译成FLASH文件并传输给客户端展示。单从运行原理来说，Laszlo与XUL（<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>用户接口语言，    <a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a> User interface Language）、XAML（“Longhorn”）标记语言很类似。但它的最大优势在于：它把描述语言编译成FLASH，而FLASH是任何浏览器都支持的展示形式，从而一举解决了浏览器之间的移植问题。而且，在未来的计划中，Laszlo还可以将LZX编译成Java或.NET本地代码，从而大大提高运行效率。<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">具体请参考http://www.openlaszlo.org。<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">
				<o:p> </o:p>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">IBM AlphaWorks网站近日发布了用于开发Laszlo应用程序的集成开发环境（实际上是一个Eclipse插件），使J2EE开发者能够在他们熟悉的Eclipse环境中快速开发基于Laszlo的rich client应用程序。可以在下列地址下载该插件：<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">http://alphaworks.ibm.com/tech/ide4laszlo<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">此外，AlphaWorks网站还提供了一个用Laszlo开发的示例应用，展示了在Eclispe环境下开发Laszlo应用的过程。demo的地址如下：<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">http://dl.alphaworks.ibm.com/technologies/rcb/demo.html<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">
				<o:p> </o:p>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none; mso-outline-level: 1">
				<b>FLEX</b>
				<o:p>
				</o:p>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">    Flex是Macromedia公司开发的，用于Rich client开发的环境，其原理是将M<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>(the Macromedia Flex Markup Language)文件，编译成SWF文件，然后显示在浏览器中,并利用Web Service技术和服务器通信。从而利用Flash的强大功能，带来更丰富的用户体验。</p>
<img src ="http://www.cppblog.com/ivenher/aggbug/5813.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-04-18 12:50 <a href="http://www.cppblog.com/ivenher/articles/5813.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Rich Client技术简介 2</title><link>http://www.cppblog.com/ivenher/articles/5812.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Tue, 18 Apr 2006 04:47:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/5812.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/5812.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/5812.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/5812.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/5812.html</trackback:ping><description><![CDATA[
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">RMI可以是做CORBA的Java版本，但相比较而言这是一个轻量级的版本了，对于服务器和客户端两边都用Java来实现的前提下，这是一个非常好的选择。<?XML:NAMESPACE PREFIX = O /?><o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">CORBA和RMI有一个共同的缺陷：通常不会在系统80端口提供服务，所以这在具备网罗防火墙的情况下显得非常被动。<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 13pt 0cm; LINE-HEIGHT: 170%; mso-layout-grid-align: none; mso-pagination: lines-together; mso-outline-level: 1">
				<b>
						<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>-RPC<o:p></o:p></b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">为了解决在系统的80端口提供RPC的服务，而又不影响正在执行的WEB服务，人们想出了用HTTP协议传输RPC包的办法。对于几乎是专门用于传输文本的HTTP协议，要在其上传输RPC封包，最方便的方法莫过于把RPC封包编码成文本形式——例如<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>文件。<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">
				<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>-RPC（http://www.xml-rpc.com）是由美国UserLand公司指定的一个RPC协议。它将RPC信息封包编码为<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>，然后通过HTTP传输封包；它是一个简单的RPC协议，只支持一些基本数据类型，不支持对象模型，这势必掣肘在客户端和服务器端之间传输复杂的对象。<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">
				<o:p> </o:p>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none; mso-outline-level: 1">
				<b>SOAP<o:p></o:p></b>
		</p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">
				<b>      </b>SOAP即Simple Object Access Protocol(简单对象访问协议)是在分散或分布式的环境中交换信息的简单的协议，是一个基于<a class="Channel_KeyLink" href="http://www.chinageren.com/Article/WEB/XML/Index.html">XML</a>的协议。它允许所有的操作在80端口上进行，从而也绕过了防火墙等问题。<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">　　SOAP规范中有三个基本组成部分：SOAP封装，编码规则，以及在请求和响应之间的交互方式。<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">　　目前已有的基于JAVA提供SOAP功能的产品有：Apache SOAP, IBM SOAP4J等<o:p></o:p></p>
		<p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">    要了解更多关于SOAP的信息，可以访问 http://www.w3.org/TR/SOAP <o:p></o:p></p>
<img src ="http://www.cppblog.com/ivenher/aggbug/5812.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-04-18 12:47 <a href="http://www.cppblog.com/ivenher/articles/5812.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Rich Client技术简介 1（转）</title><link>http://www.cppblog.com/ivenher/articles/5811.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Tue, 18 Apr 2006 04:36:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/5811.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/5811.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/5811.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/5811.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/5811.html</trackback:ping><description><![CDATA[传统的基于C/S的Windows应用程序总是让客户面临着一些感觉很是不爽的问题，如：部署问题、升级困难、维护困难、安全问题，但是完全的WEB开发由于HTTP协议的无状态特性——浏览器和服务器总是在不停地执行Request和Response来营造一种有状态、持续会话的假象，致使人们又开始怀恋具有更强运算能力、本地存储能力和更稳定的通讯能力的客户端程序了。<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /?><o:p></o:p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24.1pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">不得不说的是，宽带网络的出现在某种意义促成了Rich Client的诞生。通过快捷的发布，特定的通讯协议标准，Rich Client正以不可阻挡的气势向人们重现着C/S模式下客户端程序的优势。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 13pt 0cm; LINE-HEIGHT: 170%; mso-layout-grid-align: none; mso-pagination: lines-together; mso-outline-level: 1"><b>Rich Client</b><b>的发布</b><b><o:p></o:p></b></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">C/S架构下，客户端程序发布与维护一直比较困难和繁琐。在版本更新以后，需要对客户的客户端程序进行逐个下载安装及配置更新，这是一个体力活，而这也一直是使用户大量选择WEB程序的因素之一。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">在Rich Client时代，由于宽带网络的便利，在客户端尽尽需要从服务器端下载已经更新好的程序运行，而不必理会繁琐的下载、安装和配置的过程。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">这里不得不提Java的是WebStart技术。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">WebStart是让用户只需在网页上点击一个超级链接就能运行一个Java桌面应用的技术。对于一个拥有WebStart能力的Java应用来说，用户使用它就和使用WEB应用一样的简单，但它所具有的界面能力和本地处理能力却是WEB应用无法望其项背的。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">具体的应用的技术知识可以从<u>http://java.sun.com</u>中寻找相关文档，这里不一一赘述。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 13pt 0cm; LINE-HEIGHT: 170%; mso-layout-grid-align: none; mso-pagination: lines-together; mso-outline-level: 1"><b>Rich Client</b><b>的通信机制</b><b><o:p></o:p></b></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">除了快捷方便的发布外，Rich Client还需要与服务器端建立一种快速、可靠、强大、易用的通信交互机制。但我们开发WEB应用时，表现层和业务服务层常常只是同一个进程中的不同对象，它们之间的交互不过是Java的方法调用而已，当表现层逻辑被分发到世界各地的计算机上，客户端和服务器之间的交互就成了一个大问题——从前的C/S被淘汰，很大程度上归咎于socket通信的复杂性。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 24pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">现在，形形色色的RPC（远程过程调用，Remote Procedure Call）技术以独特的优势扮演起了信使的角色。以下列举几种Rich Client可以采用的通信机制。<o:p></o:p></p><p class="MsoNormal" style="MARGIN: 13pt 0cm; LINE-HEIGHT: 170%; mso-layout-grid-align: none; mso-pagination: lines-together; mso-outline-level: 1"><b>CORBA</b><b>和</b><b>RMI<o:p></o:p></b></p><p class="MsoNormal" style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; LINE-HEIGHT: 150%; mso-layout-grid-align: none">CORBA（通用对象请求代理体系结构，Common Object Request Broker Architecture）曾经红极一时，它能够兼容各种操作系统平台的语言，强大的的可扩展性所带来的负面影响就是实现的复杂和繁琐。如果服务器端和客户端都采用Java开发，那么CORBA所需要的语言无关的IDL就完全变成了画蛇添足。当然，对于需要集成大量企业内遗留的系统的EAI（企业应用集成）项目中，它一直是首选的技术。</p><img src ="http://www.cppblog.com/ivenher/aggbug/5811.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2006-04-18 12:36 <a href="http://www.cppblog.com/ivenher/articles/5811.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>在DOS实模式下直接存取4GB内存</title><link>http://www.cppblog.com/ivenher/articles/1737.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Wed, 14 Dec 2005 03:57:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/1737.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/1737.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/1737.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/1737.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/1737.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 作为软件开发人员，大多数对于保护模式都感到神秘和不易理解。本人在开发32位微内核抢占式多线程操作系统过程中，深入了解到CPU的地址机理，在这里将分析CPU的工作原理，解开保护模式的神秘面纱，读者将会发现保护模式其实与实模式一样简单和易于控制。在此基础上用四五十行C语言程序做到进出保护模式和在实模式之下直接访问整个4GB内存空间。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 虽然有许多书籍对保护模式作解释，但没有一本能简单明了地解释清楚，冗长烦杂的术语让人看着想打瞌睡，甚至还有许多用汇编写的(可能根本不能运行的)保护模式试验程序，事实上用C语言本身就可以做保护模式的进出工作。<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 我们可能知道CPU上电后从ROM中的BIOS开始运行，而Intel文档却说80x86CUP上电总是从最高内存下16字节开始执行，那么BIOS是处在内存的最顶端64K(FFFF0000H)还是1M之下的64K(F0000H)处呢?事实上在这两个地方都同时出现(可用后面存取4GB内存的程序验证)。<BR>为什么?为了弄清楚以上问题，首先要了解CPU是如何处理物理地址的?真的是在实模式下用段寄存器左移4位与偏移量相加，在保护模式下用段描述符中的基地址加偏移量而两者是毫无关联的吗?答案是两者其实是一样的。当Intel把80286推出时其地址空间变成了24位，从8086的20位到24位，十分自然地要加大段寄存器才行，实际上它们都被加大了，只是由于保护的原因加大的部分没有被程序看见，到了80386之后地址又从24位加大到32位(80386SX是24位)。整个段寄存器如下图所示：<BR>@@12A08400.GIF;图1@@<BR>在8086中CPU只有“看得见部分”，从而也直接参与了地址形成运算，但在80286之后，在“看不见部分”中已经包含了地址值，“看得见部分”就退化为只是一个标号再也不用参与地址形成运算了。地址的形成总是从“不可看见部分”取出基址值与偏移相加形成地址。也就是说在实模式下当一个段寄存器被装入一个值时，“看不见部分”的界限被设成FFFFH，基址部分才是要装入值左移4位，属性部分设成16位0特权级。这个过程与保护模式时装入一个段存器是同理的，只是保护模式的“不可见部分”是从描述表中取值，而实模式是一套固定的过程。<BR>对于CPU在形成地址时，是没有实模式与保护模式之分的，它只管用基址(“不可见部分”)去加上偏移量。实模式与保护模式的差别实际上只是保护处理部件是否工作得更精确而已，比如不允许代码段的写入。实模式下的段寄存装入有固定的形成办法从而也就不需要保护模式的“描述符”了，因此保持了与8086/8088的兼容性。而“描述符”也只是为了装入段寄存器的“不可见部分”而设的。<BR>从上面的“整个段寄存器”可见CPU的地址形成与“看得见部分”的当前值毫无关系，这也解释了为什么在刚进入保护模式时后面的代码依然被正确地运行而这时代码段寄存器CS的值却还是进入保护模式前的实模式值，或者从保护模式回到实模式时代码段CS被改变之前程序是正常地工作，而不会“突变”到CS左移4位的地址上去，比如在保护模式时CS是08H的选择器，到了实模式时CS还是08H但地址不会突然变成80H加上偏段量中去。因为地址的形成不理会段寄存器“看得见部分”的当前值，这一个值只是在被装入时对CPU有用。<BR>地址的形成与CPU的工作模式无关，也就是说实模式与0特权级保护模式不分页时是一模一样的。明白了这一机理，在实模式下一样可以处理通常被认为只有在保护模式才能做的事，比如访问整个机器的内存。可以不必理会保护模式下的众多术语，或者更易于理解，如选择器就是“看得见部分”，描述符是为了装入“不可见部分”而设的。<BR>作为验证CPU的这种机理，这里写了一个实模式下访问4GB内存的C程序。有一些书籍也介绍有同样功能的汇编程序，但它们都错误地认为是利用80386芯片的设计疏漏。实际上Intel本身就在使用这种办法，使得CPU上电时能从FFFFFFF0H处开始第一条指令，这种技术在286之后的每一台机器每一次冷启动时都使用，只是我们不知道罢了。CPU上电也整个代码段寄存器是这样的：<BR>@@12A08401.GIF;图2@@<BR>EIP=0000FFF0H<BR>这样CS∶EIP形成了FFFFFFF0H的物理地址，当CPU进行一次远跳转重新装入CS时，基址就变了。<BR>为了访问4G内存空间，必须有一个段寄存器的“不可见部分”的界限为4G-1，基址为0，这样就包含了4GB内存，不必理会可见部分的值。显然要让段寄存器在实模式下直接装入这些值是不可能的。唯一的办法是让CPU进入一会儿保护模式在装入了段寄存器之后马上回到实模式。<BR>进入保护模式十分简单，只要建好GDT把CRO寄存器的位0置上1，CPU就在保护模式了，从前面所分析CPU地址形成机理可知，这时不必理会寄存器的“看得见部分”值是否合法，各种段寄存器是一样可用的，就像没进保护模式一样。在把一个包含有4GB地址空间的值装入某个段寄存器之后就可返回实模式。<BR>预先可建好GDT如下：<BR>unsigned long GDT-Table[]={0,0, //空描述符，必须为零0x0000FFFF,0xCF9A00, //32位平面式代码段0x0000FFFF,0xCF9200 } , //32位平面式数据段只是为了访问数据的话只要2个GDT就足够了，因为并没有重装代码段，这里给出3个GDT只是为了完整性。<BR>通常在进入保护模式时要关闭所有的中断，把IDTR的界限设置为0，CPU自动关闭所有中断，包括NMI，返回实模式后恢复IDTR并开中断。<BR>另外A20地址线的控制对于正确访问整个内存也很重要，在进入保护模式前要让8042打开A20地址线。<BR>在这个例子里FS段寄存器设成可访问4GB内存的基址和界限，由于在DOS中很少有程序会用到GS、FS这两个386增加的段寄存器，当要读写4GB范围中的任一个地方都可通过FS段来达到，直到FS在实模式下被重装入冲掉为止。<BR>这个例子在386SX、386DX、486上都运行通过。例子里加有十分详细的注释，由于这一程序是用BC 3.1编译连接的，而其连接器不能为DOS程序处理32位寄存器，所以直接在代码中加入操作码前缀0x66和地址前缀0x67，以便让DOS实模式下的16位程序可用32位寄存器和地址。程序的右边以注释形式给出等效的32位指令。要注意16位的指令中mov al, byte ptr [BX]的指令码正好是32位的指令mov al, byte ptr[EDI]。<BR>读者可用这个程序验证BIOS是否同时在两个区域出现。如果有线性定址能力的VESA显示卡(如TVGA9440)还可进一步验证线性显示缓冲区在1MB之上的工作情况。<BR>#include &lt;dos.h&gt;<BR>unsigned long GDT-Table[]=<BR>{0,0, //NULL - 00H<BR>0x0000FFFF,0x00CF9A00, //Code32 - 08h Base=0 Limit=4G-1 Size=4G<BR>0x0000FFFF,0x00CF9200 //Data32 - 10h Base=0 Limit=4G-1 Size=4G<BR>};<BR>unsigned char OldIDT [6]={0}; //Save The IDTR before Enter Protect Mode.<BR>unsigned char pdescr-tmp [6]={0}; //NULL The IDTR s Limit=0 CPU will<BR>// disable all Interrupts, include NMI.<BR>#define KeyWait() {while(inportb(0x64) &amp;2);}<BR>void A20Enable(void)<BR>{<BR>keyWait ();<BR>outportb(0x64,0xD1);<BR>KeyWait();<BR>outportb(0x60,0xDF); //Enable A20 with 8042.<BR>KeyWait();<BR>outportb(0x64,0xFF);<BR>KeyWait ();<BR>}<BR>void LoadFSLimit4G(void)<BR>{<BR>A20Enable (); //Enable A20<BR>//***<BR>Disable ints &amp; Null IDT<BR>//***<BR>asm {<BR>CLI //Disable inerrupts<BR>SIDT OldIDT //Save OLD IDTR<BR>LIDT pdescr-tmp //Set up empty IDT.Disable any interrupts,<BR>} // Include NMI.<BR>//***<BR>Lodd GDTR<BR>//***<BR>asm{ // The right Code is Real, But BC++ s Linker NOT<BR>// Work with 32bits Code.<BR>db 0x66 //32 bit Operation Prefix in 16 Bit DOS.<BR>MOV CX,DS //MOV ECX,DS<BR>db 0x66 //Get Data segment physical Address<BR>SHL CX,4 //SHL ECX,4<BR>MOV word ptr pdescr-tmp [0],(3*8-1)<BR>//MOV word ptr pdescr-tmp [0], (3*8-1)<BR>db 0x66<BR>XOR AX,AX //XOR EAX,EAX<BR>MOV AX,offset GDT-Table<BR>// MOV AX,offset GDT-Table<BR>db 0x66<BR>ADD AX,CX //ADD EAX,ECX<BR>MOV word ptr pdescr-tmp [2], AX<BR>//GDTR Base low16 bits<BR>db 0x66<BR>SHR AX,16 //SHR EAX,16<BR>MOV word ptr pdescr-tmp [4],AX<BR>//GDTR Base high16 bits<BR>LGDT pdescr-tmp //Load GDTR<BR>}<BR>//****<BR>//* Enter 32 bit Flat Protected Mode<BR>//****<BR>asm{<BR>mov DX,0x10 // The Data32 Selector<BR>db 0x66,0x0F,0x20,0xC0 // MOV EAX,CR0<BR>db 0x66<BR>MOV BX,AX // MOV EBX,EAX<BR>OR AX,1<BR>db 0x66,0x0F,0x22,0xC0<BR>//MOV CRO,EAX // Set Protection enable bit<BR>JMP Flsuh<BR>} //Clear machine perform cache.<BR>flush: // Now In Flat Mode, But The CS is Real Mode Value.<BR>asm { //And it s attrib is 16Bit Code Segment.<BR>db 0x66<BR>MOV AX,BX //MOV EAX,EBX<BR>db 0x8E,0xE2 //MOV FS,DX<BR>//Load FS Base=0 Size=4G now<BR>db 0x66,0x0F,0x22,0xC0 //MOV CRO,EAX<BR>//Return Real Mode.<BR>LIDT OldIDT //LIDT OldIDT //Restore IDTR<BR>STI // STI //Enable INTR<BR>}<BR>}<BR>unsigned char ReadByte (unsigned long Address)<BR>{<BR>asm db 0x66<BR>asm mov di,word ptr Address // MOV EDI, Address<BR>asm db 0x67 //32 bit Address Prefix<BR>asm db 0x64 //FS:<BR>asm mov al,byte ptr [BX] // =MOV AL, FS: [EDI]<BR>return -AL;<BR>}<BR>unsigned char WriteByte(unsigned Long Address)<BR>{<BR>asm db 0x66<BR>asm mov di,word ptr Address //MOV EDI, Address<BR>asm db 0x67 //32 bit Address Prefix<BR>asm db 0x64 //FS:<BR>asm mov byte ptr [BX],al //=MOV FS: [EDI],AL<BR>return -AL;<BR>}<BR>//////// Don t Touch Above Code ///<BR># include &lt;stdio, h&gt;<BR>void Dump4G (unsigned long Address)<BR>{<BR>int i;<BR>int j;<BR>for (i=0; i&lt;20; i++)<BR>{<BR>printf (“%081X: ”, (Address+i*16));<BR>for (j=0; j&lt;16;j++)<BR>printf ("% 02X" ,ReadByte (Address+i*16+j));<BR>printf (" ");<BR>for (j=0;j&lt;16;j++)<BR>{<BR>if (ReadByte (Address+i*16+j) &lt;0x20) printf (" . ");<BR>else printf (" %C ", ReadByte (Address+i*16+j));<BR>}<BR>printf (" ");<BR>}<BR>}<BR>main ()<BR>{<BR>unsigned long Address=0;<BR>unsigned long tmp;<BR>LoadFSLimit4G ();<BR>printf ("====Designed By Southern. 1995.7.17==== ");<BR>printf (" Now you can Access The Machine All 4G Memory. ");<BR>printf (" Input the Start Memory Physical to DUMP. ");<BR>printf (" Press D to Cuntinue DUMP, 0 to End &amp; Quit, ");<BR>do {<BR>printf ("-");<BR>scanf ("%IX", &amp;tmp);<BR>if (tmp==0x0d) Address+=(20*16);<BR>else Address=tmp;<BR>Dump4G (Address);<BR>}while (Address !=0);<BR>return 0;<BR>} <BR><BR>(作者地址：珠海巨人集团电脑排版公司 <BR><img src ="http://www.cppblog.com/ivenher/aggbug/1737.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-14 11:57 <a href="http://www.cppblog.com/ivenher/articles/1737.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Win2k内存分页机制 (一)- -</title><link>http://www.cppblog.com/ivenher/articles/1735.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Wed, 14 Dec 2005 03:30:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/1735.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/1735.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/1735.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/1735.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/1735.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 
<P>
<P><B><FONT face=宋体>基本概念</FONT></B></P>
<P><FONT face=宋体>&nbsp;&nbsp;&nbsp; Windows 2000 使用基于分页机制的虚拟内存。每个进程有4GB的虚拟地址空间。基于分页机制，这4GB地址空间的一些部分被映射了物理内存，一些部分映射硬盘上的交换文件，一些部分什么也没有映射。程序中使用的都是4GB地址空间中的虚拟地址。而访问物理内存，需要使用物理地址。<BR><BR>下面我们看看什么是物理地址，什么是虚拟地址。<BR><BR>物理地址 (physical address): 放在寻址总线上的地址。放在寻址总线上，如果是读，电路根据这个地址每位的值就将相应地址的物理内存中的数据放到数据总线中传输。如果是写，电路根据这个地址每位的值就将相应地址的物理内存中放入数据总线上的内容。物理内存是以字节(8位)为单位编址的。<BR><BR>虚拟地址 (virtual address): 4G虚拟地址空间中的地址，程序中使用的都是虚拟地址。<BR><BR>如果CPU寄存器中的分页标志位被设置，那么执行内存操作的机器指令时，CPU会自动根据页目录和页表中的信息，把虚拟地址转换成物理地址，完成该指令。比如 mov eax,004227b8h ，这是把地址004227b8h处的值赋给寄存器的汇编代码，004227b8这个地址就是虚拟址。CPU在执行这行代码时，发现寄存器中的分页标志位已经被设定，就自动完成虚拟地址到物理地址的转换，使用物理地址取出值，完成指令。对于Intel CPU 来说，分页标志位是寄存器CR0的第31位，为1表示使用分页，为0表示不使用分页。对于初始化之后的 Win2k 我们观察 CR0 ，发现第31位为1。表明Win2k是使用分页的。<BR><BR>&nbsp;&nbsp;&nbsp; 使用了分页机制之后，4G的地址空间被分成了固定大小的页，每一页或者被映射到物理内存，或者被映射到硬盘上的交换文件中，或者没有映射任何东西。对于一般程序来说，4G的地址空间，只有一小部分映射了物理内存，大片大片的部分是没有映射任何东西。物理内存也被分页，来映射地址空间。对于32bit的Win2k，页的大小是4K字节。CPU用来把虚拟地址转换成物理地址的信息存放在叫做页目录和页表的结构里。<BR><BR>&nbsp;&nbsp;&nbsp; 物理内存分页，一个物理页的大小为4K字节，第0个物理页从物理地址 0x00000000 处开始。由于页的大小为4KB，就是0x1000字节，所以第1页从物理地址 0x00001000 处开始。第2页从物理地址 0x00002000 处开始。可以看到由于页的大小是4KB，所以只需要32bit的地址中高20bit来寻址物理页。</FONT></P>
<P><FONT face=宋体>&nbsp;&nbsp;&nbsp; 页表，一个页表的大小为4K字节，放在一个物理页中。由1024个4字节的页表项组成。页表项的大小为4个字节(32bit)，所以一个页表中有1024个页表项。页表中的每一项的内容（每项4个字节,32bit）高20bit用来放一个物理页的物理地址，低12bit放着一些标志。<BR><BR>&nbsp;&nbsp;&nbsp; 页目录，一个页目录大小为4K字节，放在一个物理页中。由1024个4字节的页目录项组成。页目录项的大小为4个字节(32bit)，所以一个页目录中有1024个页目录项。页目录中的每一项的内容（每项4个字节）高20bit用来放一个页表（页表放在一个物理页中）的物理地址，低12bit放着一些标志。</FONT></P>
<P><FONT face=宋体>&nbsp;&nbsp;&nbsp; 对于x86系统，页目录的物理地址放在CPU的CR3寄存器中。<BR><BR>&nbsp;&nbsp;&nbsp; CPU把虚拟地址转换成物理地址：<BR>&nbsp;&nbsp;&nbsp; 一个虚拟地址，大小4个字节(32bit)，包含着找到物理地址的信息，分为3个部分：第22位到第31位这10位（最高10位）是页目录中的索引，第12位到第21位这10位是页表中的索引，第0位到第11位这12位（低12位）是页内偏移。对于一个要转换成物理地址的虚拟地址，CPU首先根据CR3中的值，找到页目录所在的物理页。然后根据虚拟地址的第22位到第31位这10位（最高的10bit)的值作为索引，找到相应的页目录项(PDE,page directory entry),页目录项中有这个虚拟地址所对应页表的物理地址。有了页表的物理地址，根据虚拟地址的第12位到第21位这10位的值作为索引，找到该页表中相应的页表项(PTE,page table entry),页表项中就有这个虚拟地址所对应物理页的物理地址。最后用虚拟地址的最低12位，也就是页内偏移，加上这个物理页的物理地址，就得到了该虚拟地址所对应的物理地址。</FONT></P>
<P><FONT face=宋体>&nbsp;&nbsp;&nbsp; 一个页目录有1024项，虚拟地址最高的10bit刚好可以索引1024项（2的10次方等于1024）。一个页表也有1024项，虚拟地址中间部分的10bit，刚好索引1024项。虚拟地址最低的12bit（2的12次方等于4096），作为页内偏移，刚好可以索引4KB，也就是一个物理页中的每个字节。</FONT></P>
<P><FONT face=宋体>&nbsp;&nbsp;&nbsp; 一个虚拟地址转换成物理地址的计算过程就是，处理器通过CR3找到当前页目录所在物理页，取虚拟地址的高10bit,然后把这10bit右移2bit（因为每个页目录项4个字节长，右移2bit相当于乘4）得到在该页中的地址，取出该地址处PDE（4个字节），就找到了该虚拟地址对应页表所在物理页，取虚拟地址第12位到第21位这10位，然后把这10bit右移2bit（因为每个页表项4个字节长，右移2bit相当于乘4）得到在该页中的地址，取出该地址处的PTE（4个字节），就找到了该虚拟地址对应物理页的地址，最后加上12bit的页内偏移得到了物理地址。<BR><BR>&nbsp;&nbsp;&nbsp; 32bit的一个指针，可以寻址范围0x00000000-0xFFFFFFFF,4GB大小。也就是说一个32bit的指针可以寻址整个4GB地址空间的每一个字节。一个页表项负责4K的地址空间和物理内存的映射，一个页表1024项，也就是负责1024*4k=4M的地址空间的映射。一个页目录项，对应一个页表。一个页目录有1024项，也就对应着1024个页表，每个页表负责4M地址空间的映射。1024个页表负责1024*4M=4G的地址空间映射。一个进程有一个页目录。所以以页为单位，页目录和页表可以保证4G的地址空间中的每页和物理内存的映射。<BR><BR>&nbsp;&nbsp;&nbsp; 每个进程都有自己的4G地址空间，从 0x00000000-0xFFFFFFFF 。通过每个进程自己的一套页目录和页表来实现。由于每个进程有自己的页目录和页表，所以每个进程的地址空间映射的物理内存是不一样的。两个进程的同一个虚拟地址处（如果都有物理内存映射）的值一般是不同的，因为他们往往对应不同的物理页。<BR><BR>&nbsp;&nbsp;&nbsp; 4G地址空间中低2G，0x00000000-0x7FFFFFFF 是用户地址空间，4G地址空间中高2G，<BR>0x80000000-0xFFFFFFFF 是系统地址空间。访问系统地址空间需要程序有ring0的权限。</FONT></P>
<P><FONT face=宋体></FONT></P>
<P>作者: <A href="mailto:jiurl@mail.china.com">JIURL</A></P><img src ="http://www.cppblog.com/ivenher/aggbug/1735.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-14 11:30 <a href="http://www.cppblog.com/ivenher/articles/1735.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>const用法</title><link>http://www.cppblog.com/ivenher/articles/1643.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 09 Dec 2005 02:54:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/1643.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/1643.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/1643.html#Feedback</comments><slash:comments>7</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/1643.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/1643.html</trackback:ping><description><![CDATA[<SPAN lang=EN-US style="FONT-SIZE: 9pt; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA">const</SPAN><SPAN style="FONT-SIZE: 9pt; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA">主要是为了程序的健壮型<SPAN lang=EN-US>,</SPAN>减少程序出错<SPAN lang=EN-US>.<BR></SPAN>最基本的用法<SPAN lang=EN-US>:<BR>const int a=100; b</SPAN>的内容不变<SPAN lang=EN-US>,b</SPAN>只能是<SPAN lang=EN-US>100</SPAN>也就是声明一个<SPAN lang=EN-US>int</SPAN>类型的常量<SPAN lang=EN-US>(#define b =100)<BR>int const b=100; //</SPAN>和上面作用一样<SPAN lang=EN-US>&nbsp;<BR><BR>const</SPAN>指针和引用一般用在函数的参数中<SPAN lang=EN-US><BR>int* m = &amp;a; //</SPAN>出错<SPAN lang=EN-US>,</SPAN>常量只能用常指针<SPAN lang=EN-US><BR>int c= 1;const int*pc = &amp;c;//</SPAN>常指针可指向常量<SPAN lang=EN-US>&nbsp;<BR><BR>const int* pa = &amp;a; //</SPAN>指针指向的内容为常量<SPAN lang=EN-US>(</SPAN>就是<SPAN lang=EN-US>b</SPAN>的值不变<SPAN lang=EN-US>)<BR>int const *a = &amp;b; //</SPAN>指针指向的内容为常量<SPAN lang=EN-US>(</SPAN>就是<SPAN lang=EN-US>b</SPAN>的值不变<SPAN lang=EN-US>)*p=3//error<BR>int* const a = &amp;b; //</SPAN>指针为常量<SPAN lang=EN-US>,</SPAN>不能更改指针了如<SPAN lang=EN-US> a++</SPAN>但可以改值<SPAN lang=EN-US>*p=3;&nbsp;<BR><BR></SPAN>从这可以看出<SPAN lang=EN-US>const</SPAN>放在<SPAN lang=EN-US>*</SPAN>左侧修饰的是指针的内容<SPAN lang=EN-US>,const</SPAN>放在<SPAN lang=EN-US>*</SPAN>右侧修饰的是指针<SPAN lang=EN-US><BR></SPAN>本身<SPAN lang=EN-US>.&nbsp;<BR><BR>const</SPAN>引用的用法和指针一样<SPAN lang=EN-US><BR>int const &amp; a=b; </SPAN>和指针一样<SPAN lang=EN-US><BR>const int&amp; a=b; </SPAN>和指针一样<SPAN lang=EN-US><BR></SPAN>但没有<SPAN lang=EN-US> int&amp; const a=b </SPAN>的用法因为引用不能做移位运算，但只是出个<SPAN lang=EN-US>warning&nbsp;<BR><BR>const int* const a = &amp;b; //</SPAN>综合应用<SPAN lang=EN-US>,</SPAN>一般用来传递多维的数组<SPAN lang=EN-US><BR></SPAN>类如：<SPAN lang=EN-US>char* init[] = {"Paris","in the","Spring"};<BR>void fun(const int* const a){}<BR>fun(init)//</SPAN>保护参数不被修改<SPAN lang=EN-US>&nbsp;<BR><BR>int A(int)const; //</SPAN>是常函数，只能用在类中，调用它的对象不能改改变成员值<SPAN lang=EN-US><BR>const int A(); //</SPAN>返回的是常量<SPAN lang=EN-US>,</SPAN>所以必须这么调用<SPAN lang=EN-US> cosnt int a=A();<BR>int A(const int); //</SPAN>参数不能改值<SPAN lang=EN-US>,</SPAN>可用在任意函数<SPAN lang=EN-US><BR>int A(const int*);<BR>....<BR>int height() const;//</SPAN>常函数只能由常函数调用<SPAN lang=EN-US><BR>int max(int,int) const;<BR>int Max = max(height(),height());&nbsp;<BR><BR>const int* pHeap = new int;<BR>delete pHeap;<BR>p = NULL;//</SPAN>出错<SPAN lang=EN-US><BR></SPAN>我的解决办法是强制类型转换<SPAN lang=EN-US><BR>const int* pHeap = new int(1);<BR>delete (int*)pHeap;<BR>pHeap = NULL;&nbsp;<BR><BR></SPAN>一、<SPAN lang=EN-US>const </SPAN>和引用联合使用的时候要注意<SPAN lang=EN-US>&nbsp;<BR><BR>const int a = 1;&nbsp;<BR>const int&amp; ref1 = a;<BR>const int&amp; ref2 = 1;&nbsp;<BR><BR>ref1 </SPAN>和<SPAN lang=EN-US> ref2 </SPAN>都是正确的，但是他们引用的内容和一般的引用不同<SPAN lang=EN-US><BR></SPAN>对<SPAN lang=EN-US> const int&amp; ref1 = a; </SPAN>而言，其实这个<SPAN lang=EN-US> ref1 </SPAN>已经和<SPAN lang=EN-US> a </SPAN>没有任何关系了<SPAN lang=EN-US><BR>ref1 </SPAN>实际上是对一个临时量的引用。同理<SPAN lang=EN-US> const int&amp; ref2 = 1; </SPAN>也是对<SPAN lang=EN-US><BR></SPAN>一个临时量做的引用。当引用临时量是<SPAN lang=EN-US> C++ </SPAN>的隐式类型转换可以起作用。<SPAN lang=EN-US><BR></SPAN>临时量的生存期和引用量的生存期相同。<SPAN lang=EN-US>&nbsp;<BR><BR></SPAN>二、强传<SPAN lang=EN-US>const</SPAN>对象可能导致无定义行为<SPAN lang=EN-US>&nbsp;<BR><BR></SPAN>对于优化做的比较好的编译器，代码<SPAN lang=EN-US> const int i = 1;<BR></SPAN>当后面用到变量<SPAN lang=EN-US> i </SPAN>的时候，编译器会优化掉对<SPAN lang=EN-US> i </SPAN>的存取，而直接使用立即数<SPAN lang=EN-US> 1&nbsp;<BR><BR>const int i = 1;&nbsp;<BR><BR>*(const_cast&lt;int*&gt;(&amp;i)) = 2;<BR>cout &lt;&lt; *(int*)&amp;i &lt;&lt; endl;<BR>cout &lt;&lt; i &lt;&lt; endl;&nbsp;<BR><BR></SPAN>所以，对<SPAN lang=EN-US> const </SPAN>对象做<SPAN lang=EN-US> const_cast </SPAN>可能导致无定义行为<SPAN lang=EN-US><BR></SPAN>目前我就遇到这些问题，那位还有补充的吗<SPAN lang=EN-US>&nbsp;<BR><BR><BR><BR><BR><BR></SPAN>能不能把自己的经验也谈谈。大家交流交流<SPAN lang=EN-US><BR></SPAN>这个就是我在调错时发现的<SPAN lang=EN-US><BR>int height() const;//</SPAN>常函数只能由常函数调用<SPAN lang=EN-US><BR>int max(int,int) const;<BR>int Max = max(height(),height());&nbsp;<BR><BR><BR><BR><BR><BR>Thinking again in C++</SPAN>（一）常量性原理<SPAN lang=EN-US> cphj</SPAN>（原作）<SPAN lang=EN-US>&nbsp;<BR></SPAN>有些地方很受启发<SPAN lang=EN-US>&nbsp;<BR><BR><BR>1.</SPAN>不能将<SPAN lang=EN-US>const</SPAN>修饰的任何对象、引用和指针作为赋值表达式的左值。<SPAN lang=EN-US><BR>const int cx=100;<BR>const int &amp; rcx=cx;<BR>const int * pcx=&amp;cx;<BR>cx=200; //error<BR>rcx=200; //error<BR>*pcx=200; //error&nbsp;<BR><BR>2.const</SPAN>类型的对象不能直接被<SPAN lang=EN-US>non-const</SPAN>类型的别名所引用。<SPAN lang=EN-US><BR>(1)</SPAN>不能将<SPAN lang=EN-US>const</SPAN>类型的对象传递给<SPAN lang=EN-US>non-const</SPAN>类型的引用。<SPAN lang=EN-US><BR>const int cx=100;<BR>int &amp; rx=cx; //error<BR>(2)</SPAN>不能将<SPAN lang=EN-US>const</SPAN>类型的实参传递给形参为<SPAN lang=EN-US>non-const</SPAN>类型引用的函数。<SPAN lang=EN-US><BR>void f(int a)<BR>{<BR>}<BR>void g(int &amp; ra)<BR>{<BR>}<BR>const int cx=100;<BR>f(cx); //ok<BR>g(cx); //error<BR>(3)</SPAN>不能将<SPAN lang=EN-US>const</SPAN>类型的对象作为<SPAN lang=EN-US>non-const</SPAN>类型引用的函数返回值。<SPAN lang=EN-US><BR>int &amp; f(const int &amp; rca)<BR>{<BR>return rca; //error<BR>}<BR>int x=100;<BR>f(x);&nbsp;<BR><BR>3.</SPAN>可以使用<SPAN lang=EN-US>const</SPAN>类型别名引用<SPAN lang=EN-US>non-const</SPAN>对象。此时通过<SPAN lang=EN-US>const</SPAN>引用不能修改对象，但对象可以通过<SPAN lang=EN-US>non-const</SPAN>引用被修改。<SPAN lang=EN-US><BR>int x=100;<BR>int &amp; rx=x;<BR>const int &amp; rcx=x; //ok<BR>x=200;<BR>rx=200;<BR>rcx=200; //error&nbsp;<BR><BR>4.</SPAN>指针的属性有两个：指针的类型和指针本身的常量性。其中，指向<SPAN lang=EN-US>const</SPAN>对象与指向<SPAN lang=EN-US>non-const</SPAN>对象，是不同的指针类型。<SPAN lang=EN-US><BR>int x=100;<BR>const int * pcx=&amp;x; //[1]<BR>int * px=&amp;x; //[2]<BR>int y=100;<BR>int * const cpy=&amp;y; //[3]<BR>int * py=&amp;y; //[4]<BR>[1][2]</SPAN>两个指针的类型不同；<SPAN lang=EN-US>[3][4]</SPAN>两个指针的常量性不同。<SPAN lang=EN-US><BR></SPAN>对象与指向对象的指针的规则类似于对象与引用。即，<SPAN lang=EN-US>const</SPAN>类型的对象不能直接被<SPAN lang=EN-US>non-const</SPAN>类型的指针所指示（同<SPAN lang=EN-US>2</SPAN>）；可以使用<SPAN lang=EN-US>const</SPAN>类型的指针指向<SPAN lang=EN-US>non-const</SPAN>对象（同<SPAN lang=EN-US>3</SPAN>）。<SPAN lang=EN-US>&nbsp;<BR><BR>5.</SPAN>可以将相同类型（包括常量性）的<SPAN lang=EN-US>const</SPAN>指针值赋给<SPAN lang=EN-US>non-const</SPAN>指针。<SPAN lang=EN-US><BR>int x=100;<BR>int * px;<BR>const int * pcx=&amp;x;<BR>px=pcx; //error<BR>int * const cpx=&amp;x;<BR>px=cpx; //ok&nbsp;<BR><BR>6.</SPAN>若函数的返回值为内建类型或是指针，则该返回值自动成为<SPAN lang=EN-US>const</SPAN>性质。但自定义类型则为<SPAN lang=EN-US>non-const</SPAN>性质。<SPAN lang=EN-US><BR>int f() //</SPAN>相当于返回<SPAN lang=EN-US>const int<BR>{<BR>return 100;<BR>}<BR>int * g(int &amp; ra) //</SPAN>相当于返回<SPAN lang=EN-US>int * const<BR>{<BR>return &amp;ra;<BR>}<BR>class CTest<BR>{<BR>int n;<BR>public:<BR>CTest(int n){this-&gt;n=n;}<BR>};<BR>CTest h() //</SPAN>返回的就是<SPAN lang=EN-US>CTest<BR>{<BR>return CTest(200);<BR>}&nbsp;<BR><BR>f()=200; //error&nbsp;<BR><BR>int x=100;<BR>int y=200;<BR>int * px=&amp;x;<BR>g(y)=px; //error<BR>*g(y)=x; //ok</SPAN>，从这点可以看出<SPAN lang=EN-US>g()</SPAN>返回的不是<SPAN lang=EN-US>const int *&nbsp;<BR><BR>CTest t(100);<BR>h()=t; //ok</SPAN>，但却是完全错误的、危险的做法<SPAN lang=EN-US><BR>//</SPAN>所以<SPAN lang=EN-US>h()</SPAN>的正确写法是返回<SPAN lang=EN-US>const CTest<BR><BR><BR><BR><BR><BR>const int b=100; b</SPAN>的内容不变<SPAN lang=EN-US>,b</SPAN>只能是<SPAN lang=EN-US>100<BR>int const b=100; b</SPAN>必须为<SPAN lang=EN-US>int</SPAN>型<SPAN lang=EN-US>,</SPAN>不能为其他类型<SPAN lang=EN-US>?<BR></SPAN>这<SPAN lang=EN-US>2</SPAN>句话的意思应该是一样的吧 ，<SPAN lang=EN-US> THINKING IN C++</SPAN>是这样说的<SPAN lang=EN-US><BR><BR><BR><BR><BR><BR>const int a=100; a</SPAN>的内容不变<SPAN lang=EN-US>,a</SPAN>只能是<SPAN lang=EN-US>100</SPAN>（同样不能类型转换）。<SPAN lang=EN-US><BR>int const b=100; b</SPAN>必须为<SPAN lang=EN-US>int</SPAN>型<SPAN lang=EN-US>,</SPAN>不能为其他类型<SPAN lang=EN-US>?</SPAN>（同样在使用中不能修改）。<SPAN lang=EN-US><BR></SPAN>所以<SPAN lang=EN-US>a</SPAN>和<SPAN lang=EN-US>b</SPAN>是一样的，称为整型常数，在使用中不能被修改，当然都不能转为其他类型了。<SPAN lang=EN-US>&nbsp;<BR>#include &lt;iostream&gt;&nbsp;<BR><BR>using namespace std;&nbsp;<BR><BR>int main()<BR>{<BR></SPAN>　　<SPAN lang=EN-US>const int a = 100;<BR></SPAN>　　<SPAN lang=EN-US>int const b = 100;&nbsp;<BR><BR></SPAN>　<SPAN lang=EN-US> a = 100; //</SPAN>这四条语句编译时都会出现“<SPAN lang=EN-US>Cannot modify a const object&nbsp;<BR>b = 100; //in function main()</SPAN>”的错误提示，也就是说，任何企图修改　　<SPAN lang=EN-US> a = 100.0;</SPAN>　<SPAN lang=EN-US>//a</SPAN>和<SPAN lang=EN-US>b</SPAN>（其实是一样的）的行为都会出现“灾难”，在语法上讲就　　<SPAN lang=EN-US>b = 100.0; //</SPAN>是<SPAN lang=EN-US>a</SPAN>和<SPAN lang=EN-US>b</SPAN>都不能出现在赋值语句的左边！<SPAN lang=EN-US>&nbsp;<BR><BR></SPAN>　　<SPAN lang=EN-US>cout&lt;&lt;'\n'&lt;&lt;a&lt;&lt;'\n'&lt;&lt;b&lt;&lt;endl;&nbsp;<BR><BR></SPAN>　　<SPAN lang=EN-US>return 0;<BR>}<BR><BR><BR><BR><BR><BR></SPAN>常函数的调用是这样的：常量对象只能调用常成员函数，非常量对象即可以调常成员函数，也可以调一般成员函数，但当某个函数有<SPAN lang=EN-US>const</SPAN>和非<SPAN lang=EN-US>const</SPAN>两个版本时，<SPAN lang=EN-US>const</SPAN>对象调<SPAN lang=EN-US>const</SPAN>版本，非<SPAN lang=EN-US>const</SPAN>对象调非<SPAN lang=EN-US>const</SPAN>版本<SPAN lang=EN-US><BR></SPAN>例：<SPAN lang=EN-US><BR>class A<BR>{<BR>public:<BR>int &amp; GetData(){return data;}<BR>const int &amp; GetData()const {return data;}<BR>private:<BR>int data;<BR>}&nbsp;<BR>A a;<BR>a.GetData();//</SPAN>调用<SPAN lang=EN-US>int &amp; GetData(){return data;}<BR>//</SPAN>但如果没有这个函数，也可以调用<SPAN lang=EN-US>const int &amp; GetData()const&nbsp;<BR>const A const_a;<BR>const_a.GetData();//</SPAN>调用<SPAN lang=EN-US>const int &amp; GetData()const {return data;}<BR></SPAN>常函数只能调常函数，也是由于这个原因<SPAN lang=EN-US><BR><BR><BR><BR><BR><BR></SPAN>算你狠！加两点<SPAN lang=EN-US><BR><BR></SPAN>一、<SPAN lang=EN-US>const </SPAN>和引用联合使用的时候要注意<SPAN lang=EN-US>&nbsp;<BR><BR>const int a = 1;&nbsp;<BR>const int&amp; ref1 = a;<BR>const int&amp; ref2 = 1;&nbsp;<BR><BR>ref1 </SPAN>和<SPAN lang=EN-US> ref2 </SPAN>都是正确的，但是他们引用的内容和一般的引用不同<SPAN lang=EN-US><BR></SPAN>对<SPAN lang=EN-US> const int&amp; ref1 = a; </SPAN>而言，其实这个<SPAN lang=EN-US> ref1 </SPAN>已经和<SPAN lang=EN-US> a </SPAN>没有任何关系了<SPAN lang=EN-US><BR>ref1 </SPAN>实际上是对一个临时量的引用。同理<SPAN lang=EN-US> const int&amp; ref2 = 1; </SPAN>也是对<SPAN lang=EN-US><BR></SPAN>一个临时量做的引用。当引用临时量是<SPAN lang=EN-US> C++ </SPAN>的隐式类型转换可以起作用。<SPAN lang=EN-US><BR></SPAN>临时量的生存期和引用量的生存期相同。<SPAN lang=EN-US>&nbsp;<BR><BR></SPAN>二、强传<SPAN lang=EN-US>const</SPAN>对象可能导致无定义行为<SPAN lang=EN-US>&nbsp;<BR><BR></SPAN>对于优化做的比较好的编译器，代码<SPAN lang=EN-US> const int i = 1;<BR></SPAN>当后面用到变量<SPAN lang=EN-US> i </SPAN>的时候，编译器会优化掉对<SPAN lang=EN-US> i </SPAN>的存取，而直接使用立即数<SPAN lang=EN-US> 1&nbsp;<BR><BR>const int i = 1;&nbsp;<BR><BR>*(const_cast&lt;int*&gt;(&amp;i)) = 2;<BR>cout &lt;&lt; *(int*)&amp;i &lt;&lt; endl;<BR>cout &lt;&lt; i &lt;&lt; endl;&nbsp;<BR><BR></SPAN>所以，对<SPAN lang=EN-US> const </SPAN>对象做<SPAN lang=EN-US> const_cast </SPAN>可能导致无定义行为<SPAN lang=EN-US><BR><BR><BR><BR><BR><BR>#include &lt;iostream.h&gt;<BR>void fun(char b){cout &lt;&lt;"void"&lt;&lt;endl;}<BR>int fun(int const b){cout &lt;&lt;"int"&lt;&lt;endl;}&nbsp;<BR>int main()<BR>{<BR>fun(1.0);//</SPAN>详细看看重载函数吧<SPAN lang=EN-US>&nbsp;<BR>fun(4); //</SPAN>想一想调用哪一个<SPAN lang=EN-US>&nbsp;<BR><BR>return 0;<BR>}<BR></SPAN>我试了一下，会出错<SPAN lang=EN-US>? vc</SPAN>说：<SPAN lang=EN-US>'fun':ambiguous call to overloaded function&nbsp;<BR><BR><BR><BR><BR><BR></SPAN>补充的好啊，这个一般不会注意的<SPAN lang=EN-US><BR>const int i = 1;<BR>*(const_cast&lt;int*&gt;(&amp;i)) = 2;<BR>cout &lt;&lt; *(int*)&amp;i &lt;&lt; endl;<BR>cout &lt;&lt; i &lt;&lt; endl;<BR></SPAN>这个可真有意思，调试时两个都是<SPAN lang=EN-US>2</SPAN>，可编译就是<SPAN lang=EN-US>2</SPAN>，<SPAN lang=EN-US>1</SPAN>了<SPAN lang=EN-US><BR>const</SPAN>的永远都是<SPAN lang=EN-US>const,</SPAN>这样能更改就不错了，不然就自相矛盾了<SPAN lang=EN-US><BR></SPAN>奇怪的是<SPAN lang=EN-US> pi </SPAN>和<SPAN lang=EN-US> &amp;i</SPAN>地址一样啊，就像楼上说的这是编译时的优化<SPAN lang=EN-US><BR></SPAN>处理<SPAN lang=EN-US><BR>const int i = 1;<BR>int* pi=const_cast&lt;int*&gt;(&amp;i);<BR>*pi=2;<BR>cout &lt;&lt; *pi &lt;&lt; endl;<BR>cout &lt;&lt; i &lt;&lt; endl;&nbsp;<BR><BR><BR><BR><BR><BR></SPAN>那个主要是隐式转换<SPAN lang=EN-US><BR></SPAN>你可依次把两个函数注掉看看调用<SPAN lang=EN-US><BR>#include &lt;iostream.h&gt;<BR>//void fun(char b){cout &lt;&lt;"void"&lt;&lt;endl;}<BR>void fun(int b){cout &lt;&lt;"int"&lt;&lt;endl;}<BR>int main()<BR>{<BR>fun('a');<BR>fun(4);&nbsp;<BR>return 0;<BR>} </SPAN></SPAN><img src ="http://www.cppblog.com/ivenher/aggbug/1643.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-12-09 10:54 <a href="http://www.cppblog.com/ivenher/articles/1643.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>操作内存 </title><link>http://www.cppblog.com/ivenher/articles/1069.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 11 Nov 2005 05:11:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/1069.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/1069.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/1069.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/1069.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/1069.html</trackback:ping><description><![CDATA[<TABLE height="100%" cellSpacing=0 cellPadding=10 width=800 border=0>
<TBODY>
<TR>
<TD vAlign=top>
<TABLE>
<TBODY>
<TR>
<TD></TD></TR></TBODY></TABLE></TD>
<TD vAlign=top width="100%"><FONT color=yellow size=4>操作内存</FONT> 
<P><FONT size=5>
<P>在前面的章节中，我们已经了解了寄存器的基本使用方法。而正如结尾提到的那样，仅仅使用寄存器做一点运算是没有什么太大意义的，毕竟它们不能保存太多的数据，因此，对编程人员而言，他肯定迫切地希望访问内存，以保存更多的数据。</P>
<P>我将分别介绍如何在保护模式和实模式操作内存，然而在此之前，我们先熟悉一下这两种模式中内存的结构。</P>
<H3>3.1 实模式</H3>
<P>事实上，在实模式中，内存比保护模式中的结构更令人困惑。内存被分割成段，并且，操作内存时，需要指定段和偏移量。不过，理解这些概念是非常容易的事情。请看下面的图：</P>
<P align=center><IMG height=147 src="file:///C:/My%20Documents/简明x86汇编语言教程(4)[修订版].files/realmem.gif" width=531 border=0></P>
<P>段-寄存器这种格局是早期硬件电路限制留下的一个伤疤。地址总线在当时有20-bit。</P>
<P>然而20-bit的地址不能放到16-bit的寄存器里，这意味着有4-bit必须放到别的地方。因此，为了访问所有的内存，必须使用两个16-bit寄存器。</P>
<P>这一设计上的折衷方案导致了今天的段-偏移量格局。最初的设计中，其中一个寄存器只有4-bit有效，然而为了简化程序，两个寄存器都是16-bit有效，并在执行时求出加权和来标识20-bit地址。</P>
<P>偏移量是16-bit的，因此，一个段是64KB。下面的图可以帮助你理解20-bit地址是如何形成的：</P>
<P align=center><IMG height=177 src="file:///C:/My%20Documents/简明x86汇编语言教程(4)[修订版].files/segoffcal.gif" width=233 border=0></P>
<P>段-偏移量标识的地址通常记做 <B>段:偏移量 </B>的形式。</P>
<P>由于这样的结构，一个内存有多个对应的地址。例如，0000:0010和0001:0000指的是同一内存地址。又如，</P>
<P>0000:1234 = 0123:0004 = 0120:0034 = 0100:0234<BR>0001:1234 = 0124:0004 = 0120:0044 = 0100:0244</P>
<P>作为负面影响之一，在段上加1相当于在偏移量上加16，而不是一个“全新”的段。反之，在偏移量上加16也和在段上加1等价。某些时候，据此认为段的“粒度”是16字节。</P>
<P><B>练习题<BR></B>尝试一下将下面的地址转化为20bit的地址：</P>
<TABLE class=code id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%">2EA8:D678 26CF:8D5F 453A:CFAD 2933:31A6 5924:DCCF<BR>694E:175A 2B3C:D218 728F:6578 68E1:A7DC 57EC:AEEA</TD></TR></TBODY></TABLE>
<P>稍高一些的要求是，写一个程序将段为AX、偏移量为BX的地址转换为20bit的地址，并保存于EAX中。</P>
<P>[<SPAN class=tip id=Answer1>上面习题的答案</SPAN>]</P>
<P dir=ltr>我们现在可以写一个真正的程序了。</P>
<P dir=ltr>经典程序：Hello, world</P>
<TABLE class=code id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD vAlign=top align=left width="50%"><FONT color=#008000>;;; 应该得到一个29字节的.com文件</FONT><BR><BR>.MODEL TINY<BR>.CODE <BR><BR>CR equ 13 <BR>LF equ 10 <BR>TERMINATOR equ '$' <BR><BR>ORG 100h <BR><BR>Main PROC<BR>mov dx,offset sMessage <BR>mov ah,9<BR>int 21h <BR>mov ax,4c00h<BR>int 21h<BR>Main ENDP<BR><BR>sMessage:<BR>&nbsp; DB 'Hello, World!'<BR>&nbsp; DB CR,LF,TERMINATOR<BR><BR>END Main</TD>
<TD vAlign=top align=left width="50%"><FONT color=#008000><BR><BR>; .COM文件的内存模型是‘TINY’<BR>; 代码段开始<BR><BR>; 回车<BR>; 换行<BR>; DOS字符串结束符<BR><BR>; 代码起始地址为CS:0100h<BR><BR><BR>; 令DS:DX指向Message<BR>; int 21h(DOS中断)功能9 -<BR>; 显示字符串到标准输出设备<BR>; int 21h功能4ch -<BR>; 终止程序并返回AL的错误代码<BR>　</FONT> 
<P>　</P>
<P><FONT color=#008000><BR>; 程序结束的同时指定入口点为Main</FONT></P></TD></TR></TBODY></TABLE>
<P>那么，我们需要解释很多东西。</P>
<P>首先，作为汇编语言的抽象，C语言拥有“指针”这个数据类型。在汇编语言中，几乎所有对内存的操作都是由对给定地址的内存进行访问来完成的。这样，在汇编语言中，绝大多数操作都要和指针产生或多或少的联系。</P>
<P>这里我想强调的是，由于这一特性，汇编语言中同样会出现C程序中常见的缓冲区溢出问题。如果你正在设计一个与安全有关的系统，那么最好是仔细检查你用到的每一个串，例如，它们是否一定能够以你预期的方式结束，以及（如果使用的话）你的缓冲区是否能保证实际可能输入的数据不被写入到它以外的地方。作为一个汇编语言程序员，你有义务检查每一行代码的可用性。</P>
<P>程序中的equ伪指令是宏汇编特有的，它的意思接近于C或Pascal中的const（常量）。多数情况下，equ伪指令并不为符号分配空间。</P>
<P>此外，汇编程序执行一项操作是非常繁琐的，通常，在对与效率要求不高的地方，我们习惯使用系统提供的中断服务来完成任务。例如本例中的中断21h，它是DOS时代的中断服务，在Windows中，它也被认为是Windows API的一部分（这一点可以在Microsoft的文档中查到）。中断可以被理解为高级语言中的子程序，但又不完全一样——中断使用系统栈来保存当前的机器状态，可以由硬件发起，通过修改机器状态字来反馈信息，等等。</P>
<P>那么，最后一段通过DB存放的数据到底保存在哪里了呢？答案是紧挨着代码存放。在汇编语言中，DB和普通的指令的地位是相同的。如果你的汇编程序并不知道新的助记符（例如，新的处理器上的CPUID指令），而你很清楚，那么可以用DB 机器码的方式强行写下指令。这意味着，你可以超越汇编器的能力撰写汇编程序，然而，直接用机器码编程是几乎肯定是一件费力不讨好的事——汇编器厂商会经常更新它所支持的指令集以适应市场需要，而且，你可以期待你的汇编其能够产生正确的代码，因为机器查表是不会出错的。既然机器能够帮我们做将程序转换为代码这件事情，那么为什么不让它来做呢？</P>
<P>细心的读者不难发现，在程序中我们没有对DS进行赋值。那么，这是否意味着程序的结果将是不可预测的呢？答案是否定的。DOS（或Windows中的MS-DOS VM）在加载.com文件的时候，会对寄存器进行很多初始化。.com文件被限制为小于64KB，这样，它的代码段、数据段都被装入同样的数值（即，初始状态下DS=CS）。</P>
<P>也许会有人说，“嘿，这听起来不太好，一个64KB的程序能做得了什么呢？还有，你吹得天花乱坠的堆栈段在什么地方？”那么，我们来看看下面这个新的Hello world程序，它是一个EXE文件，在DOS实模式下运行。</P>
<TABLE class=code id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD vAlign=top align=left width="50%"><FONT color=#008000>;;; 应该得到一个561 字节的EXE文件</FONT><BR><BR>.MODEL SMALL<BR>.STACK 200h<BR><BR>CR equ 13<BR>LF equ 10<BR>TERMINATOR equ '$'<BR><BR>.DATA<BR><BR>Message DB 'Hello, World !'<BR>&nbsp; DB CR,LF,TERMINATOR<BR><BR>.CODE<BR><BR>Main PROC<BR>mov ax, DGROUP<BR>mov ds, ax<BR><BR>mov dx, offset Message<BR>mov ah, 9<BR>int 21h<BR><BR>mov ax, 4c00h<BR>int 21h<BR>Main ENDP<BR><BR>END main</TD>
<TD vAlign=top align=left width="50%">　 
<P><FONT color=#008000>; 采用“SMALL”内存模型<BR>; 堆栈段</FONT></P>
<P><FONT color=#008000>; 回车<BR>; 换行<BR>; DOS字符串结束符</FONT></P>
<P><FONT color=#008000>; 定义数据段</FONT></P>
<P><FONT color=#008000>; 定义显示串</FONT></P>
<P><FONT color=#008000>; 定义代码段</FONT></P>
<P><FONT color=#008000><BR>; 将数据段<BR>; 加载到DS寄存器</FONT></P>
<P><FONT color=#008000>; 设置DX<BR>; 显示</FONT></P>
<P><FONT color=#008000><BR>; 终止程序</FONT></P></TD></TR></TBODY></TABLE>
<P>561字节？实现相同功能的程序大了这么多！为什么呢？我们看到，程序拥有了完整的堆栈段、数据段、代码段，其中堆栈段足足占掉了512字节，其余的基本上没什么变化。</P>
<P>分成多个段有什么好处呢？首先，它让程序显得更加清晰——你肯定更愿意看一个结构清楚的程序，代码中hard-coded的字符串、数据让人觉得费解。比如，mov dx, 0152h肯定不如mov dx, offset Message来的亲切。此外，通过分段你可以使用更多的内存，比如，代码段腾出的空间可以做更多的事情。exe文件另一个吸引人的地方是它能够实现“重定位”。现在你不需要指定程序入口点的地址了，因为系统会找到你的程序入口点，而不是死板的100h。</P>
<P>程序中的符号也会在系统加载的时候重新赋予新的地址。exe程序能够保证你的设计容易地被实现，不需要考虑太多的细节。</P>
<P>当然，我们的主要目的是将汇编语言作为高级语言的一个有用的补充。如我在开始提到的那样，真正完全用汇编语言实现的程序不一定就好，因为它不便于维护，而且，由于结构的原因，你也不太容易确保它是正确的；汇编语言是一种非结构化的语言，调试一个精心设计的汇编语言程序，即使对于一个老手来说也不啻是一场恶梦，因为你很可能掉到别人预设的“陷阱”中——这些技巧确实提高了代码性能，然而你很可能不理解它，于是你把它改掉，接着就发现程序彻底败掉了。使用汇编语言加强高级语言程序时，你要做的通常只是使用汇编指令，而不必搭建完整的汇编程序。绝大多数（也是目前我遇到的全部）C/C++编译器都支持内嵌汇编，即在程序中使用汇编语言，而不必撰写单独的汇编语言程序——这可以节省你的不少精力，因为前面讲述的那些伪指令，如equ等，都可以用你熟悉的高级语言方式来编写，编译器会把它转换为适当的形式。</P>
<P>需要说明的是，在高级语言中一定要注意编译结果。编译器会对你的汇编程序做一些修改，这不一定符合你的要求（附带说一句，有时编译器会很聪明地调整指令顺序来提高性能，这种情况下最好测试一下哪种写法的效果更好），此时需要做一些更深入的修改，或者用db来强制编码。</P>
<H3>3.2 保护模式</H3>
<P>实模式的东西说得太多了，尽管我已经删掉了许多东西，并把一些原则性的问题拿到了这一节讨论。这样做不是没有理由的——保护模式才是现在的程序（除了操作系统的底层启动代码）最常用的CPU模式。保护模式提供了很多令人耳目一新的功能，包括内存保护（这是保护模式这个名字的来源）、进程支持、更大的内存支持，等等。</P>
<P>对于一个编程人员来说，能“偷懒”是一件令人愉快的事情。这里“偷懒”是说把“应该”由系统做的事情做的事情全都交给系统。为什么呢？这出自一个基本思想——人总有犯错误的时候，然而规则不会，正确地了解规则之后，你可以期待它像你所了解的那样执行。对于C程序来说，你自己用C语言写的实现相同功能的函数通常没有系统提供的函数性能好（除非你用了比函数库好很多的算法），因为系统的函数往往使用了更好的优化，甚至可能不是用C语言直接编写的。</P>
<P>当然，“偷懒”的意思是说，把那些应该让机器做的事情交给计算机来做，因为它做得更好。我们应该把精力集中到设计算法，而不是编写源代码本身上，因为编译器几乎只能做等价优化，而实现相同功能，但使用更好算法的程序实现，则几乎只能由人自己完成。</P>
<P>举个例子，这样一个函数：</P>
<TABLE class=code id=AutoNumber3 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%"><FONT color=#0000ff>int</FONT> fun(){<BR>&nbsp; <FONT color=#0000ff>int</FONT> a=0;<BR>&nbsp; <FONT color=#0000ff>register int </FONT>i;<BR>&nbsp; <FONT color=#0000ff>for</FONT>(i=0; i&lt;1000; i++) a+=i;<BR>&nbsp; <FONT color=#0000ff>return</FONT> a;<BR>}</TD></TR></TBODY></TABLE>
<P>在某种编译模式[DEBUG]下被编译为</P>
<TABLE class=code id=AutoNumber4 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="50%">push ebp<BR>mov ebp,esp<BR>sub esp,48h<BR>push ebx<BR>push esi<BR>push edi<BR>lea edi,[ebp-48h]<BR>mov ecx,12h<BR>mov eax,0CCCCCCCCh<BR>rep stos dword ptr [edi]<BR>mov dword ptr [ebp-4],0<BR>mov dword ptr [ebp-8],0<BR>jmp fun+31h<BR>mov eax,dword ptr [ebp-8]<BR>add eax,1<BR>mov dword ptr [ebp-8],eax<BR>cmp dword ptr [ebp-8],3E8h<BR>jge fun+45h<BR>mov ecx,dword ptr [ebp-4]<BR>add ecx,dword ptr [ebp-8]<BR>mov dword ptr [ebp-4],ecx<BR>jmp fun+28h<BR>mov eax,dword ptr [ebp-4]<BR>pop edi<BR>pop esi<BR>pop ebx<BR>mov esp,ebp<BR>pop ebp<BR>ret</TD>
<TD vAlign=top width="50%"><FONT color=#008000>; 子程序入口</FONT> 
<P><FONT color=#008000><BR>; 保护现场<BR><BR><BR>; 初始化变量-调试版本特有。<BR>; 本质是在堆中挖一块地儿，存CCCCCCCC。<BR>; 用串操作进行，这将发挥Intel处理器优势<BR>; ‘a=0’<BR>; ‘i=0’</FONT></P>
<P><FONT color=#008000>; 走着<BR>; i++<BR><BR><BR>; i&lt;1000?<BR><BR><BR>; a+=i;<BR><BR>; return a;</FONT></P>
<P><FONT color=#008000>; 恢复现场</FONT></P>
<P>　</P>
<P><FONT color=#008000>; 返回</FONT></P></TD></TR></TBODY></TABLE>
<P>而在另一种模式[RELEASE/MINSIZE]下却被编译为</P>
<TABLE class=code id=AutoNumber5 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="50%">xor eax,eax<BR>xor ecx,ecx<BR>add eax,ecx<BR>inc ecx<BR>cmp ecx,3E8h<BR>jl fun+4<BR>ret</TD>
<TD vAlign=top width="50%">
<P dir=ltr><FONT color=#008000>; a=0;<BR>; i=0;<BR>; a+=i;<BR>; i++;<BR>; i&lt;1000?<BR>; 是-&gt;继续继续<BR>; return a</FONT></P></TD></TR></TBODY></TABLE>
<P>如果让我来写，多半会写成</P>
<TABLE class=code id=AutoNumber6 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="50%">mov eax, 079f2ch<BR>ret</TD>
<TD width="50%">
<P dir=ltr><FONT color=#008000>; return 499500</FONT></P></TD></TR></TBODY></TABLE>
<P>为什么这样写呢？我们看到，i是一个外界不能影响、也无法获知的内部状态量。作为这段程序来说，对它的计算对于结果并没有直接的影响——它的存在不过是方便算法描述而已。并且我们看到的，这段程序实际上无论执行多少次，其结果都不会发生变化，因此，直接返回计算结果就可以了，计算是多余的（如果说一定要算，那么应该是编译器在编译过程中完成它）。</P>
<P>更进一步，我们甚至希望编译器能够直接把这个函数变成一个符号常量，这样连操作堆栈的过程也省掉了。</P>
<P>第三种结果属于“等效”代码，而不是“等价”代码。作为用户，很多时候是希望编译器这样做的，然而由于目前的技术尚不成熟，有时这种做法会造成一些问题（gcc和g++的顶级优化可以造成编译出的FreeBSD内核行为异常，这是我在FreeBSD上遇到的唯一一次软件原因的kernel panic），因此，并不是所有的编译器都这样做（另一方面的原因是，如果编译器在这方面做的太过火，例如自动求解全部“固定”问题，那么如果你的程序是解决固定的问题“很大”，如求解迷宫，那么在编译过程中你就会找锤子来砸计算机了）。然而，作为编译器制造商，为了提高自己的产品的竞争力，往往会使用第三种代码来做函数库。正如前面所提到的那样，这种优化往往不是编译器本身的作用，尽管现代编译程序拥有编译执行、循环代码外提、无用代码去除等诸多优化功能，但它都不能保证程序最优。最后一种代码恐怕很少有编译器能够做到，不信你可以用自己常用的编译器加上各种优化选项试试:)</P>
<P>发现什么了吗？三种代码中，对于内存的访问一个比一个少。这样做的理由是，尽可能地利用寄存器并减少对内存的访问，可以提高代码性能。在某些情况下，使代码既小又快是可能的。</P>
<P>书归正传，我们来说说保护模式的内存模型。保护模式的内存和实模式有很多共同之处。</P>
<P align=center><IMG height=190 src="file:///C:/My%20Documents/简明x86汇编语言教程(4)[修订版].files/protmem.gif" width=519 border=0></P>
<P>毫无疑问，以'protected mode'(保护模式), 'global descriptor table'(全局描述符表), 'local descriptor table'(本地描述符表)和'selector'(选择器)搜索，你会得到完整介绍它们的大量信息。</P>
<P>保护模式与实模式的内存类似，然而，它们之间最大的区别就是保护模式的内存是“线性”的。</P>
<P>新的计算机上，32-bit的寄存器已经不是什么新鲜事（如果你哪天听说你的CPU的寄存器不是32-bit的，那么它——简直可以肯定地说——的字长要比32-bit还要多。新的个人机上已经开始逐步采用64-bit的CPU了），换言之，实际上段/偏移量这一格局已经不再需要了。尽管如此，在继续看保护模式内存结构时，仍请记住段/偏移量的概念。不妨把段寄存器看作对于保护模式中的选择器的一个模拟。选择器是全局描述符表(Global Descriptor Table, GDT)或本地描述符表(Local Descriptor Table, LDT)的一个指针。</P>
<P>如图所示，GDT和LDT的每一个项目都描述一块内存。例如，一个项目中包含了某块被描述的内存的物理的基地址、长度，以及其他一些相关信息。</P>
<P>保护模式是一个非常重要的概念，同时也是目前撰写应用程序时，最常用的CPU模式（运行在新的计算机上的操作系统很少有在实模式下运行的）。</P>
<P>为什么叫保护模式呢？它“保护”了什么？答案是进程的内存。保护模式的主要目的在于允许多个进程同时运行，并保护它们的内存不受其他进程的侵犯。这有点类似于C++中的机制，然而它的强制力要大得多。如果你的进程在保护模式下以不恰当的方式访问了内存（例如，写了“只读”内存，或读了不可读的内存，等等），那么CPU就会产生一个异常。这个异常将交给操作系统处理，而这种处理，假如你的程序没有特别说明操作系统该如何处理的话，一般就是杀掉做错了事情的进程。</P>
<P>我像这样的对话框大家一定非常熟悉（临时写了一个程序故意造成的错误）：</P>
<P align=center><IMG height=139 src="file:///C:/My%20Documents/简明x86汇编语言教程(4)[修订版].files/null.gif" width=446 border=0></P>
<P>好的，只是一个程序崩溃了，而操作系统的其他进程照常运行（同样的程序在DOS中几乎是板上钉钉的死机，因为NULL指针的位置恰好是中断向量表），你甚至还可以调试它。</P>
<P>保护模式还有其他很多好处，在此就不一一赘述了。实模式和保护模式之间的切换问题我打算放在后面的“高级技巧”一章来讲，因为多数程序并不涉及这个。</P>
<P>了解了内存的格局，我们就可以进入下一节——操作内存了。</P>
<H3>3.3 操作内存</H3>
<P>前两节中，我们介绍了实模式和保护模式中使用的不同的内存格局。现在开始解释如何使用这些知识。</P>
<P>回忆一下前面我们说过的，寄存器可以用作内存指针。现在，是他们发挥作用的时候了。</P>
<P>可以将内存想象为一个顺序的字节流。使用指针，可以任意地操作（读写）内存。</P>
<P>现在我们需要一些其他的指令格式来描述对于内存的操作。操作内存时，首先需要的就是它的地址。</P>
<P>让我们来看看下面的代码：</P>
<TABLE class=code id=AutoNumber7 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%">mov ax,[0]</TD></TR></TBODY></TABLE>
<P>方括号表示，里面的表达式指定的不是立即数，而是偏移量。在实模式中，DS:0中的那个字（16-bit长）将被装入AX。</P>
<P>然而0是一个常数，如果需要在运行的时候加以改变，就需要一些特殊的技巧，比如程序自修改。汇编支持这个特性，然而我个人并不推荐这种方法——自修改大大降低程序的可读性，并且还降低稳定性，性能还不一定好。我们需要另外的技术。</P>
<TABLE class=code id=AutoNumber8 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%">mov bx,0<BR>mov ax,[bx]</TD></TR></TBODY></TABLE>
<P>看起来舒服了一些，不是吗？BX寄存器的内容可以随时更改，而不需要用冗长的代码去修改自身，更不用担心由此带来的不稳定问题。</P>
<P>同样的，mov指令也可以把数据保存到内存中：</P>
<TABLE class=code id=AutoNumber9 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%">mov [0],ax</TD></TR></TBODY></TABLE>
<P>在存储器与寄存器之间交换数据应该足够清楚了。</P>
<P>有些时候我们会需要操作符来描述内存数据的宽度：</P>
<TABLE id=AutoNumber10 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=1>
<TBODY>
<TR>
<TD align=middle width="50%" bgColor=#000080><FONT color=#ffffff>操作符</FONT></TD>
<TD align=middle width="50%" bgColor=#000080><FONT color=#ffffff>意义</FONT></TD></TR>
<TR>
<TD align=middle width="50%">byte ptr</TD>
<TD align=middle width="50%">一个字节(8-bit, 1 byte)</TD></TR>
<TR>
<TD align=middle width="50%" bgColor=#e0e0e0>word ptr</TD>
<TD align=middle width="50%" bgColor=#e0e0e0>一个字(16-bit)</TD></TR>
<TR>
<TD align=middle width="50%">dword ptr</TD>
<TD align=middle width="50%">一个双字(32-bit)</TD></TR></TBODY></TABLE>
<P>例如，在DS:100h处保存1234h，以字存放：</P>
<TABLE class=code id=AutoNumber11 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%">mov word ptr [100h],01234h</TD></TR></TBODY></TABLE>
<P>于是我们将mov指令扩展为：</P>
<TABLE class=code id=AutoNumber12 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%">mov reg(8,16,32), mem(8,16,32)<BR>mov mem(8,16,32), reg(8,16,32)<BR>mov mem(8,16,32), imm(8,16,32)</TD></TR></TBODY></TABLE>
<P>需要说明的是，加减同样也可以在[]中使用，例如：</P>
<TABLE class=code id=AutoNumber13 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%">mov ax,[bx+10]<BR>mov ax,[bx+si]<BR>mov ax,es:[di+bp]</TD></TR></TBODY></TABLE>
<P>等等。我们看到，对于内存的操作，即使使用MOV指令，也有许多种可能的方式。下一节中，我们将介绍如何操作串。</P>
<P style="FONT-SIZE: 9pt"><FONT color=#ff0000>感谢</FONT> 网友 水杉 指出此答案中的一处错误。<BR><FONT color=#ff0000>感谢</FONT> Heallven 指出.COM程序实例编译失败的问题</P><?xml:namespace prefix = tool /><tool:tip avoidmouse="false" element="Answer1">
<P>2EA8:D678 -&gt; 物理的 3C0F8<BR>694E:175A -&gt; 物理的 6AC4A<BR>26CF:8D5F -&gt; 物理的 2FA4F<BR>2B3C:D218 -&gt; 物理的 385E8<BR>453A:CFAD -&gt; 物理的 5235D<BR>728F:6578 -&gt; 物理的 78E68<BR>2933:31A6 -&gt; 物理的 2C4D6<BR>68E1:A7DC -&gt; 物理的 735FC</P>
<P><B>编程</B><BR>shl eax,4<BR>add eax,bx</P></tool:tip></FONT></TD></TR></TBODY></TABLE><img src ="http://www.cppblog.com/ivenher/aggbug/1069.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-11-11 13:11 <a href="http://www.cppblog.com/ivenher/articles/1069.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用寄存器 </title><link>http://www.cppblog.com/ivenher/articles/1068.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 11 Nov 2005 05:09:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/1068.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/1068.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/1068.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/1068.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/1068.html</trackback:ping><description><![CDATA[<FONT color=#ffff00 size=4>使用寄存器</FONT> 
<P><FONT size=5>
<TABLE cellSpacing=0 cellPadding=0 width="98%" align=center border=0>
<TBODY>
<TR>
<TD align=middle width="100%"><BR></TD></TR>
<TR>
<TD align=middle width="100%">
<TABLE style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 align=center border=0>
<TBODY>
<TR>
<TD style="WORD-WRAP: break-word"><FONT class=news>
<H3>2.2 使用寄存器</H3>
<P>在前一节中的x86基本寄存器的介绍，对于一个汇编语言编程人员来说是不可或缺的。现在你知道，寄存器是处理器内部的一些保存数据的存储单元。仅仅了解这些是不足以写出一个可用的汇编语言程序的，但你已经可以大致读懂一般汇编语言程序了（不必惊讶，因为汇编语言的祝记符和英文单词非常接近），因为你已经了解了关于基本寄存器的绝大多数知识。</P>
<P>在正式引入第一个汇编语言程序之前，我粗略地介绍一下汇编语言中不同进制整数的表示方法。如果你不了解十进制以外的其他进制，请把鼠标移动到<SPAN class=tip id=oRadixes>这里</SPAN>。</P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0><BR><B>　　汇编语言中的整数常量表示</B> 
<UL type=square>
<LI><B>十进制整数<BR></B>这是汇编器默认的数制。直接用我们熟悉的表示方式表示即可。例如，1234表示十进制的1234。不过，如果你指定了使用其他数制，或者有凡事都进行完整定义的小爱好，也可以写成[十进制数]d或[十进制数]D的形式。 
<LI><B>十六进制数<BR></B>这是汇编程序中最常用的数制，我个人比较偏爱使用十六进制表示数据，至于为什么，以后我会作说明。十六进制数表示为0[十六进制数]h或0[十六进制数]H，其中，如果十六进制数的第一位是数字，则开头的0可以省略。例如，7fffh, 0ffffh，等等。 
<LI><B>二进制数<BR></B>这也是一种常用的数制。二进制数表示为[二进制数]b或[二进制数]B。一般程序中用二进制数表示掩码（mask code）等数据非常的直观，但需要些很长的数据（4位二进制数相当于一位十六进制数）。例如，1010110b。 
<LI><B>八进制数<BR></B>八进制数现在已经不是很常用了（确实还在用，一个典型的例子是Unix的文件属性）。八进制数的形式是[八进制数]q、[八进制数]Q、[八进制数]o、[八进制数]O。例如，777Q。</LI></UL></TD></TR></TBODY></TABLE>
<P>需要说明的是，这些方法是针对宏汇编器（例如，MASM、TASM、NASM）说的，调试器默认使用十六进制表示整数，并且不需要特别的声明（例如，在调试器中直接用FFFF表示十进制的65535，用10表示十进制的16）。</P>
<P>现在我们来写一小段汇编程序，修改EAX、EBX、ECX、EDX的数值。</P>
<P>我们假定程序执行之前，寄存器中的数值是全0：</P>
<TABLE id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=1>
<TBODY>
<TR>
<TD align=middle width="20%" rowSpan=2>　</TD>
<TD align=middle width="40%" rowSpan=2><FONT face=Tahoma>?</FONT></TD>
<TD align=middle width="40%" colSpan=2><FONT face=Tahoma>X</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>H</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>L</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EAX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>0000</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EBX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>0000</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>ECX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>0000</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EDX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>0000</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD></TR></TBODY></TABLE>
<P>正如前面提到的，EAX的高16bit是没有办法直接访问的，而AX对应它的低16bit，AH、AL分别对应AX的高、低8bit。</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="50%" bgColor=#e0e0e0>mov eax, 012345678h<BR>mov ebx, 0abcdeffeh<BR>mov ecx, 1<BR>mov edx, 2</TD>
<TD width="50%" bgColor=#e0e0e0><FONT color=#008000>; 将012345678h送入eax<BR>; 将0abcdeffeh送入ebx<BR>; 将000000001h送入ecx<BR>; 将000000002h送入edx</FONT></TD></TR></TBODY></TABLE>
<P>则执行上述程序段之后，寄存器的内容变为：</P>
<TABLE id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=1>
<TBODY>
<TR>
<TD align=middle width="20%" rowSpan=2>　</TD>
<TD align=middle width="40%" rowSpan=2><FONT face=Tahoma>?</FONT></TD>
<TD align=middle width="40%" colSpan=2><FONT face=Tahoma>X</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>H</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>L</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EAX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>1234</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>56</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>78</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EBX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>abcd</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>ef</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>fe</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>ECX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>0000</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>01</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EDX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>0000</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>02</FONT></TD></TR></TBODY></TABLE>
<P>那么，你已经了解了mov这个指令（mov是move的缩写）的一种用法。它可以将数送到寄存器中。我们来看看下面的代码：</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="50%" bgColor=#e0e0e0>mov eax, ebx<BR>mov ecx, edx</TD>
<TD width="50%" bgColor=#e0e0e0><FONT color=#008000>; ebx内容送入eax<BR>; edx内容送入ecx</FONT></TD></TR></TBODY></TABLE>
<P>则寄存器内容变为：</P>
<TABLE id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=1>
<TBODY>
<TR>
<TD align=middle width="20%" rowSpan=2>　</TD>
<TD align=middle width="40%" rowSpan=2><FONT face=Tahoma>?</FONT></TD>
<TD align=middle width="40%" colSpan=2><FONT face=Tahoma>X</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>H</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>L</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EAX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>abcd</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>ef</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>fe</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EBX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>abcd</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>ef</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>fe</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>ECX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>0000</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>02</FONT></TD></TR>
<TR>
<TD align=middle width="20%"><FONT face=Tahoma>EDX</FONT></TD>
<TD align=middle width="40%"><FONT face=Tahoma>0000</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>00</FONT></TD>
<TD align=middle width="20%"><FONT face=Tahoma>02</FONT></TD></TR></TBODY></TABLE>
<P>我们可以看到，“move”之后，数据依然保存在原来的寄存器中。不妨把mov指令理解为“送入”，或“装入”。</P>
<P><B>练习题</B></P>
<P>把寄存器恢复成都为全0的状态，然后执行下面的代码：</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="50%" bgColor=#e0e0e0>mov eax, 0a1234h<BR>mov bx, ax<BR>mov ah, bl<BR>mov al, bh</TD>
<TD width="50%" bgColor=#e0e0e0><FONT color=#008000>; 将0a1234h送入eax<BR>; 将ax的内容送入bx<BR>; 将bl内容送入ah<BR>; 将bh内容送入al</FONT></TD></TR></TBODY></TABLE>
<P>思考：此时，EAX的内容将是多少？[<SPAN class=tip id=oAnswer1>答案</SPAN>]</P>
<P>下面我们将介绍一些指令。在介绍指令之前，我们约定：</P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0>
<P dir=ltr><BR><B>　　 使用Intel文档中的寄存器表示方式</B></P>
<UL type=square>
<LI>reg32 32-bit寄存器（表示EAX、EBX等） 
<LI>reg16 16-bit寄存器（在32位处理器中，这AX、BX等） 
<LI>reg8&nbsp; 8-bit寄存器（表示AL、BH等） 
<LI>imm32 32-bit立即数（可以理解为常数） 
<LI>imm16 16-bit立即数 
<LI>imm8&nbsp; 8-bit立即数</LI></UL></TD></TR></TBODY></TABLE>
<P>在寄存器中载入另一寄存器，或立即数的值：</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0>
<P dir=ltr>mov reg32, (reg32 | imm8 | imm16 | imm32)<BR>mov reg32, (reg16 | imm8 | imm16)<BR>mov reg8, (reg8 | imm8)</P></TD></TR></TBODY></TABLE>
<P dir=ltr>例如，mov eax, 010h表示，在eax中载入00000010h。需要注意的是，如果你希望在寄存器中装入0，则有一种更快的方法，在后面我们将提到。</P>
<P dir=ltr>交换寄存器的内容：</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0>xchg reg32, reg32<BR>xchg reg16, reg16<BR>xchg reg8, reg8</TD></TR></TBODY></TABLE>
<P>例如，xchg ebx, ecx，则ebx与ecx的数值将被交换。由于系统提供了这个指令，因此，采用其他方法交换时，速度将会较慢，并需要占用更多的存储空间，编程时要避免这种情况，即，尽量利用系统提供的指令，因为多数情况下，这意味着更小、更快的代码，同时也杜绝了错误（如果说Intel的CPU在交换寄存器内容的时候也会出错，那么它就不用卖CPU了。而对于你来说，检查一行代码的正确性也显然比检查更多代码的正确性要容易）刚才的习题的程序用下面的代码将更有效：</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="50%" bgColor=#e0e0e0>mov eax, 0a1234h<BR>mov bx, ax<BR>xchg ah, al</TD>
<TD width="50%" bgColor=#e0e0e0><FONT color=#008000>; 将0a1234h送入eax<BR>; 将ax内容送入bx<BR>; 交换ah, al的内容</FONT></TD></TR></TBODY></TABLE>
<P>递增或递减寄存器的值：</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0>inc reg(8,16,32)<BR>dec reg(8,16,32)</TD></TR></TBODY></TABLE>
<P>这两个指令往往用于循环中对指针的操作。需要说明的是，某些时候我们有更好的方法来处理循环，例如使用loop指令，或rep前缀。这些将在后面的章节中介绍。</P>
<P>将寄存器的数值与另一寄存器，或立即数的值相加，并存回此寄存器：</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0>
<P dir=ltr>add reg32, reg32 / imm(8,16,32)<BR>add reg16, reg16 / imm(8,16)<BR>add reg8, reg8 / imm(8)</P></TD></TR></TBODY></TABLE>
<P dir=ltr>例如，add eax, edx，将eax+edx的值存入eax。减法指令和加法类似，只是将add换成sub。</P>
<P dir=ltr>需要说明的是，与高级语言不同，汇编语言中，如果要计算两数之和（差、积、商，或一般地说，运算结果），那么必然有一个寄存器被用来保存结果。在PASCAL中，我们可以用nA := nB + nC来让nA保存nB+nC的结果，然而，汇编语言并不提供这种方法。如果你希望保持寄存器中的结果，需要用另外的指令。这也从另一个侧面反映了“寄存器”这个名字的意义。数据只是“寄存”在那里。如果你需要保存数据，那么需要将它放到内存或其他地方。</P>
<P dir=ltr>类似的指令还有and、or、xor（与，或，异或）等等。它们进行的是逻辑运算。</P>
<P dir=ltr>我们称add、mov、sub、and等称为为指令助记符（这么叫是因为它比机器语言容易记忆，而起作用就是方便人记忆，某些资料中也称为指令、操作码、opcode[operation code]等）；后面的参数成为操作数，一个指令可以没有操作数，也可以有一两个操作数，通常有一个操作数的指令，这个操作数就是它的操作对象；而两个参数的指令，前一个操作数一般是保存操作结果的地方，而后一个是附加的参数。</P>
<P dir=ltr>我不打算在这份教程中用大量的篇幅介绍指令——很多人做得比我更好，而且指令本身并不是重点，如果你学会了如何组织语句，那么只要稍加学习就能轻易掌握其他指令。更多的指令可以参考<A href="http://developer.intel.com/">Intel</A>提供的资料。编写程序的时候，也可以参考一些在线参考手册。Tech!Help和HelpPC 2.10尽管已经很旧，但足以应付绝大多数需要。</P>
<P dir=ltr>聪明的读者也许已经发现，使用sub eax, eax，或者xor eax, eax，可以得到与mov eax, 0类似的效果。在高级语言中，你大概不会选择用a=a-a来给a赋值，因为测试会告诉你这么做更慢，简直就是在自找麻烦，然而在汇编语言中，你会得到相反的结论，多数情况下，以由快到慢的速度排列，这三条指令将是xor eax, eax、sub eax, eax和mov eax, 0。</P>
<P dir=ltr>为什么呢？处理器在执行指令时，需要经过几个不同的阶段：取指、译码、取数、执行。</P>
<P dir=ltr>我们反复强调，寄存器是CPU的一部分。从寄存器取数，其速度很显然要比从内存中取数快。那么，不难理解，xor eax, eax要比mov eax, 0更快一些。</P>
<P dir=ltr>那么，为什么a=a-a通常要比a=0慢一些呢？这和编译器的优化有一定关系。多数编译器会把a=a-a翻译成类似下面的代码(通常，高级语言通过ebp和偏移量来访问局部变量；程序中，x为a相对于本地堆的偏移量，在只包含一个32-bit整形变量的程序中，这个值通常是4)：</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0>
<P dir=ltr>mov eax, <FONT color=#0000ff>dword ptr</FONT> [ebp-x]<BR>sub eax, <FONT color=#0000ff>dword ptr</FONT> [ebp-x]<BR>mov <FONT color=#0000ff>dword ptr</FONT> [ebp-x],eax</P></TD></TR></TBODY></TABLE>
<P>而把a=0翻译成</P>
<TABLE class=code style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0>
<P dir=ltr>mov <FONT color=#0000ff>dword ptr</FONT> [ebp-x], 0</P></TD></TR></TBODY></TABLE>
<P>上面的翻译只是示意性的，略去了很多必要的步骤，如保护寄存器内容、恢复等等。如果你对与编译程序的实现过程感兴趣，可以参考相应的书籍。多数编译器（特别是C/C++编译器，如Microsoft Visual C++）都提供了从源代码到宏汇编语言程序的附加编译输出选项。这种情况下，你可以很方便地了解编译程序执行的输出结果；如果编译程序没有提供这样的功能也没有关系，调试器会让你看到编译器的编译结果。</P>
<P>如果你明确地知道编译器编译出的结果不是最优的，那就可以着手用汇编语言来重写那段代码了。怎么确认是否应该用汇编语言重写呢？</P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD width="100%" bgColor=#e0e0e0><BR><B>　　使用汇编语言重写代码之前需要确认的几件事情</B> 
<UL type=square>
<LI>首先，这种优化<B>最好</B>有<B>明显的效果</B>。比如，一段循环中的计算，等等。一条语句的执行时间是很短的，现在新的CPU的指令周期都在0.000000001s以下，Intel甚至已经做出了4GHz主频（主频的倒数是时钟周期）的CPU，如果你的代码自始至终只执行一次，并且你只是减少了几个时钟周期的执行时间，那么改变将是无法让人察觉的；很多情况下，这种“优化”并不被提倡，尽管它确实减少了执行时间，但为此需要付出大量的时间、人力，多数情况下得不偿失（极端情况，比如你的设备内存价格非常昂贵的时候，这种优化也许会有意义）。 
<LI>其次，确认你已经使用了<B>最好的算法</B>，并且，你优化的程序的实现是<B>正确</B>的。汇编语言能够提供同样算法的最快实现，然而，它并不是万金油，更不是解决一切的灵丹妙药。用高级语言实现一种好的算法，不一定会比汇编语言实现一种差的算法更慢。不过需要注意的是，时间、空间复杂度最小的算法不一定就是解决某一特定问题的最佳算法。举例说，快速排序在完全逆序的情况下等价于冒泡排序，这时其他方法就比它快。同时，用汇编语言优化一个不正确的算法实现，将给调试带来很大的麻烦。 
<LI>最后，确认你<B>已经</B>将高级语言编译器的性能<B>发挥到极致</B>。Microsoft的编译器在RELEASE模式和DEBUG模式会有差异相当大的输出，而对于GNU系列的编译器而言，不同级别的优化也会生成几乎完全不同的代码。此外，在编程时对于问题的严格定义，可以极大地帮助编译器的优化过程。如何优化高级语言代码，使其编译结果最优超出了本教程的范围，但如果你不能确认已经发挥了编译器的最大效能，用汇编语言往往是一种更为费力的方法。 
<LI><B>还有一点非常重要，那就是你明白自己做的是什么。</B>好的高级语言编译器有时会有一些让人难以理解的行为，比如，重新排列指令顺序，等等。如果你发现这种情况，那么优化的时候就应该小心——编译器很可能比你拥有更多的关于处理器的知识，例如，对于一个超标量处理器，编译器会对指令序列进行“封包”，使他们尽可能的并行执行；此外，宏汇编器有时会自动插入一些nop指令，其作用是将指令凑成整数字长（32-bit，对于16-bit处理器，是16-bit）。这些都是提高代码性能的必要措施，如果你不了解处理器，那么最好不要改动编译器生成的代码，因为这种情况下，盲目的修改往往不会得到预期的效果。</LI></UL></TD></TR></TBODY></TABLE>
<P>曾经在一份杂志上看到过有人用纯机器语言编写程序。不清楚到底这是不是编辑的失误，因为一个头脑正常的人恐怕不会这么做程序，即使它不长、也不复杂。首先，汇编器能够完成某些封包操作，即使不行，也可以用db伪指令来写指令；用汇编语言写程序可以防止很多错误的发生，同时，它还减轻了人的负担，很显然，“完全用机器语言写程序”是完全没有必要的，因为汇编语言可以做出完全一样的事情，并且你可以依赖它，因为计算机不会出错，而人总有出错的时候。此外，如前面所言，如果用高级语言实现程序的代价不大（例如，这段代码在程序的整个执行过程中只执行一遍，并且，这一遍的执行时间也小于一秒），那么，为什么不用高级语言实现呢？</P>
<P>一些比较狂热的编程爱好者可能不太喜欢我的这种观点。比方说，他们可能希望精益求精地优化每一字节的代码。但多数情况下我们有更重要的事情，例如，你的算法是最优的吗？你已经把程序在高级语言许可的范围内优化到尽头了吗？并不是所有的人都有资格这样说。汇编语言是这样一件东西，它足够的强大，能够控制计算机，完成它能够实现的任何功能；同时，因为它的强大，也会提高开发成本，并且，难于维护。因此，我个人的建议是，如果在软件开发中使用汇编语言，则应在软件接近完成的时候使用，这样可以减少很多不必要的投入。</P>
<P>第二章中，我介绍了x86系列处理器的基本寄存器。这些寄存器对于x86兼容处理器仍然是有效的，如果你偏爱AMD的CPU，那么使用这些寄存器的程序同样也可以正常运行。</P>
<P>不过现在说用汇编语言进行优化还为时尚早——不可能写程序，而只操作这些寄存器，因为这样只能完成非常简单的操作，既然是简单的操作，那可能就会让人觉得乏味，甚至找一台足够快的机器穷举它的所有结果（如果可以穷举的话），并直接写程序调用，因为这样通常会更快。但话说回来，看完接下来的两章——内存和堆栈操作，你就可以独立完成几乎所有的任务了，配合第五章中断、第六章子程序的知识，你将知道如何驾驭处理器，并让它为你工作。</P><?xml:namespace prefix = tool /><tool:tip avoidmouse="false" element="oRadixes">数字计算机内部<B>只支持</B>二进制数，因为这样计算机<BR>只需要表示两种(某些情况是3种，这一内容超过了<BR>这份教程的范围，如果您感兴趣，可以参考数字逻<BR>辑电路的相关书籍)状态.&nbsp; 对于电路而言，这表现<BR>为高、低电平，或者开、关，分别非常明显，因而<BR>工作比较稳定；另一方面，由于只有两种状态，设<BR>计起来也比较简单。这样，使用二进制意味着低成<BR>本、稳定，多数情况下，这也意味着快速。 
<P></P>
<P>与十进制类似，我们可以用下面的式子来换算出一<BR>个任意形如a<SUB>m-1</SUB>……a<SUB>3</SUB>a<SUB>2</SUB>a<SUB>1</SUB>a<SUB>0 </SUB>的m位r进制数对应的<BR>数值n：</P>
<P align=center><IMG height=57 src="file:///C:/My%20Documents/简明x86汇编语言教程(3).files/radixsum.gif" width=156 border=0></P>
<P>程序设计中常用十六进制和八进制数字代替二进制<BR>数，其原因在于，16和8是2的整次方幂，这样，一<BR>位十六或八进制数可以表示整数个二进制位。十六<BR>进制中， 使用字母A、B、C、D、E、F表示10-15，<BR>而十六进制或八进制数制表示的的数字比二进制数<BR>更短一些。</P></tool:tip><tool:tip avoidmouse="false" element="oAnswer1">
<P>EAX的内容为000A3412h.</P></tool:tip></FONT></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE></FONT></P><img src ="http://www.cppblog.com/ivenher/aggbug/1068.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-11-11 13:09 <a href="http://www.cppblog.com/ivenher/articles/1068.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>寄存器 </title><link>http://www.cppblog.com/ivenher/articles/1067.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Fri, 11 Nov 2005 05:06:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/1067.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/1067.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/1067.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/1067.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/1067.html</trackback:ping><description><![CDATA[<FONT size=5>&nbsp;</FONT>
<P>1. 通用<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>: </P>
<P>EAX</P>
<P>(accumulator)"累加器",很多加法乘法指令的缺省<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>.</P>
<P>EBX</P>
<P>(base)"基地址"<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>, 在内存寻址时存放基地址.</P>
<P>&nbsp;</P>
<P>ECX</P>
<P>(counter)计数器, 是重复(REP)前缀指令和LOOP指令的内定计数</P>
<P>EDX</P>
<P>用来放整数除法产生的余数.</P>
<P>低16位:AX,BX,CX和DX</P>
<P>低8位 :AL,BL,CL和DL</P>
<P>高8位 :AH,BH,CH和DH</P>
<P>ESI</P>
<P>(source index)"源索引<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>", DS:ESI指向源串,如:字符串操作指令中,</P>
<P>EDI</P>
<P>(destination index)"目标索引<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>",ES:EDI指向目标串</P>
<P>EBP</P>
<P>(BASE POINTER)"基址指针",被用作高级语言函数调用的<BR></P>
<P>,ESP(这个虽然通用,但<BR>很少被用做除了堆栈指针外的用途) 这些32位可以被用作多种4个<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>的又可</P>
<P>&nbsp;函数的返回值经常被放在EAX中. ESI/EDI分别叫做"源/目标索引<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>"(source/destination index<BR><BR>),因为在很多字符串操作指令中, DS:ESI指向源串,而. EBP是"基址指针"(BASE POINTER), 它最经常"<BR><BR>框架指针"(frame pointer). 在破解的时候,经常可以看见一个标准的函数起始代码: push ebp ;保存当前ebp mov ebp,esp ;EBP设为当前堆栈指针 <BR><BR>sub esp, xxx ;预留xxx字节给函数临时变量. ... 这样一来,EBP 构成了该函数的一个框架, 在EBP上方分别是原来的EBP, 返回地址和参数. EBP下<BR><BR>方则是临时变量. 函数返回时作 mov esp,ebp/pop ebp/ret 即可. ESP 专门用作堆栈指针. 2. 段<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>: CS(Code Segment，代码段) 指定当前执<BR><BR>行的代码段. EIP (Instruction pointer, 指令指针)则指向该段中一个具体的指令. CS:EIP指向哪个指令, CPU 就执行它. 一般只能用jmp, ret, <BR><BR>jnz, call 等指令来改变程序流程,而不能直接对它们赋值. DS(DATA SEGMENT, 数据段) 指定一个数据段. 注意:在当前的计算机系统中, 代码和数<BR><BR>据没有本质差别, 都是一串二进制数, 区别只在于你如何用它. 例如, CS 制定的段总是被用作代码, 一般不能通过CS指定的地址去修改该段. 然而<BR><BR>,你可以为同一个段申请一个数据段描述符"别名"而通过DS来访问/修改. 自修改代码的程序常如此做. ES,FS,GS 是辅助的段<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>, 指定附加的数<BR><BR>据段. SS(STACK SEGMENT)指定当前堆栈段. ESP 则指出该段中当前的堆栈顶. 所有push/pop 系列指令都只对SS:ESP指出的地址进行操作. 3. 标志<BR><BR><FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>(EFLAGS): 该<FONT style="BACKGROUND-COLOR: #316ac5" color=#ffffff>寄存器</FONT>有32位,组合了各个系统标志. EFLAGS一般不作为整体访问, 而只对单一的标志位感兴趣. 常用的标志有: 进位标志C(<BR><BR>CARRY), 在加法产生进位或减法有借位时置1, 否则为0. 零标志Z(ZERO), 若运算结果为0则置1, 否则为0 符号位S(SIGN), 若运算结果的最高位置<BR><BR>1, 则该位也置1. 溢出标志O(OVERFLOW), 若(带符号)运算结果超出可表示范围, 则置1. JXX 系列指令就是根据这些标志来决定是否要跳转, 从而<BR><BR>实现条件分枝. 要注意,很多JXX 指令是等价的, 对应相同的机器码. 例如, JE 和JZ 是一样的,都是当Z=1是跳转. 只有JMP 是无条件跳转. JXX 指<BR><BR>令分为两组, 分别用于无符号操作和带符号操作. JXX 后面的"XX" 有如下字母: 无符号操作: 带符号操作: A = "ABOVE", 表示"高于" G = "<BR><BR>GREATER", 表示"大于" B = "BELOW", 表示"低于" L = "LESS", 表示"小于" C = "CARRY", 表示"进位"或"借位" O = "OVERFLOW", 表示"溢出" S <BR><BR>= "SIGN", 表示"负" 通用符号: E = "EQUAL" 表示"等于", 等价于Z (ZERO) N = "NOT" 表示"非", 即标志没有置位. 如JNZ "如果Z没有置位则跳<BR><BR>转" Z = "ZERO", 与E同. 如果仔细想一想,就会发现 JA = JNBE, JAE = JNB, JBE = JNA, JG = JNLE, JGE= JNL, JL= JNGE, .... 4. 端口 端口<BR><BR>是直接和外部设备通讯的地方。外设接入系统后，系统就会把外设的数据接口映射到特定的端口地址空间，这样，从该端口读入数据就是从外设读<BR><BR>入数据，而向外设写入数据就是向端口写入数据。当然这一切都必须遵循外设的工作方式。端口的地址空间与内存地址空间无关，系统总共提供对<BR><BR>64K个8位端口的访问，编号0-65535. 相邻的8位端口可以组成成一个16位端口，相邻的16位端口可以组成一个32位端口。端口输入输出由指令<BR><BR>IN,OUT,INS和OUTS实现，具体可参考汇编语言书籍。<BR></P>
<P>&nbsp;</P>
<P>中央处理器(CPU)在微机系统处于“领导核心”的地位。汇编语言被编译成机器语言之后，将由处理器来执行。那么，首先让我们来了解一下处理器的主要作用，这将帮助你更好地驾驭它。</P>
<P>
<TABLE id=AutoNumber2 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" bgColor=#e0e0e0 border=0>
<TBODY>
<TR>
<TD width="100%"><B>典型的处理器的主要任务包括</B> 
<UL>
<LI>从内存中获取机器语言指令，译码，执行 
<LI>根据指令代码管理它自己的寄存器 
<LI>根据指令或自己的的需要修改内存的内容 
<LI>响应其他硬件的中断请求 </LI></UL></TD></TR></TBODY></TABLE></P>
<P>一般说来，处理器拥有对整个系统的所有总线的控制权。对于Intel平台而言，处理器拥有对数据、内存和控制总线的控制权，根据指令控制整个计算机的运行。在以后的章节中，我们还将讨论系统中同时存在多个处理器的情况。</P>
<P>处理器中有一些寄存器，这些寄存器可以保存特定长度的数据。某些寄存器中保存的数据对于系统的运行有特殊的意义。</P>
<P>新的处理器往往拥有更多、具有更大字长的寄存器，提供更灵活的取指、寻址方式。</P>
<P><B>寄存器</B></P>
<P>如前所述，处理器中有一些可以保存数据的地方被称作寄存器。</P>
<P>寄存器可以被装入数据，你也可以在不同的寄存器之间移动这些数据，或者做类似的事情。基本上，像四则运算、位运算等这些计算操作，都主要是针对寄存器进行的。</P>
<P>首先让我来介绍一下80386上最常用的4个通用寄存器。先瞧瞧下面的图形，试着理解一下：</P>
<P align=center><IMG height=117 src="http://www.frontfree.net/articles/pages/0000000542/eaxlayout.gif" width=367 border=0></P>
<P>上图中，数字表示的是位。我们可以看出，EAX是一个32-bit寄存器。同时，它的低16-bit又可以通过AX这个名字来访问；AX又被分为高、低8bit两部分，分别由AH和AL来表示。</P>
<P>对于EAX、AX、AH、AL的改变同时也会影响与被修改的那些寄存器的值。从而事实上只存在一个32-bit的寄存器EAX，而它可以通过4种不同的途径访问。</P>
<P>也许通过名字能够更容易地理解这些寄存器之间的关系。EAX中的E的意思是“扩展的”，整个EAX的意思是扩展的AX。X的意思Intel没有明示，我个人认为表示它是一个可变的量 。而AH、AL中的H和L分别代表高和低 。</P>
<P>为什么要这么做呢？主要由于历史原因。早期的计算机是8位的，8086是第一个16位处理器，其通用寄存器的名字是AX，BX等等；80386是Intel推出的第一款IA-32系列处理器，所有的寄存器都被扩充为32位。为了能够兼容以前的16位应用程序，80386不能将这些寄存器依旧命名为AX、BX，并且简单地将他们扩充为32位——这将增加处理器在处理指令方面的成本。</P>
<P>Intel微处理器的寄存器列表（在本章先只介绍80386的寄存器，MMX寄存器以及其他新一代处理器的新寄存器将在以后的章节介绍）</P>
<P><B>通用寄存器</B><BR>下面介绍通用寄存器及其习惯用法。顾名思义，通用寄存器是那些你可以根据自己的意愿使用的寄存器，修改他们的值通常不会对计算机的运行造成很大的影响。通用寄存器最多的用途是计算。</P>
<P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=1>
<TBODY>
<TR>
<TD width="14%" bgColor=#e0e0e0>
<P align=center>EAX<BR>32-bit宽</P></TD>
<TD width="86%" bgColor=#e0e0e0>通用寄存器。相对其他寄存器，在进行运算方面比较常用。在保护模式中，也可以作为内存偏移指针（此时，DS作为段 寄存器或选择器）</TD></TR>
<TR>
<TD width="14%">
<P align=center>EBX<BR>32-bit宽</P></TD>
<TD width="86%">通用寄存器。通常作为内存偏移指针使用（相对于EAX、ECX、EDX），DS是默认的段寄存器或选择器。在保护模式中，同样可以起这个作用。</TD></TR>
<TR>
<TD width="14%" bgColor=#e0e0e0>
<P align=center>ECX<BR>32-bit宽</P></TD>
<TD width="86%" bgColor=#e0e0e0>通用寄存器。通常用于特定指令的计数。在保护模式中，也可以作为内存偏移指针（此时，DS作为 寄存器或段选择器）。</TD></TR>
<TR>
<TD width="14%">
<P align=center>EDX<BR>32-bit宽</P></TD>
<TD width="86%">通用寄存器。在某些运算中作为EAX的溢出寄存器（例如乘、除）。在保护模式中，也可以作为内存偏移指针（此时，DS作为段 寄存器或选择器）。</TD></TR></TBODY></TABLE></P>
<P>上述寄存器同EAX一样包括对应的16-bit和8-bit分组。</P>
<P>用作内存指针的特殊寄存器</P>
<P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=1>
<TBODY>
<TR>
<TD width="14%" bgColor=#e0e0e0>
<P align=center>ESI<BR>32-bit宽 </P></TD>
<TD width="86%" bgColor=#e0e0e0>通常在内存操作指令中作为“源地址指针”使用。当然，ESI可以被装入任意的数值，但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。</TD></TR>
<TR>
<TD width="14%">
<P align=center>EDI<BR>32-bit宽</P></TD>
<TD width="86%">通常在内存操作指令中作为“目的地址指针”使用。当然，EDI也可以被装入任意的数值，但通常没有人把它当作通用寄存器来用。DS是默认段寄存器或选择器。</TD></TR>
<TR>
<TD width="14%" bgColor=#e0e0e0>
<P align=center>EBP<BR>32-bit宽</P></TD>
<TD width="86%" bgColor=#e0e0e0>这也是一个作为指针的寄存器。通常，它被高级语言编译器用以建造‘堆栈帧’来保存函数或过程的局部变量，不过，还是那句话，你可以在其中保存你希望的任何数据。SS是它的默认段寄存器或选择器。</TD></TR></TBODY></TABLE></P>
<P>注意，这三个寄存器没有对应的8-bit分组。换言之，你可以通过SI、DI、BP作为别名访问他们的低16位，却没有办法直接访问他们的低8位。</P>
<P><B>段寄存器和选择器</B></P>
<P>实模式下的段寄存器到保护模式下摇身一变就成了选择器。不同的是，实模式下的“段寄存器”是16-bit的，而保护模式下的选择器是32-bit的。</P>
<P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=1>
<TBODY>
<TR>
<TD width="14%" bgColor=#e0e0e0>CS</TD>
<TD width="86%" bgColor=#e0e0e0>代码段，或代码选择器。同IP寄存器(稍后介绍)一同指向当前正在执行的那个地址。处理器执行时从这个寄存器指向的段（实模式）或内存（保护模式）中获取指令。除了跳转或其他分支指令之外，你无法修改这个寄存器的内容。</TD></TR>
<TR>
<TD width="14%">DS</TD>
<TD width="86%">数据段，或数据选择器。这个寄存器的低16 bit连同ESI一同指向的指令将要处理的内存。同时，所有的内存操作指令 默认情况下都用它指定操作段(实模式)或内存(作为选择器，在保护模式。这个寄存器可以被装入任意数值，然而在这么做的时候需要小心一些。方法是，首先把数据送给AX，然后再把它从AX传送给DS(当然，也可以通过堆栈来做).</TD></TR>
<TR>
<TD width="14%" bgColor=#e0e0e0>ES</TD>
<TD width="86%" bgColor=#e0e0e0>附加段，或附加选择器。这个寄存器的低16 bit连同EDI一同指向的指令将要处理的内存。同样的，这个寄存器可以被装入任意数值，方法和DS类似。</TD></TR>
<TR>
<TD width="14%">FS</TD>
<TD width="86%">F段或F选择器(推测F可能是Free?)。可以用这个寄存器作为默认段寄存器或选择器的一个替代品。它可以被装入任何数值，方法和DS类似。</TD></TR>
<TR>
<TD width="14%" bgColor=#e0e0e0>GS</TD>
<TD width="86%" bgColor=#e0e0e0>G段或G选择器(G的意义和F一样，没有在Intel的文档中解释)。它和FS几乎完全一样。</TD></TR>
<TR>
<TD width="14%">SS</TD>
<TD width="86%">堆栈段或堆栈选择器。这个寄存器的低16 bit连同ESP一同指向下一次堆栈操作(push和pop)所要使用的堆栈地址。这个寄存器也可以被装入任意数值，你可以通过入栈和出栈操作来给他赋值，不过由于堆栈对于很多操作有很重要的意义，因此，不正确的修改有可能造成对堆栈的破坏。</TD></TR></TBODY></TABLE></P>
<P>* 注意 一定不要在初学汇编的阶段把这些寄存器弄混。他们非常重要，而一旦你掌握了他们，你就可以对他们做任意的操作了。段寄存器，或选择器，在没有指定的情况下都是使用默认的那个。这句话在现在看来可能有点稀里糊涂，不过你很快就会在后面知道如何去做。</P>
<P>特殊寄存器(指向到特定段或内存的偏移量)：</P>
<P>
<TABLE id=AutoNumber1 style="BORDER-COLLAPSE: collapse" borderColor=#111111 cellSpacing=0 cellPadding=0 width="100%" border=1>
<TBODY>
<TR>
<TD width="14%" bgColor=#e0e0e0>EIP</TD>
<TD width="86%" bgColor=#e0e0e0>这个寄存器非常的重要。这是一个32位宽的寄存器 ，同CS一同指向即将执行的那条指令的地址。不能够直接修改这个寄存器的值，修改它的唯一方法是跳转或分支指令。(CS是默认的段或选择器)</TD></TR>
<TR>
<TD width="14%">ESP</TD>
<TD width="86%">这个32位寄存器指向堆栈中即将被操作的那个地址。尽管可以修改它的值，然而并不提倡这样做，因为如果你不是非常明白自己在做什么，那么你可能造成堆栈的破坏。对于绝大多数情况而言，这对程序是致命的。(SS是默认的段或选择器)</TD></TR></TBODY></TABLE></P>
<P>IP: Instruction Pointer, 指令指针<BR>SP: Stack Pointer, 堆栈指针</P>
<P>好了，上面是最基本的寄存器。下面是一些其他的寄存器，你甚至可能没有听说过它们。(都是32位宽)：</P>
<P>CR0, CR2, CR3(控制寄存器)。举一个例子，CR0的作用是切换实模式和保护模式。</P>
<P>还有其他一些寄存器，D0, D1, D2, D3, D6和D7(调试寄存器)。他们可以作为调试器的硬件支持来设置条件断点。</P>
<P>TR3, TR4, TR5, TR6 和 TR? 寄存器(测试寄存器)用于某些条件测试。</P>
<P>最后我们要说的是一个在程序设计中起着非常关键的作用的寄存器：标志寄存器。</P>
<P align=center><IMG height=266 src="http://www.frontfree.net/articles/pages/0000000542/flaglayout.gif" width=388 border=0></P><img src ="http://www.cppblog.com/ivenher/aggbug/1067.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-11-11 13:06 <a href="http://www.cppblog.com/ivenher/articles/1067.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>预处理过程</title><link>http://www.cppblog.com/ivenher/articles/916.html</link><dc:creator>爱饭盒</dc:creator><author>爱饭盒</author><pubDate>Thu, 03 Nov 2005 03:30:00 GMT</pubDate><guid>http://www.cppblog.com/ivenher/articles/916.html</guid><wfw:comment>http://www.cppblog.com/ivenher/comments/916.html</wfw:comment><comments>http://www.cppblog.com/ivenher/articles/916.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ivenher/comments/commentRss/916.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ivenher/services/trackbacks/916.html</trackback:ping><description><![CDATA[<SPAN style="FONT-SIZE: 9pt; FONT-FAMILY: 宋体; mso-bidi-font-family: 宋体; mso-ansi-language: EN-US; mso-fareast-language: ZH-CN; mso-bidi-language: AR-SA">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<FONT color=#000000 size=5><STRONG>&nbsp;预处理过程</STRONG></FONT><BR><BR>预处理过程扫描源代码，对其进行初步的转换，产生新的源代码提供给编译器。可见预处理过程先于编译器对源代码进行处理。<SPAN lang=EN-US><BR></SPAN>在<SPAN lang=EN-US>C</SPAN>语言中，并没有任何内在的机制来完成如下一些功能：在编译时包含其他源文件、定义宏、根据条件决定编译时是否包含某些代码。要完成这些工作，就需要使用预处理程序。尽管在目前绝大多数编译器都包含了预处理程序，但通常认为它们是独立于编译器的。预处理过程读入源代码，检查包含预处理指令的语句和宏定义，并对源代码进行响应的转换。预处理过程还会删除程序中的注释和多余的空白字符。<SPAN lang=EN-US><BR></SPAN>预处理指令是以<SPAN lang=EN-US>#</SPAN>号开头的代码行。<SPAN lang=EN-US>#</SPAN>号必须是该行除了任何空白字符外的第一个字符。<SPAN lang=EN-US>#</SPAN>后是指令关键字，在关键字和<SPAN lang=EN-US>#</SPAN>号之间允许存在任意个数的空白字符。整行语句构成了一条预处理指令，该指令将在编译器进行编译之前对源代码做某些转换。下面是部分预处理指令：<SPAN lang=EN-US><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>指令<SPAN lang=EN-US>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>用途<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>空指令，无任何效果<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>包含一个源代码文件<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>定义宏<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#undef&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>取消已定义的宏<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#if&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>如果给定条件为真，则编译下面代码<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#ifdef&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>如果宏已经定义，则编译下面代码<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#ifndef&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>如果宏没有定义，则编译下面代码<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#elif&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>如果前面的<SPAN lang=EN-US>#if</SPAN>给定条件不为真，当前条件为真，则编译下面代码<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>结束一个<SPAN lang=EN-US>#if</SPAN>……<SPAN lang=EN-US>#else</SPAN>条件编译块<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#error&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>停止编译并显示错误信息<SPAN lang=EN-US><BR><BR></SPAN>一、文件包含<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;#include</SPAN>预处理指令的作用是在指令处展开被包含的文件。包含可以是多重的，也就是说一个被包含的文件中还可以包含其他文件。标准<SPAN lang=EN-US>C</SPAN>编译器至少支持八重嵌套包含。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>预处理过程不检查在转换单元中是否已经包含了某个文件并阻止对它的多次包含。这样就可以在多次包含同一个头文件时，通过给定编译时的条件来达到不同的效果。例如：<SPAN lang=EN-US><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;AAA<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include&nbsp;"t.c"<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#undef&nbsp;AAA<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include&nbsp;"t.c"<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>为了避免那些只能包含一次的头文件被多次包含，可以在头文件中用编译时条件来进行控制。例如：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*my.h*/<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#ifndef&nbsp;MY_H<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;MY_H<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>……<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>在程序中包含头文件有两种格式：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include&nbsp;&lt;my.h&gt;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#include&nbsp;"my.h"<BR>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>第一种方法是用尖括号把头文件括起来。这种格式告诉预处理程序在编译器自带的或外部库的头文件中搜索被包含的头文件。第二种方法是用双引号把头文件括起来。这种格式告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件，如果找不到，再搜索编译器自带的头文件。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>采用两种不同包含格式的理由在于，编译器是安装在公共子目录下的，而被编译的应用程序是在它们自己的私有子目录下的。一个应用程序既包含编译器提供的公共头文件，也包含自定义的私有头文件。采用两种不同的包含格式使得编译器能够在很多头文件中区别出一组公共的头文件。<SPAN lang=EN-US><BR><BR></SPAN>二、宏<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>宏定义了一个代表特定内容的标识符。预处理过程会把源代码中出现的宏标识符替换成宏定义时的值。宏最常见的用法是定义代表某个值的全局符号。宏的第二种用法是定义带参数的宏，这样的宏可以象函数一样被调用，但它是在调用语句处展开宏，并用调用时的实际参数来代替定义中的形式参数。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;1.#define</SPAN>指令<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define</SPAN>预处理指令是用来定义宏的。该指令最简单的格式是：首先神明一个标识符，然后给出这个标识符代表的代码。在后面的源代码中，就用这些代码来替代该标识符。这种宏把程序中要用到的一些全局值提取出来，赋给一些记忆标识符。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;MAX_NUM&nbsp;10<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;array[MAX_NUM];<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;for(i=0;i&lt;MAX_NUM;i++)&nbsp;&nbsp;/*</SPAN>……<SPAN lang=EN-US>*/<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>在这个例子中，对于阅读该程序的人来说，符号<SPAN lang=EN-US>MAX_NUM</SPAN>就有特定的含义，它代表的值给出了数组所能容纳的最大元素数目。程序中可以多次使用这个值。作为一种约定，习惯上总是全部用大写字母来定义宏，这样易于把程序红的宏标识符和一般变量标识符区别开来。如果想要改变数组的大小，只需要更改宏定义并重新编译程序即可。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>宏表示的值可以是一个常量表达式，其中允许包括前面已经定义的宏标识符。例如：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;ONE&nbsp;1<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;TWO&nbsp;2<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;THREE&nbsp;(ONE+TWO)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>注意上面的宏定义使用了括号。尽管它们并不是必须的。但出于谨慎考虑，还是应该加上括号的。例如：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;six=THREE*TWO;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>预处理过程把上面的一行代码转换成：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;six=(ONE+TWO)*TWO;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>如果没有那个括号，就转换成<SPAN lang=EN-US>six=ONE+TWO*TWO;</SPAN>了。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>宏还可以代表一个字符串常量，例如：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;VERSION&nbsp;"Version&nbsp;1.0&nbsp;Copyright(c)&nbsp;2003"<BR>&nbsp;&nbsp;&nbsp;&nbsp;2.</SPAN>带参数的<SPAN lang=EN-US>#define</SPAN>指令<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>带参数的宏和函数调用看起来有些相似。看一个例子：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;Cube(x)&nbsp;(x)*(x)*(x)<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>可以时任何数字表达式甚至函数调用来代替参数<SPAN lang=EN-US>x</SPAN>。这里再次提醒大家注意括号的使用。宏展开后完全包含在一对括号中，而且参数也包含在括号中，这样就保证了宏和参数的完整性。看一个用法：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;num=8+2;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;volume=Cube(num);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>展开后为<SPAN lang=EN-US>(8+2)*(8+2)*(8+2);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>如果没有那些括号就变为<SPAN lang=EN-US>8+2*8+2*8+2</SPAN>了。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>下面的用法是不安全的：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;volume=Cube(num++);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>如果<SPAN lang=EN-US>Cube</SPAN>是一个函数，上面的写法是可以理解的。但是，因为<SPAN lang=EN-US>Cube</SPAN>是一个宏，所以会产生副作用。这里的擦书不是简单的表达式，它们将产生意想不到的结果。它们展开后是这样的：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;volume=(num++)*(num++)*(num++);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>很显然，结果是<SPAN lang=EN-US>10*11*12,</SPAN>而不是<SPAN lang=EN-US>10*10*10;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>那么怎样安全的使用<SPAN lang=EN-US>Cube</SPAN>宏呢？必须把可能产生副作用的操作移到宏调用的外面进行：<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;num=8+2;<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;volume=Cube(num);<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;num++;<BR>&nbsp;&nbsp;&nbsp;&nbsp;3.#</SPAN>运算符<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>出现在宏定义中的<SPAN lang=EN-US>#</SPAN>运算符把跟在其后的参数转换成一个字符串。有时把这种用法的<SPAN lang=EN-US>#</SPAN>称为字符串化运算符。例如：<SPAN lang=EN-US><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;PASTE(n)&nbsp;"adhfkj"#n<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;main()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("%s\n",PASTE(15));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>宏定义中的<SPAN lang=EN-US>#</SPAN>运算符告诉预处理程序，把源代码中任何传递给该宏的参数转换成一个字符串。所以输出应该是<SPAN lang=EN-US>adhfkj15</SPAN>。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;4.##</SPAN>运算符<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;##</SPAN>运算符用于把参数连接到一起。预处理程序把出现在<SPAN lang=EN-US>##</SPAN>两侧的参数合并成一个符号。看下面的例子：<SPAN lang=EN-US><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;NUM(a,b,c)&nbsp;a##b##c<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;STR(a,b,c)&nbsp;a##b##c<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;main()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("%d\n",NUM(1,2,3));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("%s\n",STR("aa","bb","cc"));<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>最后程序的输出为<SPAN lang=EN-US>:<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;123<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;aabbcc<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>千万别担心，除非需要或者宏的用法恰好和手头的工作相关，否则很少有程序员会知道<SPAN lang=EN-US>##</SPAN>运算符。绝大多数程序员从来没用过它。<SPAN lang=EN-US><BR><BR></SPAN>三、条件编译指令<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>条件编译指令将决定那些代码被编译，而哪些是不被编译的。可以根据表达式的值或者某个特定的宏是否被定义来确定编译条件。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;1.#if</SPAN>指令<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#if</SPAN>指令检测跟在制造另关键字后的常量表达式。如果表达式为真，则编译后面的代码，知道出现<SPAN lang=EN-US>#else</SPAN>、<SPAN lang=EN-US>#elif</SPAN>或<SPAN lang=EN-US>#endif</SPAN>为止；否则就不编译。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;2.#endif</SPAN>指令<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif</SPAN>用于终止<SPAN lang=EN-US>#if</SPAN>预处理指令。<SPAN lang=EN-US><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;DEBUG&nbsp;0<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;main()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#if&nbsp;DEBUG<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("Debugging\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("Running\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>由于程序定义<SPAN lang=EN-US>DEBUG</SPAN>宏代表<SPAN lang=EN-US>0</SPAN>，所以<SPAN lang=EN-US>#if</SPAN>条件为假，不编译后面的代码直到<SPAN lang=EN-US>#endif</SPAN>，所以程序直接输出<SPAN lang=EN-US>Running</SPAN>。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>如果去掉<SPAN lang=EN-US>#define</SPAN>语句，效果是一样的。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;3.#ifdef</SPAN>和<SPAN lang=EN-US>#ifndef<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;DEBUG<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;main()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#ifdef&nbsp;DEBUG<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("yes\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#ifndef&nbsp;DEBUG<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("no\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#if&nbsp;defined</SPAN>等价于<SPAN lang=EN-US>#ifdef;&nbsp;#if&nbsp;!defined</SPAN>等价于<SPAN lang=EN-US>#ifndef<BR>&nbsp;&nbsp;&nbsp;&nbsp;4.#else</SPAN>指令<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#else</SPAN>指令用于某个<SPAN lang=EN-US>#if</SPAN>指令之后，当前面的<SPAN lang=EN-US>#if</SPAN>指令的条件不为真时，就编译<SPAN lang=EN-US>#else</SPAN>后面的代码。<SPAN lang=EN-US>#endif</SPAN>指令将中指上面的条件块。<SPAN lang=EN-US><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;DEBUG<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;main()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#ifdef&nbsp;DEBUG<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("Debugging\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("Not&nbsp;debugging\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("Running\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;5.#elif</SPAN>指令<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#elif</SPAN>预处理指令综合了<SPAN lang=EN-US>#else</SPAN>和<SPAN lang=EN-US>#if</SPAN>指令的作用。<SPAN lang=EN-US><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#define&nbsp;TWO<BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;main()<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#ifdef&nbsp;ONE<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("1\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#elif&nbsp;defined&nbsp;TWO<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("2\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#else<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;printf("3\n");<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#endif<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</SPAN>程序很好理解，最后输出结果是<SPAN lang=EN-US>2</SPAN>。<SPAN lang=EN-US><BR><BR>&nbsp;&nbsp;&nbsp;&nbsp;6.</SPAN>其他一些标准指令<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#error</SPAN>指令将使编译器显示一条错误信息，然后停止编译。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#line</SPAN>指令可以改变编译器用来指出警告和错误信息的文件号和行号。<SPAN lang=EN-US><BR>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;#pragma</SPAN>指令没有正式的定义。编译器可以自定义其用途。典型的用法是禁止或允许某些烦人的警告信息。</SPAN><img src ="http://www.cppblog.com/ivenher/aggbug/916.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ivenher/" target="_blank">爱饭盒</a> 2005-11-03 11:30 <a href="http://www.cppblog.com/ivenher/articles/916.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>