﻿<?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/ispfcn/</link><description /><language>zh-cn</language><lastBuildDate>Tue, 07 Apr 2026 20:33:45 GMT</lastBuildDate><pubDate>Tue, 07 Apr 2026 20:33:45 GMT</pubDate><ttl>60</ttl><item><title>在PHP中实现进程间通讯</title><link>http://www.cppblog.com/ispfcn/archive/2008/04/18/47518.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Fri, 18 Apr 2008 09:26:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2008/04/18/47518.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/47518.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2008/04/18/47518.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/47518.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/47518.html</trackback:ping><description><![CDATA[<p>　　本文将讨论在PHP4环境下如何使用进程间通讯机制——IPC(Inter-Process-Communication)。本文讨论的软件环境是Linux+php4.0.4或更高版本。首先,我们假设你已经装好了PHP4和UNIX, 为了使得php4可以使用共享内存和信号量，必须在编译php4程序时激活shmop和sysvsem这两个扩展模块。 </p>
<p>　　实现方法：在PHP设定(configure)时加入如下选项。 <br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">--</span><span style="COLOR: #000000">enable</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">shmop&nbsp;</span><span style="COLOR: #000000">--</span><span style="COLOR: #000000">enable</span><span style="COLOR: #000000">-</span><span style="COLOR: #000000">sysvsem&nbsp;&nbsp;</span></div>
<p>&nbsp;</p>
<p>　　这样就使得你的PHP系统可以处理相关的IPC函数了。 </p>
<p>　　IPC是什么？ </p>
<p>　　IPC (Inter-process communication) 是一个Unix标准通讯机制，它提供了使得在同一台主机不同进程之间可以互相通讯的方法。基本的IPC处理机制有3种：它们分别是共享内存、信号量和消息队列。本文中我们主要讨论共享内存和信号量的使用。关于消息队列，笔者在不久的将来还会专门介绍。 </p>
<p>　　在PHP中使用共享内存段 </p>
<p>　　在不同的处理进程之间使用共享内存是一个实现不同进程之间相互通讯的好方法。如果你在一个进程中向所共享的内存写入一段信息，那么所有其他的进程也可以看到这段被写入的数据。非常方便。在PHP中有了共享内存的帮助，你可以实现不同进程在运行同一段PHP脚本时返回不同的结果。或实现对PHP同时运行数量的实时查询等等。 </p>
<p>　　共享内存允许两个或者多个进程共享一给定的存储区。因为数据不需要在客户机和服务器之间复制，所以这是最快的一种IPC。使用共享内存的唯一窍门是多个进程对一给定存储区的同步存取。 </p>
<p>　　如何建立一个共享内存段呢？下面的代码可以帮你建立共享内存。&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #800080">$shm_id</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;shmop_open(</span><span style="COLOR: #800080">$key</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$mode</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$perm</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$size</span><span style="COLOR: #000000">);&nbsp;</span></div>
<p>&nbsp;</p>
<p>　　注意，每个共享内存段都有一个唯一的ID, 在PHP中，shmop_open会把建立好的共享内存段的ID返回，这里我们用$shm_id记录它。而$key是一个我们逻辑上表示共享内存段的Key值。不同进程只要选择同一个Key id就可以共享同一段存储段。习惯上我们用一个串（类似文件名一样的东西）的散列值作为key id. $mode指明了共享内存段的使用方式。这里由于是新建，因此值为&#8217;c&#8217; &#8211;取create之意。如果你是访问已经建立过的共享内存那么请用&#8217;a&#8217;,-- 取Access之意。$perm参数定义了访问的权限，8进制，关于权限定义请看UNIX文件系统帮助。$size定义了共享内存的大小。尽管有点象fopen(文件处理)你可不要当它同文件处理一样。后面的描述你将看到着一点。 </p>
<p>　　例如： <br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #800080">$shm_id</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;shmop_open(</span><span style="COLOR: #000000">0xff3</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">c</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0644</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">100</span><span style="COLOR: #000000">);&nbsp;</span></div>
<p><br>　　这里我们打开了一个共享内存段 键值0xff3 &#8211;rw-r—r—格式，大小为100字节。 </p>
<p>　　如果需要访问已有的共享内存段，你必须在调用shmop_open中设第3、4个参数为0。 </p>
<p>　　IPC工作状态的查询 </p>
<p>　　在Unix下，你可以用一个命令行程序ipcs查询系统所有的IPC资源状态。不过有些系统要求需要超级用户方能执行。下图是一段ipcs的运行结果。</p>
<p>&nbsp;<img height=159 alt="" src="http://www.cppblog.com/images/cppblog_com/ispfcn/1308/1.jpg" width=400 border=0><br>　　上图中系统显示了4个共享内存段，注意其中第4个键值为0x00000ff3的就是我们刚刚运行过的PHP程序所创建的。关于ipcs的用法请参考UNIX用户手册。 </p>
<p>　　如何释放共享内存呢 </p>
<p>　　释放共享内存的办法是调用PHP指令:shmop_delete($id) </p>
<p>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">shmop_delete(</span><span style="COLOR: #800080">$id</span><span style="COLOR: #000000">);&nbsp;</span></div>
<p>&nbsp;</p>
<p>　　$id 就是你调用shmop_open所存的shmop_op的返回值。还有一个办法就是用UNIX的管理指令: </p>
<p>　　ipcrm id, id就是你用ipcs看到的ID.和你程序中的$id不一样。不过要小心，如果你用ipcrm直接删除共享内存段那么有可能导致其他不知道这一情况的进程在引用这个已经不复存在的共享内存器时出现一些不可预测的错误(往往结果不妙)。 </p>
<p>　　如何使用(读写)共享内存呢 </p>
<p>　　使用如下所示函数向共享内存写入数据 </p>
<p>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">int&nbsp;shmop_write&nbsp;(int&nbsp;shmid</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #0000ff">string</span><span style="COLOR: #000000">&nbsp;data</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;int&nbsp;offset)&nbsp;</span></div>
<p>&nbsp;</p>
<p>　　其中shmid是用shmop_open返回的句柄。$Data变量存放了要存放的数据。$offset描述了写入从共享内存的开始第一个字节的位置（以0开始）。 </p>
<p>　　读取操作是： </p>
<p>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">string</span><span style="COLOR: #000000">&nbsp;shmop_read&nbsp;(int&nbsp;shmid</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;int&nbsp;start</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;int&nbsp;</span><span style="COLOR: #008080">count</span><span style="COLOR: #000000">)&nbsp;</span></div>
<p>&nbsp;</p>
<p>　　同样，指明$shmid,开始偏移量（以0开始）、总读取数量。返回结果串。这样，你就可以把共享内存段当作是一个字节数组。读几个再写几个，想干嘛就干嘛，十分方便。 </p>
<p>　　多进程问题的考虑 </p>
<p>　　现在，在单独的一个PHP进程中读写、创建、删除共享内存方面上你应该没有问题了。但是，显然实际运行中不可能只是一个PHP进程在运行中。如果在多个进程的情况下你还是沿用单个进程的处理方法，你一定会碰到问题 ---- 著名的并行和互斥问题。比如说有2个进程同时需要对同一段内存进行读写。当两个进程同时执行写入操作时，你将得到一个错误的数据，因为该段内存将之可能是最后执行的进程的内容，甚至是由2个进程写入的数据轮流随机出现的一段混合的四不象。这显然是不能接受的。为了解决这个问题，我们必须引入互斥机制。互斥机制在很多操作系统的教材上都有专门讲述，这里不多重复。实现互斥机制的最简单办法就是使用信号灯。信号量是另外一种进程间通讯(IPC)的方式，它同其他IPC机构(管道、FIFO、消息队列)不同。它是一个记数器，用于控制多进程对共享数据的存储。同样的是你可以用ipcs和ipcrm实现对信号灯使用状态的查询和对其实现删除操作。在PHP中你可以用下列函数创建一个新的信号量并返回操作该信号量的句柄。如果该key指向的信号量已经存在，sem_get直接返回操作该信号量的句柄。 </p>
<p>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">int&nbsp;sem_get&nbsp;(int&nbsp;</span><span style="COLOR: #008080">key</span><span style="COLOR: #000000">&nbsp;[</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;int&nbsp;max_acquire&nbsp;[</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;int&nbsp;perm]])&nbsp;</span></div>
<p>&nbsp;</p>
<p>　　$max_acquire 指明同时最多可以用几个进程进入该信号而不必等待该信号被释放（也就是最大同时处理某一资源的进程数目,一般该值均为一）。$perm指明了访问权限。 </p>
<p>　　一旦你成功的拥有了一个信号量，你对它所能做的只有2种：请求、释放。当你执行释放操作时, 系统将把该信号值减一。如果小于0那就还设为0。而当你执行请求操作时，系统将把该信号值加一，如果该值大于设定的最大值那么系统将挂起你的处理进程直到其他进程释放到小于最大值为止。一般情况下最大值设为1,这样一来当一个进程获得请求时其他后面的进程只能等待它退出互斥区后释放信号量才能进入该互斥区并同时设为独占方式。这样的信号量常称为双态信号量。当然，如果初值是任意一个正数就表明有多少个共享资源单位可供共享应用。 </p>
<p>　　申请、释放操作的PHP格式如下： </p>
<p>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">int&nbsp;sem_acquire&nbsp;(int&nbsp;sem_identifier)&nbsp;</span></div>
<p><br>申请 <br><br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #000000">int&nbsp;sem_release&nbsp;(int&nbsp;sem_identifier)&nbsp;</span></div>
<p><br>释放 <br>其中sem_identifier是调用sem_get的返回值（句柄）。&nbsp; <br>一个简单的互斥协议例子 <br>下面是一段很简单的互斥操作规程。 <br></p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #800080">$semid</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">sem_get(</span><span style="COLOR: #000000">0xee3</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">1</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">0666</span><span style="COLOR: #000000">);&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #800080">$shm_id</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;shmop_open(</span><span style="COLOR: #000000">0xff3</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">c</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0644</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">100</span><span style="COLOR: #000000">);&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>sem_acquire(</span><span style="COLOR: #800080">$semid</span><span style="COLOR: #000000">);&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">申请&nbsp;</span><span style="COLOR: #008000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>/*</span><span style="COLOR: #008000">&nbsp;进入临界区</span><span style="COLOR: #008000">*/</span><span style="COLOR: #000000">&nbsp;<br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #008000">/*</span><span style="COLOR: #008000">这里，对共享内存进行处理&nbsp;</span><span style="COLOR: #008000">*/</span><span style="COLOR: #000000"><br><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>sem_release(</span><span style="COLOR: #800080">$semid</span><span style="COLOR: #000000">);&nbsp;</span><span style="COLOR: #008000">//</span><span style="COLOR: #008000">释放&nbsp;</span></div>
<p>&nbsp;</p>
<p>&#160;</p>
<p>　　正如你所看到的，互斥的实现很简单：申请进入临界区，对临界区资源进行操作（比如修改共享内存）退出临界区并释放信号。这样一来就可以保证在同一个时间片中不可能有同时2个进程对同一段共享内存进行操作。因为信号量机制保证一个时间片只能由一个进程进入，其他进程必须等待当前处理的进程完成后方能进入。 </p>
<p>　　临界区一般是指那些不允许同时有多个进程并发处理的代码段。 </p>
<p>　　要注意的是:在PHP中必须由同一个进程释放它所占用的信号量。在一般系统中允许进程释放别的进程占用的信号。在编写临界区代码一定要小心设计资源的分配，避免A等B，B等A的死锁情况发生。&nbsp;</p>
<p>　　<strong>运用</strong> </p>
<p>　　IPC的运用是十分广泛的。比如，在不同进程间保存一个解释过的复杂的配置文件、或具体设置的用户等，以避免重复处理。我也曾经用共享内存的技术把一大批PHP脚本必须引用的一个很大的文件放入共享内存，并由此显著提升了Web服务的速度、消除了部分瓶颈。关于它的使用还有聊天室，多路广播等等。IPC的威力取决于你的想象力的大小。如果本文对你有一点点启发，那我不胜荣幸。愿意很你讨论这令人入迷的电脑技术。Email: qwyaxm@163.net </p>
<p><br><br>&nbsp;</p>
<img src ="http://www.cppblog.com/ispfcn/aggbug/47518.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2008-04-18 17:26 <a href="http://www.cppblog.com/ispfcn/archive/2008/04/18/47518.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Apache模块 开发实例(转)</title><link>http://www.cppblog.com/ispfcn/archive/2008/01/30/42234.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Wed, 30 Jan 2008 08:35:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2008/01/30/42234.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/42234.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2008/01/30/42234.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/42234.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/42234.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;&nbsp;&nbsp;1/**//**&nbsp;&nbsp;2*&nbsp;Copyright&nbsp;2003&nbsp;Tom,&nbsp;Inc.&nbsp;All&nbsp;rights&nbsp;reserved.&nbsp;&nbsp;3*&nbsp;&nbsp;4*&nbsp;Description:&nbsp;Apache模块&nbsp;取用户图片&nbsp;...&nbsp;&nbsp;<a href='http://www.cppblog.com/ispfcn/archive/2008/01/30/42234.html'>阅读全文</a><img src ="http://www.cppblog.com/ispfcn/aggbug/42234.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2008-01-30 16:35 <a href="http://www.cppblog.com/ispfcn/archive/2008/01/30/42234.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Smarty截取中文扩展，支持UTF-8和GB</title><link>http://www.cppblog.com/ispfcn/archive/2007/11/20/37010.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Tue, 20 Nov 2007 06:34:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2007/11/20/37010.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/37010.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2007/11/20/37010.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/37010.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/37010.html</trackback:ping><description><![CDATA[<p>&nbsp;</p>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">&nbsp;1</span><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">function</span><span style="COLOR: #000000">&nbsp;smarty_modifier_truncate_cn(</span><span style="COLOR: #800080">$string</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$length</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">80</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$code</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">UTF-8</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$etc</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/dot.gif"></span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">)<br></span><span style="COLOR: #008080">&nbsp;2</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>{<br></span><span style="COLOR: #008080">&nbsp;3</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">&nbsp;(</span><span style="COLOR: #800080">$length</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">)<br></span><span style="COLOR: #008080">&nbsp;4</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">''</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">&nbsp;5</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(</span><span style="COLOR: #800080">$code</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">==</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">UTF-8</span><span style="COLOR: #000000">'</span><span style="COLOR: #000000">){<br></span><span style="COLOR: #008080">&nbsp;6</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #800080">$pa</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xef][\x80-\xbf][\x80-\xbf]|\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]|[\xf1-\xf7][\x80-\xbf][\x80-\xbf][\x80-\xbf]/</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">&nbsp;7</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}<br></span><span style="COLOR: #008080">&nbsp;8</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">else</span><span style="COLOR: #000000">{<br></span><span style="COLOR: #008080">&nbsp;9</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #800080">$pa</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">=</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">/[\x01-\x7f]|[\xa1-\xff][\xa1-\xff]/</span><span style="COLOR: #000000">"</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">10</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;}<br></span><span style="COLOR: #008080">11</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #008080">preg_match_all</span><span style="COLOR: #000000">(</span><span style="COLOR: #800080">$pa</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$string</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$t_string</span><span style="COLOR: #000000">);<br></span><span style="COLOR: #008080">12</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">if</span><span style="COLOR: #000000">(</span><span style="COLOR: #008080">count</span><span style="COLOR: #000000">(</span><span style="COLOR: #800080">$t_string</span><span style="COLOR: #000000">[</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">])&nbsp;</span><span style="COLOR: #000000">&gt;</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$length</span><span style="COLOR: #000000">)<br></span><span style="COLOR: #008080">13</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #008080">join</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">''</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #008080">array_slice</span><span style="COLOR: #000000">(</span><span style="COLOR: #800080">$t_string</span><span style="COLOR: #000000">[</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">]</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$length</span><span style="COLOR: #000000">))</span><span style="COLOR: #000000">.</span><span style="COLOR: #800080">$etc</span><span style="COLOR: #000000">;<br></span><span style="COLOR: #008080">14</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="COLOR: #0000ff">return</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #008080">join</span><span style="COLOR: #000000">(</span><span style="COLOR: #000000">''</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #008080">array_slice</span><span style="COLOR: #000000">(</span><span style="COLOR: #800080">$t_string</span><span style="COLOR: #000000">[</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">]</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #000000">0</span><span style="COLOR: #000000">,</span><span style="COLOR: #000000">&nbsp;</span><span style="COLOR: #800080">$length</span><span style="COLOR: #000000">));<br></span><span style="COLOR: #008080">15</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>}</span></div>
<br>以下代码保存为ascii格式<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">&nbsp;1</span><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">html</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;2</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">head</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;3</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">title</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000">Truncate&nbsp;测试</span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">title</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;4</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">meta&nbsp;</span><span style="COLOR: #ff0000">http-equiv</span><span style="COLOR: #0000ff">="Content-Type"</span><span style="COLOR: #ff0000">&nbsp;content</span><span style="COLOR: #0000ff">="text/html;&nbsp;charset=gbk"</span><span style="COLOR: #ff0000">&nbsp;</span><span style="COLOR: #0000ff">/&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;5</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">head</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;6</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">body</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;7</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>{{$string}}</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">br</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;8</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>{{$string|truncate_cn:15:"":""}}</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">br</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;9</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">body</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">10</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">html</span><span style="COLOR: #0000ff">&gt;</span></div>
<br><br>以下代码保存为:UTF-8格式<br>
<div style="BORDER-RIGHT: #cccccc 1px solid; PADDING-RIGHT: 5px; BORDER-TOP: #cccccc 1px solid; PADDING-LEFT: 4px; FONT-SIZE: 13px; PADDING-BOTTOM: 4px; BORDER-LEFT: #cccccc 1px solid; WIDTH: 98%; WORD-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: #cccccc 1px solid; BACKGROUND-COLOR: #eeeeee"><span style="COLOR: #008080">&nbsp;1</span><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">html</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;2</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">head</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;3</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">title</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000">Truncate&nbsp;测试</span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">title</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;4</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">meta&nbsp;</span><span style="COLOR: #ff0000">http-equiv</span><span style="COLOR: #0000ff">="Content-Type"</span><span style="COLOR: #ff0000">&nbsp;content</span><span style="COLOR: #0000ff">="text/html;&nbsp;charset=UTF-8"</span><span style="COLOR: #ff0000">&nbsp;</span><span style="COLOR: #0000ff">/&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;5</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">head</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;6</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">body</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;7</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>{{$string}}</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">br</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;8</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top>{{$string|truncate_cn:15:"UTF-8":"<img src="http://www.cppblog.com/Images/dot.gif">"}}</span><span style="COLOR: #0000ff">&lt;</span><span style="COLOR: #800000">br</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">&nbsp;9</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">body</span><span style="COLOR: #0000ff">&gt;</span><span style="COLOR: #000000"><br></span><span style="COLOR: #008080">10</span><span style="COLOR: #000000"><img src="http://www.cppblog.com/Images/OutliningIndicators/None.gif" align=top></span><span style="COLOR: #0000ff">&lt;/</span><span style="COLOR: #800000">html</span><span style="COLOR: #0000ff">&gt;</span></div>
<br>
<img src ="http://www.cppblog.com/ispfcn/aggbug/37010.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2007-11-20 14:34 <a href="http://www.cppblog.com/ispfcn/archive/2007/11/20/37010.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>抓取腾讯天气预报的类</title><link>http://www.cppblog.com/ispfcn/archive/2007/08/27/30940.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Mon, 27 Aug 2007 08:37:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2007/08/27/30940.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/30940.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2007/08/27/30940.html#Feedback</comments><slash:comments>2</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/30940.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/30940.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 类的代码：&nbsp;&nbsp;1&lt;?php&nbsp;&nbsp;2/**&nbsp;&nbsp;3&nbsp;*&nbsp;抓取腾讯天气，存储成XML&nbsp;&nbsp;4&nbsp;*&nbsp;@author&nbsp;PeterPan&nbsp;&nbsp;5&nbsp;*&nbsp;@final&nbsp;2007-08-27&nbsp;&nbsp;6&nbsp;*/&...&nbsp;&nbsp;<a href='http://www.cppblog.com/ispfcn/archive/2007/08/27/30940.html'>阅读全文</a><img src ="http://www.cppblog.com/ispfcn/aggbug/30940.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2007-08-27 16:37 <a href="http://www.cppblog.com/ispfcn/archive/2007/08/27/30940.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>关于linux上的线程资源</title><link>http://www.cppblog.com/ispfcn/archive/2006/12/29/16991.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Fri, 29 Dec 2006 09:57:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2006/12/29/16991.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/16991.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2006/12/29/16991.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/16991.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/16991.html</trackback:ping><description><![CDATA[
		<p>前几天写个多线程的程序，主要是从文件读数据，然后分线程发送，本来想在线程里用pthread_detach()分离线程，但是我想要线程执行结果，所以不得不放弃pthread_detach()。在线程结束的时候用pthread_exit()，发完文件用ptrehad_join()取返回值。结果竟然发现：起到300多个线程的时候就出现can't allocate memory，这可郁闷死我了，找了半天资料，后来看到一篇文章，是说linux上的pthread_exit()不回收线程资源，只能用pthread_join()来回收，郁闷啊……最后只得在起到一定数量的线程后回收一遍资源。又学了一招了……</p>
<img src ="http://www.cppblog.com/ispfcn/aggbug/16991.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2006-12-29 17:57 <a href="http://www.cppblog.com/ispfcn/archive/2006/12/29/16991.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>VC++开发BHO插件——定制你的浏览器</title><link>http://www.cppblog.com/ispfcn/archive/2006/12/11/16274.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Mon, 11 Dec 2006 07:49:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2006/12/11/16274.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/16274.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2006/12/11/16274.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/16274.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/16274.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 在Windows操作系统上，我们最常见的浏览器有两种：文件浏览器（exploer.exe，应用于文件系统）和Internet浏览器（iexplore.exe，应用于互联网资源）。由于这两个浏览器功能强大，而且又与Windows操作系统捆绑销售，最终也就成为了浏览器的标准。但有时候，为了给浏览器加入一些新的特性，我们往往会重新设计一个自己的浏览器。新的浏览器模仿标准浏览器的大部分功能，同时加入新特性...&nbsp;&nbsp;<a href='http://www.cppblog.com/ispfcn/archive/2006/12/11/16274.html'>阅读全文</a><img src ="http://www.cppblog.com/ispfcn/aggbug/16274.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2006-12-11 15:49 <a href="http://www.cppblog.com/ispfcn/archive/2006/12/11/16274.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>简明 Python 教程</title><link>http://www.cppblog.com/ispfcn/archive/2006/11/13/15125.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Mon, 13 Nov 2006 06:33:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2006/11/13/15125.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/15125.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2006/11/13/15125.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/15125.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/15125.html</trackback:ping><description><![CDATA[学python的初级教程，很不错<br /><a href="http://www.woodpecker.org.cn:9081/doc/abyteofpython_cn/chinese/index.html">http://www.woodpecker.org.cn:9081/doc/abyteofpython_cn/chinese/index.html</a><img src ="http://www.cppblog.com/ispfcn/aggbug/15125.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2006-11-13 14:33 <a href="http://www.cppblog.com/ispfcn/archive/2006/11/13/15125.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>简明x86汇编语言教程（七）</title><link>http://www.cppblog.com/ispfcn/archive/2006/11/06/14720.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Mon, 06 Nov 2006 02:39:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2006/11/06/14720.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/14720.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2006/11/06/14720.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/14720.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/14720.html</trackback:ping><description><![CDATA[原创：司徒彦南 <br /><br /><h3>5.0 编译优化概述</h3><p>优化是一件非常重要的事情。作为一个程序设计者，你肯定希望自己的程序既小又快。DOS时代的许多书中都提到，“某某编译器能够生成非常紧凑的代码”，换言之，编译器会为你把代码尽可能地缩减，如果你能够正确地使用它提供的功能的话。目前，Intel x86体系上流行的C/C++编译器，包括Intel C/C++ Compiler, GNU C/C++ Compiler，以及最新的Microsoft和Borland编译器，都能够提供非常紧凑的代码。正确地使用这些编译器，则可以得到性能足够好的代码。</p><p>但是，机器目前还不能像人那样做富于创造性的事情。因而，有些时候我们可能会不得不手工来做一些事情。</p><p>使用汇编语言优化代码是一件困难，而且技巧性很强的工作。很多编译器能够生成为处理器进行过特殊优化处理的代码，一旦进行修改，这些特殊优化可能就会被破坏而失效。因此，在你决定使用自己的汇编代码之前，一定要测试一下，到底是编译器生成的那段代码更好，还是你的更好。</p><p>本章中将讨论一些编译器在某些时候会做的事情(从某种意义上说，本章内容更像是计算机专业的基础课中《编译程序设计原理》、《计算机组成原理》、《计算机体系结构》课程中的相关内容)。本章的许多内容和汇编语言程序设计本身关系并不是很紧密，它们多数是在为使用汇编语言进行优化做准备。编译器确实做这些优化，但它并不总是这么做；此外，就编译器的设计本质来说，它确实没有义务这么做——编译器做的是等义变换，而不是等效变换。考虑下面的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#008000">// 程序段1</font><font color="#0000ff"><br />int</font> gaussianSum(){<br />  <font color="#0000ff">int</font> i, j=0; 
<p>  <font color="#0000ff">for</font>(i=0; i&lt;100; i++) j+=i;</p><p>  <font color="#0000ff">return</font> j;<br />}</p></td></tr></tbody></table><p>好的，首先，绝大多数编译器恐怕不会自作主张地把它“篡改”为</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#008000">// 程序段1(改进1)<br /></font><font color="#0000ff">int</font> gaussianSum(){<br />  <font color="#0000ff">int</font> i, j=0; 
<p>  <font color="#0000ff">for</font>(i=1; i&lt;100; i++) j+=i;</p><p>  <font color="#0000ff">return</font> j;<br />}</p></td></tr></tbody></table><p>多数（但确实不是全部）编译器也不会把它改为</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><p dir="ltr"><font color="#008000">// 程序段1(改进2)<br /></font><font color="#0000ff">inline int</font> gaussianSum(){<br />  <font color="#0000ff">return</font> 5050;<br />}</p></td></tr></tbody></table><p dir="ltr">这两个修改版本都不同于原先程序的语义。首先我们看到，让i从0开始是没有必要的，因为j+=i时，i=0不会做任何有用的事情；然后是，实际上没有必要每一次都计算1+...+100的和——它可以被预先计算，并在需要的时候返回。</p><p dir="ltr">这个例子也许并不恰当(估计没人会写出最初版本那样的代码)，但这种实践在程序设计中确实可能出现。我们把改进2称为<b>编译时表达式预先计算</b>，而把改进1成为<b>循环强度削减</b>。</p><p dir="ltr">然而，一些新的编译器的确会进行这两种优化。不过别慌，看看下面的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#008000">// 程序段2</font><font color="#0000ff"><br />int</font> GetFactorial(<font color="#0000ff">int</font> k){<br />  <font color="#0000ff">int</font> i, j=1; 
<p>  <font color="#0000ff">if</font>((k&lt;0) || (k&gt;=10)) <font color="#0000ff">return</font> -1;</p><p>  <font color="#0000ff">if</font>((k&lt;=1)) <font color="#0000ff">return</font> 1</p><p>  <font color="#0000ff">for</font>(i=1; i&lt;k; i++) j*=i;</p><p>  <font color="#0000ff">return</font> j;<br />}</p></td></tr></tbody></table><p>程序采用的是一个时间复杂度为O(n)的算法，不过，我们可以把他轻易地改为O(1)的算法：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#008000">// 程序段2 (非规范改进)</font><font color="#0000ff"><br />int</font> GetFactorial(<font color="#0000ff">int</font> k){<br />  <font color="#0000ff">int</font> i, j=1; 
<p>  <font color="#0000ff">static const int</font> FractorialTable[]={1, 1, 2, 6, 24,<br />    120, 720, 5040, 40320, 362880, 3628800};</p><p>  <font color="#0000ff">if</font>((k&lt;0) || (k&gt;=10)) <font color="#0000ff">return</font> -1;</p><p>  <font color="#0000ff">return</font> FractorialTable[k];<br />}</p></td></tr></tbody></table><p>这是一个典型的以空间换时间的做法。通用的编译器不会这么做——因为它没有办法在编译时确定你是不是要这么改。可以说，如果编译器真的这样做的话，那将是一件可怕的事情，因为那时候你将很难知道编译器生成的代码和自己想的到底有多大的差距。</p><p>当然，这类优化超出了本文的范围——基本上，我把它们归入“算法优化”，而不是“程序优化”一类。类似的优化过程需要程序设计人员对于程序逻辑非常深入地了解和全盘的掌握，同时，也需要有丰富的算法知识。</p><p>自然，如果你希望自己的程序性能有大幅度的提升，那么首先应该做的是算法优化。例如，把一个<i>O</i>(n<sup>2</sup>)的算法替换为一个<i>O</i>(n)的算法，则程序的性能提升将远远超过对于个别语句的修改。此外，一个已经改写为汇编语言的程序，如果要再在算法上作大幅度的修改，其工作量将和重写相当。因此，在决定使用汇编语言进行优化之前，必须首先考虑算法优化。但假如已经是最优的算法，程序运行速度还是不够快怎么办呢？</p><p>好的，现在，假定你已经使用了已知最好的算法，决定把它交给编译器，让我们来看看编译器会为我们做什么，以及我们是否有机会插手此事，做得更好。</p><h3 dir="ltr">5.1 循环优化：强度削减和代码外提</h3><p dir="ltr">比较新的编译器在编译时会自动把下面的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">for</font>(i=0; i&lt;10; i++){<br />  j = i;<br />  k = j + i;<br />}</td></tr></tbody></table><p dir="ltr">至少变换为</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">for</font>(i=0; i&lt;10; i++);<br />j=i; k=j+i;</td></tr></tbody></table><p dir="ltr">甚至</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%">j=i=10; k=20;</td></tr></tbody></table><p dir="ltr">当然，真正的编译器实际上是在中间代码层次作这件事情。</p><p dir="ltr"><b><font color="#008000">原理</font></b>如果数据项的某个中间值(程序执行过程中的计算结果)在使用之前被另一中间值覆盖，则相关计算不必进行。</p><p dir="ltr">也许有人会问，编译器不是都给咱们做了吗，管它做什么？注意，这里说的只是编译系统中优化部分的基本设计。不仅在从源代码到中间代码的过程中存在优化问题，而且编译器生成的最终的机器语言(汇编)代码同样存在类似的问题。目前，几乎所有的编译器在最终生成代码的过程中都有或多或少的瑕疵，这些瑕疵目前<font color="#ff0000">只能</font>依靠手工修改代码来解决。</p><h3 dir="ltr">5.2 局部优化：表达式预计算和子表达式提取</h3><p dir="ltr">表达式预先计算非常简单，就是在编译时尽可能地计算程序中需要计算的东西。例如，你可以毫不犹豫地写出下面的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">const unsigned long</font> nGiga = 1024L * 1024L * 1024L;</td></tr></tbody></table><p dir="ltr">而不必担心程序每次执行这个语句时作两遍乘法，因为编译器会自动地把它改为</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">const unsigned long</font> nGiga = 1073741824L;</td></tr></tbody></table><p dir="ltr">而不是傻乎乎地让计算机在执行到这个初始化赋值语句的时候才计算。当然，如果你愿意在上面的代码中掺上一些变量的话，编译器同样会把常数部分先行计算，并拿到结果。</p><p dir="ltr">表达式预计算并不会让程序性能有飞跃性的提升，但确实减少了运行时的计算强度。除此之外，绝大多数编译器会把下面的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#008000">// [假设此时b, c, d, e, f, g, h都有一个确定的非零整数值，并且，<br />// a[]为一个包括5个整数元素的数组，其下标为0到4]</font><p>a[0] = b*c;<br />a[1] = b+c;<br />a[2] = d*e;<br />a[3] = b*d + c*d;<br />a[4] = b*d*e + c*d*e; </p></td></tr></tbody></table><p dir="ltr">优化为(再次强调，编译器实际上是在中间代码的层次，而不是源代码层次做这件事情！)：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#008000">// [假设此时b, c, d, e, f, g, h都有一个确定的非零整数值，并且，<br />// a[]为一个包括5个整数元素的数组，其下标为0到4]</font><p>a[0] = b*c;<br />a[1] = b+c;<br />a[2] = d*e;<br />a[3] = a[1] * d;<br />a[4] = a[3] * e;</p></td></tr></tbody></table><p dir="ltr">更进一步，在实际代码生成过程中，一些编译器还会对上述语句的次序进行调整，以使其运行效率更高。例如，将语句调整为下面的次序：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#008000">// [假设此时b, c, d, e, f, g, h都有一个确定的非零整数值，并且，<br />// a[]为一个包括5个整数元素的数组，其下标为0到4]</font><p>a[0] = b*c;<br />a[1] = b+c;<br />a[3] = a[1] * d;<br />a[4] = a[3] * e;<br />a[2] = d*e;</p></td></tr></tbody></table><p dir="ltr">在某些体系结构中，刚刚计算完的a[1]可以放到寄存器中，以提高实际的计算性能。上述5个计算任务之间，只有1, 3, 4三个计算任务必须串行地执行，因此，在新的处理器上，这样做甚至能够提高程序的并行度，从而使程序效率变得更高。</p><h3 dir="ltr">5.3 全局寄存器优化</h3><p dir="ltr"><i>[<font color="#ff0000">待修订内容</font>] 本章中，从这一节开始的所有优化都是在微观层面上的优化了。换言之，这些优化是不能使用高级语言中的对应设施进行解释的。这一部分内容将进行较大规模的修订。</i></p><p dir="ltr">通常，此类优化是由编译器自动完成的。我个人并不推荐真的由人来完成这些工作——这些工作多半是枯燥而重复性的，编译器通常会比人做得更好(没说的，肯定也更快)。但话说回来，使用汇编语言的程序设计人员有责任了解这些内容，因为只有这样才能更好地驾驭处理器。</p><p dir="ltr">在前面的几章中我已经提到过，寄存器的速度要比内存快。因此，在使用寄存器方面，编译器一般会做一种称为全局寄存器优化的优化。</p><p dir="ltr">例如，在我们的程序中使用了4个变量：i, j, k, l。它们都作为循环变量使用：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">for</font>(i=0; i&lt;1000; i++){<br />  <font color="#0000ff">for</font>(j=0; j&lt;1000; j++){<br />    <font color="#0000ff">for</font>(k=0; k&lt;1000; k++){<br />      <font color="#0000ff">for</font>(l=0; l&lt;1000; l++)<br />        do_something(i, j, k, l);<br />    }<br />  }<br />}</td></tr></tbody></table><p dir="ltr">这段程序的优化就不那么简单了。显然，按照通常的压栈方法，i, j, k, l应该按照某个顺序被压进堆栈，然后调用do_something()，然后函数做了一些事情之后返回。问题在于，无论如何压栈，这些东西大概都得进内存(不可否认某些机器可以用CPU的Cache做这件事情，但Cache是写通式的和回写式的又会造成一些性能上的差异)。</p><p dir="ltr">聪明的读者马上就会指出，我们不是可以在定义do_something()的时候加上inline修饰符，让它在本地展开吗？没错，本地展开以增加代码量为代价换取性能，但这只是问题的一半。编译器尽管完成了本地展开，但它仍然需要做许多额外的工作。因为寄存器只有那么有限的几个，而我们却有这么多的循环变量。</p><p dir="ltr">把四个变量按照它们在循环中使用的频率排序，并决定在do_something()块中的优先顺序(放入寄存器中的优先顺序)是一个解决方案。很明显，我们可以按照l, k, j, i的顺序(从高到低，因为l将被进行1000*1000*1000*1000次运算！)来排列，但在实际的问题中，事情往往没有这么简单，因为你不知道do_something()中做的到底是什么。而且，凭什么就以for(l=0; l&lt;1000; l++)作为优化的分界点呢？如果do_something()中还有循环怎么办？</p><p dir="ltr">如此复杂的计算问题交给计算机来做通常会有比较满意的结果。一般说来，编译器能够对程序中变量的使用进行更全面地估计，因此，它分配寄存器的结果有时虽然让人费解，但却是最优的(因为计算机能够进行大量的重复计算，并找到最好的方法；而人做这件事相对来讲比较困难)。</p><p dir="ltr">编译器在许多时候能够作出相当让人满意的结果。考虑以下的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">int</font> a=0; 
<p><font color="#0000ff">for</font>(<font color="#0000ff">int</font> i=1; i&lt;10; i++)<br />  <font color="#0000ff">for</font>(<font color="#0000ff">int</font> j=1; j&lt;100; j++){<br />    a += (i*j);<br />  }</p></td></tr></tbody></table><p dir="ltr">让我们把它变为某种形式的中间代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%">00: 0 -&gt; a<br />01: 1 -&gt; i<br />02: 1 -&gt; j<br />03: i*j -&gt; t<br />04: a+t -&gt; a<br />05: j+1 -&gt; j<br />06: <font color="#0000ff">evaluate</font> j &lt; 100<br />07: <font color="#ff0000">TRUE</font>? <font color="#0000ff">goto</font> 03<br />08: i+1 -&gt; i<br />09: <font color="#0000ff">evaluate</font> i &lt; 10<br />10: <font color="#ff0000">TRUE</font>? <font color="#0000ff">goto</font> 02<br />11: [继续执行程序的其余部分]</td></tr></tbody></table><p dir="ltr">程序中执行强度最大的无疑是03到05这一段，涉及的需要写入的变量包括a, j；需要读出的变量是i。不过，最终的编译结果大大出乎我们的意料。下面是某种优化模式下Visual C++ 6.0编译器生成的代码(我做了一些修改)：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%">xor eax, eax               <font color="#008000">; a=0(eax: a)</font><br />mov edx, 1                 <font color="#008000">; i=1(edx: i)</font><br />push esi                   <font color="#008000">; 保存esi(最后要恢复，esi作为代替j的那个循环变量)</font><br />nexti:<br />mov ecx, edx               <font color="#008000">; [t=i]</font><br />mov esi, 999               <font color="#008000">; esi=999: 此处修改了原程序的语义，但仍为1000次循环。</font><br />nextj:<br />add eax, ecx               <font color="#008000">; [a+=t]</font><br />add ecx, edx               <font color="#008000">; [t+=i]</font><br />dec esi                    <font color="#008000">; j--</font><br />jne SHORT nextj            <font color="#008000">; jne 等价于 jnz. [如果还需要，则再次循环]</font><br />inc edx                    <font color="#008000">; i++</font><br />cmp edx, 10                <font color="#008000">; i与10比较</font><br />jl SHORT nexti             <font color="#008000">; i &lt; 10, 再次循</font>环<br />pop esi                    <font color="#008000">; 恢复esi</font></td></tr></tbody></table><p dir="ltr">这段代码可能有些令人费解。主要是因为它不仅使用了大量寄存器，而且还包括了5.2节中曾提到的子表达式提取技术。表面上看，多引入的那个变量(t)增加了计算时间，但要注意，这个t不仅不会降低程序的执行效率，相反还会让它变得更快！因为同样得到了计算结果(本质上，i*j即是第j次累加i的值)，但这个结果不仅用到了上次运算的结果，而且还省去了乘法(很显然计算机计算加法要比计算乘法快)。</p><p dir="ltr">这里可能会有人问，为什么要从999循环到0，而不是按照程序中写的那样从0循环到999呢？这个问题和汇编语言中的取址有关。在下两节中我将提到这方面的内容。</p><h3 dir="ltr">5.4 x86体系结构上的并行最大化和指令封包</h3><p dir="ltr">考虑这样的问题，我和两个同伴现在在山里，远处有一口井，我们带着一口锅，身边是树林；身上的饮用水已经喝光了，此处允许砍柴和使用明火(当然我们不想引起火灾:)，需要烧一锅水，应该怎么样呢？</p><p dir="ltr">一种方案是，三个人一起搭灶，一起砍柴，一起打水，一起把水烧开。</p><p dir="ltr">另一种方案是，一个人搭灶，此时另一个人去砍柴，第三个人打水，然后把水烧开。</p><p dir="ltr">这两种方案画出图来是这样：</p><p dir="ltr" align="center"><img height="329" alt="o_7_1.gif" src="http://www.cppblog.com/images/cppblog_com/ispfcn/1308/o_7_1.gif" width="341" border="0" /></p><p dir="ltr">仅仅这样很难说明两个方案孰优孰劣，因为我们并不明确三个人一起打水、一起砍柴、一起搭灶的效率更高，还是分别作效率更高(通常的想法，一起做也许效率会更高)。但假如说，三个人一个只会搭灶，一个只会砍柴，一个只会打水(当然是说这三件事情)，那么，方案2的效率就会搞一些了。</p><p dir="ltr">在现实生活中，某个人拥有专长是比较普遍的情况；在设计计算机硬件的时候则更是如此。你不可能指望加法器不做任何改动就能去做移位甚至整数乘法，然而我们注意到，串行执行的程序不可能在同一时刻同时用到处理器的所有功能，因此，我们(很自然地)会希望有一些指令并行地执行，以充分利用CPU的计算资源。</p><p dir="ltr">CPU执行一条指令的过程基本上可以分为下面几个阶段：取指令、取数据、计算、保存数据。假设这4个阶段各需要1个时钟周期，那么，只要资源够用，并且4条指令之间不存在串行关系(换言之这些指令的执行先后次序不影响最终结果，或者，更严格地说，没有任何一条指令依赖其他指令的运算结果)指令也可以像下面这样执行：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" border="1"><tbody><tr><td bordercolor="#ffffff" align="middle" width="12%" bgcolor="#000000"><font color="#ffffff">指令1</font></td><td valign="center" align="middle" width="13%" bgcolor="#ffffcc">取指令</td><td valign="center" align="middle" width="13%" bgcolor="#ccffcc">取数据</td><td valign="center" align="middle" width="13%" bgcolor="#ffcccc">计　算</td><td valign="center" align="middle" width="13%" bgcolor="#66ffff">存数据</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td></tr><tr><td bordercolor="#ffffff" align="middle" width="12%" bgcolor="#000000"><font color="#ffffff">指令2</font></td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#ffffcc">取指令</td><td valign="center" align="middle" width="13%" bgcolor="#ccffcc">取数据</td><td valign="center" align="middle" width="13%" bgcolor="#ffcccc">计　算</td><td valign="center" align="middle" width="13%" bgcolor="#66ffff">存数据</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td></tr><tr><td bordercolor="#ffffff" align="middle" width="12%" bgcolor="#000000"><font color="#ffffff">指令3</font></td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#ffffcc">取指令</td><td valign="center" align="middle" width="13%" bgcolor="#ccffcc">取数据</td><td valign="center" align="middle" width="13%" bgcolor="#ffcccc">计　算</td><td valign="center" align="middle" width="13%" bgcolor="#66ffff">存数据</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td></tr><tr><td bordercolor="#ffffff" align="middle" width="12%" bgcolor="#000000"><font color="#ffffff">指令4</font></td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#e0e0e0">　</td><td valign="center" align="middle" width="13%" bgcolor="#ffffcc">取指令</td><td valign="center" align="middle" width="13%" bgcolor="#ccffcc">取数据</td><td valign="center" align="middle" width="13%" bgcolor="#ffcccc">计　算</td><td valign="center" align="middle" width="13%" bgcolor="#66ffff">存数据</td></tr></tbody></table><p dir="ltr">这样，原本需要16个时钟周期才能够完成的任务就可以在7个时钟周期内完成，时间缩短了一半还多。如果考虑灰色的那些方格(这些方格可以被4条指令以外的其他指令使用，只要没有串行关系或冲突)，那么，如此执行对于性能的提升将是相当可观的(此时，CPU的所有部件都得到了充分利用)。</p><p dir="ltr">当然，作为程序来说，真正做到这样是相当理想化的情况。实际的程序中很难做到彻底的并行化。假设CPU能够支持4条指令同时执行，并且，每条指令都是等周期长度的4周期指令，那么，程序需要保证同一时刻先后发射的4条指令都能够并行执行，相互之间没有关联，这通常是不太可能的。</p><p dir="ltr">最新的Intel Pentium 4-XEON处理器，以及Intel Northwood Pentium 4都提供了一种被称为超线程(Hyper-Threading<sup> TM</sup>)的技术。该技术通过在一个处理器中封装两组执行机构来提高指令并行度，并依靠操作系统的调度来进一步提升系统的整体效率。</p><p dir="ltr">由于线程机制是与操作系统密切相关的，因此，在本文的这一部分中不可能做更为深入地探讨。在后续的章节中，我将介绍Win32、FreeBSD 5.x以及Linux中提供的内核级线程机制(这三种操作系统都支持SMP及超线程技术，并且以线程作为调度单位)在汇编语言中的使用方法。</p><p dir="ltr">关于线程的讨论就此打住，因为它更多地依赖于操作系统，并且，无论如何，操作系统的线程调度需要更大的开销并且，到目前为止，真正使用支持超线程的CPU，并且使用相应操作系统的人是非常少的。因此，我们需要关心的实际上还是同一执行序列中的并发执行和指令封包。不过，令人遗憾的是，实际上在这方面编译器做的几乎是肯定要比人好，因此，你需要做的只是开启相应的优化；如果你的编译器不支持这样的特性，那么就把它扔掉……据我所知，目前在Intel平台上指令封包方面做的最好的是Intel的C++编译器，经过Intel编译器编译的代码的性能令人惊异地高，甚至在AMD公司推出的兼容处理器上也是如此。</p><h3 dir="ltr">5.5 存储优化</h3><p dir="ltr">从前一节的图中我们不难看出，方案2中，如果谁的动作慢，那么他就会成为性能的瓶颈。实际上，CPU也不会像我描述的那样四平八稳地运行，指令执行的不同阶段需要的时间(时钟周期数)是不同的，因此，缩短关键步骤(即，造成瓶颈的那个步骤)是缩短执行时间的关键。</p><p dir="ltr">至少对于使用Intel系列的CPU来说，取数据这个步骤需要消耗比较多的时间。此外，假如数据跨越了某种边界(如4或8字节，与CPU的字长有关)，则CPU需要启动两次甚至更多次数的读内存操作，这无疑对性能构成不利影响。</p><p dir="ltr">基于这样的原因，我们可以得到下面的设计策略：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><p align="center"><br /><font size="3">程序设计中的内存数据访问策略</font></p><ul type="square"><li>尽可能减少对于内存的访问。在不违背这一原则的前提下，如果可能，将数据一次处理完。 
</li><li>尽可能将数据按4或8字节对齐，以利于CPU存取 
</li><li>尽可能一段时间内访问范围不大的一段内存，而不同时访问大量远距离的分散数据，以利于Cache缓存*</li></ul></td></tr></tbody></table><p dir="ltr">第一条规则比较简单。例如，需要求一组数据中的最大值、最小值、平均数，那么，最好是在一次循环中做完。</p><p dir="ltr">“于是，这家伙又攒了一段代码”……</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">int</font> a[]={1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0};<br /><font color="#0000ff">int</font> i;<br /><font color="#0000ff">int</font> avg, max, min; 
<p>avg=max=min=a[0];</p><p><font color="#0000ff">for</font>(i=1; i&lt;(<font color="#0000ff">sizeof</font>(a)/<font color="#0000ff">sizeof</font>(<font color="#0000ff">int</font>)); i++){<br />  avg+=a[i];<br />  <font color="#0000ff">if</font>(max &lt; a[i])<br />    max = a[i];<br />  <font color="#0000ff">else</font><font color="#0000ff">if</font>(min &gt; a[i])<br />    min = a[i];<br />}</p><p>avg /= i;</p></td></tr></tbody></table><p dir="ltr">Visual C++编译器把最开始一段赋值语句翻译成了一段简直可以说是匪夷所思的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#008000">; int a[]={1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0};</font><br /><br />mov edi, 2                         <font color="#008000">; 此时edi没有意义</font><br />mov esi, 3                         <font color="#008000">; esi也是！临时变量而已。</font><br />mov DWORD PTR _a$[esp+92], edi<br />mov edx, 5                         <font color="#008000">; 黑名单加上edx</font><br />mov eax, 7                         <font color="#008000">; eax也别跑:)</font><br />mov DWORD PTR _a$[esp+132], edi<br />mov ecx, 9                         <font color="#008000">; 就差你了，ecx<br /><br />; int i;<br />; int avg, max, min;<br />; avg=max=min=a[0];</font><br /><br />mov edi, 1                         <font color="#008000">; edi摇身一变，现在它是min了。</font><br />mov DWORD PTR _a$[esp+96], esi<br />mov DWORD PTR _a$[esp+104], edx<br />mov DWORD PTR _a$[esp+112], eax<br />mov DWORD PTR _a$[esp+136], esi<br />mov DWORD PTR _a$[esp+144], edx<br />mov DWORD PTR _a$[esp+152], eax<br />mov DWORD PTR _a$[esp+88], 1       <font color="#008000">; 编译器失误? 此处edi应更好</font><br />mov DWORD PTR _a$[esp+100], 4<br />mov DWORD PTR _a$[esp+108], 6<br />mov DWORD PTR _a$[esp+116], 8<br />mov DWORD PTR _a$[esp+120], ecx<br />mov DWORD PTR _a$[esp+124], 0<br />mov DWORD PTR _a$[esp+128], 1<br />mov DWORD PTR _a$[esp+140], 4<br />mov DWORD PTR _a$[esp+148], 6<br />mov DWORD PTR _a$[esp+156], 8<br />mov DWORD PTR _a$[esp+160], ecx<br />mov DWORD PTR _a$[esp+164], 0<br />mov edx, edi                      <font color="#008000"> ; edx是max。</font><br />mov eax, edi                      <font color="#008000"> ; 期待已久的avg, 它被指定为eax</font></td></tr></tbody></table><p dir="ltr">这段代码是最优的吗？我个人认为不是。因为编译器完全可以在编译过程中直接把它们作为常量数据放入内存。此外，如果预先对a[0..9]10个元素赋值，并利用串操作指令(rep movsdw)，速度会更快一些。</p><p dir="ltr">当然，犯不上因为这些问题责怪编译器。要求编译器知道a[0..9]和[10..19]的内容一样未免过于苛刻。我们看看下面的指令段：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="40%"><font color="#008000">; for(i=1; ...</font><br /><br />mov esi, edi<br />for_loop:<br /><br /><font color="#008000">; avg+=a[i];</font><br /><br />mov ecx, DWORD PTR _a$[esp+esi*4+88]<br />add eax, ecx<br /><br /><font color="#008000">; if(max &lt; a[i])</font><br /><br />cmp edx, ecx<br />jge SHORT elseif_min<br /><br /><font color="#008000">; max = a[i];</font><br /><br />mov edx, ecx<br /><br /><font color="#008000">; else if(min &gt; a[i])</font><br /><br />jmp SHORT elseif_min<br />elseif_min:<br />cmp edi, ecx<br />jle SHORT elseif_end<br /><br /><font color="#008000">; min = a[i];</font><br />mov edi, ecx<br /><br />elseif_end:<br /><br /><font color="#008000">; [for i=1]; i&lt;20; i++){</font><br /><br />inc esi<br />cmp esi, 20<br />jl SHORT for_loop<br /><br /><font color="#008000">; }<br />; avg /= i;</font><br /><br />cdq<br />idiv esi</td><td valign="top" width="60%"><font color="#008000"><br /><br />; esi: i<br /><br /><br /><br /><br />; ecx: 暂存变量, =a[i]<br />; eax: avg<br /><br /><br /><br />; edx: max<br /><br /><br /><br /><br /><br /><br /><br /><br />; 有趣的代码...并不是所有的时候都有用<br />; 但是也别随便删除<br />; edi: min<br /><br /><br /><br /><br /><br /><br /><br /><br /><br />; i++<br />; i与20比较<br /><br /><br /><br /><br /><br /><br />; avg /= i</font></td></tr></tbody></table><p dir="ltr">上面的程序倒是没有什么惊人之处。唯一一个比较吓人的东西是那个jmp SHORT指令，它是否有用取决于具体的问题。C/C++编译器有时会产生这样的代码，我过去曾经错误地把所有的此类指令当作没用的代码而删掉，后来发现程序执行时间没有明显的变化。通过查阅文档才知道，这类指令实际上是“占位指令”，他们存在的意义在于占据那个地方，一来使其他语句能够正确地按CPU觉得舒服的方式对齐，二来它可以占据CPU的某些周期，使得后续的指令能够更好地并发执行，避免冲突。另一个比较常见的、实现类似功能的指令是NOP。</p><p dir="ltr">占位指令的去留主要是靠计时执行来判断。由于目前流行的操作系统基本上都是多任务的，因此会对计时的精确性有一定影响。如果需要进行测试的话，需要保证以下几点：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="4" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><p align="center"><br /><font size="3">计时测试需要注意的问题</font></p><ul type="square"><li>测试必须在没有额外负荷的机器上完成。例如，专门用于编写和调试程序的计算机 
</li><li>尽量终止计算机上运行的所有服务，特别是杀毒程序 
</li><li>切断计算机的网络，这样网络的影响会消失 
</li><li>将进程优先级调高。对于Windows系统来说，把进程(线程)设置为Time-Critical; 对于*nix系统来说，把进程设置为实时进程 
</li><li>将测试函数运行尽可能多次运行，如10000000次，这样能够减少由于进城切换而造成的偶然误差 
</li><li>最后，如果可能的话，把函数放到单进程的系统(例如FreeDOS)中运行。</li></ul></td></tr></tbody></table><p>对于绝大多数程序来说，计时测试是一个非常重要的东西。我个人倾向于在进行优化后进行计时测试并比较结果。目前，我基于经验进行的优化基本上都能够提高程序的执行性能，但我还是不敢过于自信。优化确实会提高性能，但人做的和编译器做的思路不同，有时，我们的确会做一些费力不讨好的事情。</p><img src ="http://www.cppblog.com/ispfcn/aggbug/14720.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2006-11-06 10:39 <a href="http://www.cppblog.com/ispfcn/archive/2006/11/06/14720.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>简明x86汇编语言教程（六）</title><link>http://www.cppblog.com/ispfcn/archive/2006/11/06/14719.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Mon, 06 Nov 2006 02:36:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2006/11/06/14719.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/14719.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2006/11/06/14719.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/14719.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/14719.html</trackback:ping><description><![CDATA[原创：司徒彦南 <br /><br /><h3>4.0 利用子程序与中断</h3><p>已经掌握了汇编语言？没错，你现在已经可以去破译别人代码中的秘密。然而，我们还有一件重要的东西没有提到，那就是自程序和中断。这两件东西是如此的重要，以至于你的程序几乎不可能离开它们。</p><h4>4.1 子程序</h4><p>在高级语言中我们经常要用到子程序。高级语言中，子程序是如此的神奇，我们能够定义和主程序，或其他子程序一样的变量名，而访问不同的变量，并且，还不和程序的其他部分相冲突。</p><p>然而遗憾的是，这种“优势”在汇编语言中是不存在的。</p><p>汇编语言并不注重如何减轻程序员的负担；相反，汇编语言依赖程序员的良好设计，以期发挥CPU的最佳性能。汇编语言不是结构化的语言，因此，它不提供直接的“局部变量”。如果需要“局部变量”，只能通过堆或栈自行实现。</p><p>从这个意义上讲，汇编语言的子程序更像GWBASIC中的GOSUB调用的那些“子程序”。所有的“变量”(本质上，属于进程的内存和寄存器)为整个程序所共享，高级语言编译器所做的，将局部变量放到堆或栈中的操作，只能自行实现。</p><p>参数的传递是靠寄存器和堆栈来完成的。高级语言中，子程序(函数、过程，或类似概念的东西)依赖于堆和栈来传递。</p><p>让我们来简单地分析一下一般高级语言的子程序的执行过程。无论C、C++、BASIC、Pascal，这一部分基本都是一致的。</p><table style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><ul type="square"><br /><li>调用者将子程序执行完成时应返回的地址、参数压入堆栈 
</li><li>子程序使用BP指针+偏移量对栈中的参数寻址，并取出、完成操作 
</li><li>子程序使用RET或RETF指令返回。此时，CPU将IP置为堆栈中保存的地址，并继续予以执行</li></ul></td></tr></tbody></table><p>毋庸置疑，堆栈在整个过程中发挥着非常重要的作用。不过，本质上对子程序最重要的还是返回地址。如果子程序不知道这个地址，那么系统将会崩溃。</p><p>调用子程序的指令是CALL，对应的返回指令是RET。此外，还有一组指令，即ENTER和LEAVE，它们可以帮助进行堆栈的维护。</p><p>CALL指令的参数是被调用子程序的地址。使用宏汇编的时候，这通常是一个标号。CALL和RET，以及ENTER和LEAVE配对，可以实现对于堆栈的自动操作，而不需要程序员进行PUSH/POP，以及跳转的操作，从而提高了效率。</p><p>作为一个编译器的实现实例，我用Visual C++编译了一段C++程序代码，这段汇编代码是使用特定的编译选项得到的结果，正常的RELEASE代码会比它精简得多。包含源代码的部分反汇编结果如下(取自Visual C++调试器的运行结果，我删除了10条int 3指令，并加上了一些注释，除此之外，没有做任何修改)：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font face="Courier New"><i><font color="#ff0000">1: </font><font color="#0000ff">int</font> myTransform(<font color="#0000ff">int</font> nInput){<br /></i>00401000 push ebp                   <font color="#008000">; 保护现场原先的EBP指针</font><br />00401001 mov ebp,esp<br /><i><font color="#ff0000">2: </font><font color="#0000ff">return</font> (nInput*2 + 3) % 7;<br /></i>00401003 mov eax,dword ptr [nInput] <font color="#008000">; 取参数</font><br />00401006 lea eax,[eax+eax+3]        <font color="#008000">; LEA比ADD加法更快</font><br />0040100A cdq                        <font color="#008000">; DWORD-&gt;QWORD(扩展字长)</font><br />0040100B mov ecx,7                  <font color="#008000">; 除数</font><br />00401010 idiv eax,ecx               <font color="#008000">; 除</font><br />00401012 mov eax,edx                <font color="#008000">; 商-&gt;eax(eax中保存返回值)</font><br /><i><font color="#ff0000">3:</font> }<br /></i>00401014 pop ebp                    <font color="#008000">; 恢复现场的ebp指针</font><br />00401015 ret                        <font color="#008000">; 返回<br />; 此处删除10条int 3指令，它们是方便调试用的，并不影响程序行为。</font><br /><i><font color="#ff0000">4:<br />5: </font><font color="#0000ff">int</font> main(<font color="#0000ff">int</font> argc, <font color="#0000ff">char</font>* argv[])<br /><font color="#ff0000">6:</font> {<br /></i>00401020 push ebp                   <font color="#008000">; 保护现场原先的EBP指针</font><br />00401021 mov ebp,esp<br />00401023 sub esp,10h                <font color="#008000">; 为取argc, argv修正堆栈指针。<br /></font><i><font color="#ff0000">7:</font><font color="#0000ff">int</font> a[3];<br /><font color="#ff0000">8:</font><font color="#0000ff">for</font>(<font color="#0000ff">register int</font> i=0; i&lt;3; i++){<br /></i>00401026 mov dword ptr [i],0        <font color="#008000">; 0-&gt;i</font><br />0040102D jmp main+18h (00401038)    <font color="#008000">; 判断循环条件</font><br />0040102F mov eax,dword ptr [i]      <font color="#008000">; i-&gt;eax</font><br />00401032 add eax,1                  <font color="#008000">; eax ++</font><br />00401035 mov dword ptr [i],eax      <font color="#008000">; eax-&gt;i</font><br />00401038 cmp dword ptr [i],3        <font color="#008000">; 循环条件: i与3比较</font><br />0040103C jge main+33h (00401053)    <font color="#008000">; 如果不符合条件，则应结束循环</font><br /><i><font color="#ff0000">9:</font> a[i] = myTransform(i);<br /></i>0040103E mov ecx,dword ptr [i]      <font color="#008000">; i-&gt;ecx</font><br />00401041 push ecx                   <font color="#008000">; ecx (i) -&gt; 堆栈</font><br />00401042 call myTransform (00401000)<font color="#008000">; 调用myTransform</font><br />00401047 add esp,4                  <font color="#008000">; esp+=4: 在堆中的新单元<br />                                    ; 准备存放返回结果<br /></font>0040104A mov edx,dword ptr [i]      <font color="#008000">; i-&gt;edx</font><br />0040104D mov dword ptr a[edx*4],eax <font color="#008000">; 将eax(myTransform返回值)<br />                                    ; 放回a[i]<br /></font><i><font color="#ff0000">10:</font> }<br /></i>00401051 jmp main+0Fh (0040102f)    <font color="#008000">; 计算i++，并继续循环</font><br /><i><font color="#ff0000">11:</font><font color="#0000ff">return</font> 0;<br /></i>00401053 xor eax,eax                <font color="#008000">; 返回值应该是0</font><br /><i><font color="#ff0000">12:</font> }<br /></i>00401055 mov esp,ebp                <font color="#008000">; 恢复堆栈指针</font><br />00401057 pop ebp                    <font color="#008000">; 恢复BP</font><br />00401058 ret                        <font color="#008000">; 返回调用者(C++运行环境)</font></font></td></tr></tbody></table><p>上述代码确实做了一些无用功，当然，这是因为编译器没有对这段代码进行优化。让我们来关注一下这段代码中，是如何调用子程序的。不考虑myTransform这个函数实际进行的数值运算，最让我感兴趣的是这一行代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font face="Courier New">00401003 mov eax,dword ptr [nInput] <font color="#008000">; 取参数</font></font></td></tr></tbody></table><p>这里nInput是一个简简单单的变量符号吗？Visual C++的调试器显然不能告诉我们答案——它的设计目标是为了方便程序调试，而不是向你揭示编译器生成的代码的实际构造。我用另外一个反汇编器得到的结果是：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font face="Courier New">00401003 mov eax,dword ptr [ebp+8] <font color="#008000">  ; 取参数</font></font></td></tr></tbody></table><p>这和我们在main()中看到的压栈顺序是完全吻合的(注意，程序运行到这个地方的时候，EBP=ESP)。main()最终将i的<b>值</b>通过堆栈传递给了myTransform()。</p><p>剖析上面的程序只是说明了我前面所提到的子程序的一部分用法。对于汇编语言来说，完全没有必要拘泥于结构化程序设计的框架(在今天，使用汇编的主要目的在于提高执行效率，而不是方便程序的维护和调试，因为汇编不可能在这一点上做得比C++更好)。考虑下面的程序：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><p dir="ltr"><font color="#0000ff">void</font> myTransform1(<font color="#0000ff">int</font> nCount, <font color="#0000ff">char</font>* sBytes){<br />  <font color="#0000ff">for</font>(<font color="#0000ff">register int</font> i=1; i&lt;nCount; i++)<br />    sBytes[i] += sBytes[i-1];<br />  <font color="#0000ff">for</font>(i=0; i&lt;nCount; i++)<br />    sBytes[i] &lt;&lt;= 1;<br />}<br /><br /><font color="#0000ff">void</font> myTransform2(<font color="#0000ff">int</font> nCount, <font color="#0000ff">char</font>* sBytes){<br />  <font color="#0000ff">for</font>(<font color="#0000ff">register int</font> i=0; i&lt;nCount; i++)<br />    sBytes[i] &lt;&lt;= 1;<br />}</p></td></tr></tbody></table><p>很容易看出，这两个函数包含了公共部分，即</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%">  <font color="#0000ff">for</font>(i=0; i&lt;nCount; i++)<br />    sBytes[i] &lt;&lt;= 1;</td></tr></tbody></table><p>目前，还没有编译器能够做到将这两部分合并。依然沿用刚才的编译选项，得到的反汇编结果是(同样地删除了int 3)：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><i><font color="#ff0000">1:</font><font color="#0000ff">void</font> myTransform1(<font color="#0000ff">int</font> nCount, <font color="#0000ff">char</font>* sBytes){<br /></i>00401000 push ebp<br />00401001 mov ebp,esp<br />00401003 push ecx<br /><i><font color="#ff0000">2:</font><font color="#0000ff">for</font>(<font color="#0000ff">register int</font> i=1; i&lt;nCount; i++)<br /></i>00401004 mov dword ptr [i],1<br />0040100B jmp myTransform1+16h (00401016)<br />0040100D mov eax,dword ptr [i]<br />00401010 add eax,1<br />00401013 mov dword ptr [i],eax<br />00401016 mov ecx,dword ptr [i]<br />00401019 cmp ecx,dword ptr [nCount]<br />0040101C jge myTransform1+3Dh (0040103d)<br /><i><font color="#ff0000">3:</font> sBytes[i] += sBytes[i-1];<br /></i>0040101E mov edx,dword ptr [sBytes]<br />00401021 add edx,dword ptr [i]<br />00401024 movsx eax,byte ptr [edx-1]<br />00401028 mov ecx,dword ptr [sBytes]<br />0040102B add ecx,dword ptr [i]<br />0040102E movsx edx,byte ptr [ecx]<br />00401031 add edx,eax<br />00401033 mov eax,dword ptr [sBytes]<br />00401036 add eax,dword ptr [i]<br />00401039 mov byte ptr [eax],dl<br />0040103B jmp myTransform1+0Dh (0040100d)<br /><i><font color="#ff0000">4:</font><font color="#0000ff">for</font>(i=0; i&lt;nCount; i++)<br /></i>0040103D mov dword ptr [i],0<br />00401044 jmp myTransform1+4Fh (0040104f)<br />00401046 mov ecx,dword ptr [i]<br />00401049 add ecx,1<br />0040104C mov dword ptr [i],ecx<br />0040104F mov edx,dword ptr [i]<br />00401052 cmp edx,dword ptr [nCount]<br />00401055 jge myTransform1+6Bh (0040106b)<br /><i><font color="#ff0000">5:</font> sBytes[i] &lt;&lt;= 1;<br /></i>00401057 mov eax,dword ptr [sBytes]<br />0040105A add eax,dword ptr [i]<br />0040105D mov cl,byte ptr [eax]<br />0040105F shl cl,1<br />00401061 mov edx,dword ptr [sBytes]<br />00401064 add edx,dword ptr [i]<br />00401067 mov byte ptr [edx],cl<br />00401069 jmp myTransform1+46h (00401046)<br /><i><font color="#ff0000">6:</font> }<br /></i>0040106B mov esp,ebp<br />0040106D pop ebp<br />0040106E ret<br /><i><font color="#ff0000">7:</font><br /><font color="#ff0000">8:</font><font color="#0000ff">void</font> myTransform2(<font color="#0000ff">int</font> nCount, <font color="#0000ff">char</font>* sBytes){<br /></i>00401070 push ebp<br />00401071 mov ebp,esp<br />00401073 push ecx<br /><i><font color="#ff0000">9:</font><font color="#0000ff">for</font>(<font color="#0000ff">register int</font> i=0; i&lt;nCount; i++)<br /></i>00401074 mov dword ptr [i],0<br />0040107B jmp myTransform2+16h (00401086)<br />0040107D mov eax,dword ptr [i]<br />00401080 add eax,1<br />00401083 mov dword ptr [i],eax<br />00401086 mov ecx,dword ptr [i]<br />00401089 cmp ecx,dword ptr [nCount]<br />0040108C jge myTransform2+32h (004010a2)<br /><font color="#ff0000">10:</font> sBytes[i] &lt;&lt;= 1;<br />0040108E mov edx,dword ptr [sBytes]<br />00401091 add edx,dword ptr [i]<br />00401094 mov al,byte ptr [edx]<br />00401096 shl al,1<br />00401098 mov ecx,dword ptr [sBytes]<br />0040109B add ecx,dword ptr [i]<br />0040109E mov byte ptr [ecx],al<br />004010A0 jmp myTransform2+0Dh (0040107d)<br /><i><font color="#ff0000">11:</font> }<br /></i>004010A2 mov esp,ebp<br />004010A4 pop ebp<br />004010A5 ret<br /><i><font color="#ff0000">12:<br />13:</font><font color="#0000ff">int</font> main(<font color="#0000ff">int</font> argc, <font color="#0000ff">char</font>* argv[])<br /><font color="#ff0000">14:</font> {<br /></i>004010B0 push ebp<br />004010B1 mov ebp,esp<br />004010B3 sub esp,0CCh<br /><i><font color="#ff0000">15:</font><font color="#0000ff">char</font> a[200];<br /><font color="#ff0000">16:</font><font color="#0000ff">for</font>(<font color="#0000ff">register int</font> i=0; i&lt;200; i++)a[i]=i;<br /></i>004010B9 mov dword ptr [i],0<br />004010C3 jmp main+24h (004010d4)<br />004010C5 mov eax,dword ptr [i]<br />004010CB add eax,1<br />004010CE mov dword ptr [i],eax<br />004010D4 cmp dword ptr [i],0C8h<br />004010DE jge main+45h (004010f5)<br />004010E0 mov ecx,dword ptr [i]<br />004010E6 mov dl,byte ptr [i]<br />004010EC mov byte ptr a[ecx],dl<br />004010F3 jmp main+15h (004010c5)<br /><i><font color="#ff0000">17:</font> myTransform1(200, a);<br /></i>004010F5 lea eax,[a]<br />004010FB push eax<br />004010FC push 0C8h<br />00401101 call myTransform1 (00401000)<br />00401106 add esp,8<br /><i><font color="#ff0000">18:</font> myTransform2(200, a);<br /></i>00401109 lea ecx,[a]<br />0040110F push ecx<br />00401110 push 0C8h<br />00401115 call myTransform2 (00401070)<br />0040111A add esp,8<br /><i><font color="#ff0000">19:</font><font color="#0000ff">return</font> 0;<br /></i>0040111D xor eax,eax<br /><i><font color="#ff0000">20:</font> }</i><br />0040111F mov esp,ebp<br />00401121 pop ebp<br />00401122 ret</td></tr></tbody></table><p>非常明显地，0040103d-0040106e和00401074-004010a5这两段代码存在少量的差别，但很显然只是对寄存器的偏好不同(编译器在优化时，这可能会减少堆栈操作，从而提高性能，但在这里只是使用了不同的寄存器而已)</p><p>对代码进行合并的好处是非常明显的。新的操作系统往往使用页式内存管理。当内存不足时，程序往往会频繁引发页面失效(Page faults)，从而引发操作系统从磁盘中读取一些东西。磁盘的速度赶不上内存的速度，因此，这一行为将导致性能的下降。通过合并一部分代码，可以减少程序的大小，这意味着减少页面失效的可能性，从而软件的性能会有所提高?/p&gt; 
</p><p>当然，这样做的代价也不算低——你的程序将变得难懂，并且难于维护。因此，再进行这样的优化之前，一定要注意：</p><table style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><ul type="square"><br /><li>优化前的程序<font color="#ff0000">必须</font>是正确的。如果你不能确保这一点，那么这种优化必将给你的调试带来极大的麻烦。 
</li><li>优化前的程序实现<font color="#ff0000">最好</font>是最优的。仔细检查你的设计，看看是否已经使用了最合适(即，对于此程序而言最优)的算法，并且已经在高级语言许可的范围内进行了最好的实现。 
</li><li>优化<font color="#ff0000">最好</font>能够非常有效地减少程序大小(例如，如果只是减少十几个字节，恐怕就没什么必要了)，或非常有效地提高程序的运行速度(如果代码只是运行一次，并且只是节省几个时钟周期，那么在多数场合都没有意义)。否则，这种优化将得不偿失。</li></ul></td></tr></tbody></table><h4>4.2 中断</h4><p>中断应该说是一个陈旧的话题。在新的系统中，它的作用正在逐渐被削弱，而变成操作系统专用的东西。并不是所有的计算机系统都提供中断，然而在x86系统中，它的作用是不可替代的。</p><p>中断实际上是一类特殊的子程序。它通常由系统调用，以响应突发事件。</p><p>例如，进行磁盘操作时，为了提高性能，可能会使用DMA方式进行操作。CPU向DMA控制器发出指令，要求外设和内存直接交换数据，而不通过CPU。然后，CPU转去进行起他的操作；当数据交换结束时，CPU可能需要进行一些后续操作，但此时它如何才能知道DMA已经完成了操作呢？</p><p>很显然不是依靠CPU去查询状态——这样DMA的优势就不明显了。为了尽可能地利用DMA的优势，在完成DMA操作的时候，DMA会告诉CPU“这事儿我办完了”，然后CPU会根据需要进行处理。</p><p>这种处理可能很复杂，需要若干条指令来完成。子程序是一个不错的主意，不过，CALL指令需要指定地址，让外设强迫CPU执行一条CALL指令也违背了CPU作为核心控制单元的设计初衷。考虑到这些，在x86系统中引入了中断向量的概念。</p><p>中断向量表是保存在系统数据区(实模式下，是0:0开始的一段区域)的一组指针。这组指针指向每一个中断服务程序的地址。整个中断向量表的结构是一个线性表。</p><p>每一个中断服务有自己的唯一的编号，我们通常称之为中断号。每一个中断号对应中断向量表中的一项，也就是一个中断向量。外设向CPU发出中断请求，而CPU自己将根据当前的程序状态决定是否中断当前程序并调用相应的中断服务。</p><p>不难根据造成中断的原因将中断分为两类：硬件中断和软件中断。硬件中断有很多分类方法，如根据是否可以屏蔽分类、根据优先级高低分类，等等。考虑到这些分类并不一定科学，并且对于我们介绍中断的使用没有太大的帮助，因此我并不打算太详细地介绍它(在本教程的高级篇中，关于加密解密的部分会提到某些硬件中断的利用，但那是后话)。</p><p>在设计操作系统时，中断向量的概念曾经带来过很大的便利。操作系统随时可能升级，这样，通过CALL来调用操作系统的服务(如果说每个程序都包含对于文件系统、进程表这些应该由操作系统管理的数据的直接操作的话，不仅会造成程序的臃肿，而且不利于系统的安全)就显得不太合适了——没人能知道，以后的操作系统的服务程序入口点会不会是那儿。软件中断的存在为解决这个问题提供了方便。</p><p>对于一台包含了BIOS的计算机来说，启动的时候系统已经提供了一部分服务，例如显示服务。无论你的BIOS、显示卡有多么的“个性”，只要他们和IBM PC兼容，那么此时你肯定可以通过调用16(10h)号中断来使用显示服务。调用中断的指令是</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><p align="center"><br />int 中断号<br />  </p></td></tr></tbody></table><p>这将引发CPU去调用一个中断。CPU将保存当前的程序状态字，清除Trap和Interrupt两个标志，将即将执行的指令地址压入堆栈，并调用中断服务(根据中断向量表)。</p><p>编写中断服务程序不是一件容易的事情。很多时候，中断服务程序必须写成<b>可重入代码</b>(或纯代码，pure code)。所谓可重入代码是指，程序的运行过程中可以被打断，并由开始处再次执行，并且在合理的范围内(多次重入，而不造成堆栈溢出等其他问题)，程序可以在被打断处继续执行，并且执行结果不受影响。</p><p>由于在多线程环境中等其他一些地方进行程序设计时也需要考虑这个因素，因此这里着重讲一下可重入代码的编写。</p><p>可重入代码最主要的要求就是，程序不应使用某个指定的内存地址的内存(对于高级语言来说，这通常是全局变量，或对象的成员)。如果可能的话，应使用寄存器，或其他方式来解决。如果不能做到这一点，则必须在开始、结束的时候分别禁止和启用中断，并且，运行时间不能太长。</p><p>下面用C语言分别举一个可重入函数，和两个非可重入函数的例子(注. 这些例子应该是在某本多线程或操作系统的书上看到的，遗憾的是我想不起来是哪本书了，在这里先感谢那位作者提供的范例)：</p><p>可重入函数：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">void</font> strcpy(<font color="#0000ff">char</font>* lpszDest, <font color="#0000ff">char</font>* lpszSrc){<br />  <font color="#0000ff">while</font>(*dest++=*src++);<br />  *dest=0;<br />}</td></tr></tbody></table><p>非可重入函数</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">char</font> cTemp;                 <font color="#008000">                  // 全局变量</font><br /><br /><font color="#0000ff">void</font> SwapChar(<font color="#0000ff">char</font>* lpcX, <font color="#0000ff">char</font>* lpcY){<br />  cTemp = *lpcX; *lpcX = *lpcY; lpcY = cTemp; <font color="#008000">// 引用了全局变量，在分享内存的多个线程中可能造成问题</font><br />}</td></tr></tbody></table><p>非可重入函数</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%"><font color="#0000ff">void</font> SwapChar2(<font color="#0000ff">char</font>* lpcX, <font color="#0000ff">char</font>* lpcY){<br /><font color="#0000ff">  static char</font> cTemp;                 <font color="#008000">         // 静态变量</font><br />  cTemp = *lpcX; *lpcX = *lpcY; lpcY = cTemp; <font color="#008000">// 引用了静态变量，在分享内存的多个线程中可能造成问题</font><br />}</td></tr></tbody></table><p>中断利用的是系统的栈。栈操作是可重入的(因为栈可以保证“先进后出”)，因此，我们并不需要考虑栈操作的重入问题。使用宏汇编器写出可重入的汇编代码需要注意一些问题。简单地说，干脆不要用标号作为变量是一个不错的主意。</p><p>使用高级语言编写可重入程序相对来讲轻松一些。把持住不访问那些全局(或当前对象的)变量，不使用静态局部变量，坚持只适用局部变量，写出的程序就将是可重入的。</p><p>书归正传，调用软件中断时，通常都是通过寄存器传进、传出参数。这意味着你的int指令周围也许会存在一些“帮手”，比如下面的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%">mov ax, 4c00h<br />int 21h</td></tr></tbody></table><p>就是通过调用DOS中断服务返回父进程，并带回错误反馈码0。其中，ax中的数据4c00h就是传递给DOS中断服务的参数。</p><p>到这里，x86汇编语言的基础部分就基本上讲完了，《简明x86汇编语言教程》的初级篇——汇编语言基础也就到此告一段落。当然，目前为止，我只是蜻蜓点水一般提到了一些学习x86汇编语言中我认为需要注意的重要概念。许多东西，包括全部汇编语句的时序特性(指令执行周期数，以及指令周期中各个阶段的节拍数等)、功能、参数等等，限于个人水平和篇幅我都没有作详细介绍。如果您对这些内容感兴趣，请参考Intel和AMD两大CPU供应商网站上提供的开发人员参考。</p><p>在以后的简明x86汇编语言教程中级篇和高级篇中，我将着重介绍汇编语言的调试技术、优化，以及一些具体的应用技巧，包括反跟踪、反反跟踪、加密解密、病毒与反病毒等等。</p><img src ="http://www.cppblog.com/ispfcn/aggbug/14719.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2006-11-06 10:36 <a href="http://www.cppblog.com/ispfcn/archive/2006/11/06/14719.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> 简明x86汇编语言教程（五） </title><link>http://www.cppblog.com/ispfcn/archive/2006/11/06/14718.html</link><dc:creator>编程之道</dc:creator><author>编程之道</author><pubDate>Mon, 06 Nov 2006 02:33:00 GMT</pubDate><guid>http://www.cppblog.com/ispfcn/archive/2006/11/06/14718.html</guid><wfw:comment>http://www.cppblog.com/ispfcn/comments/14718.html</wfw:comment><comments>http://www.cppblog.com/ispfcn/archive/2006/11/06/14718.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/ispfcn/comments/commentRss/14718.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/ispfcn/services/trackbacks/14718.html</trackback:ping><description><![CDATA[原创：司徒彦南<br /><br /><h3>3.4 串操作</h3><p>我们前面已经提到，内存可以和寄存器交换数据，也可以被赋予立即数。问题是，如果我们需要把内存的某部分内容复制到另一个地址，又怎么做呢？</p><p>设想将DS:SI处的连续512字节内容复制到ES:DI（先不考虑可能的重叠）。也许会有人写出这样的代码：</p><table class="code" style="BORDER-COLLAPSE: collapse" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td valign="top" width="15%"><br />NextByte:</td><td valign="top" width="52%">mov cx,512<br />mov al,ds:[si]<br />mov es:[di],al<br />inc si<br />inc di<br />loop NextByte</td><td valign="top" width="33%"><font color="#008000">; 循环次数</font></td></tr></tbody></table><p>我不喜欢上面的代码。它的确能达到作用，但是，效率不好。如果你是在做优化，那么写出这样的代码意味着赔了夫人又折兵。</p><p>Intel的CPU的强项是串操作。所谓串操作就是由CPU去完成某一数量的、重复的内存操作。需要说明的是，我们常用的KMP算法（用于匹配字符串中的模式）的改进——Boyer算法，由于没有利用串操作，因此在Intel的CPU上的效率并非最优。好的编译器往往可以利用Intel CPU的这一特性优化代码，然而，并非所有的时候它都能产生最好的代码。</p><p>某些指令可以加上REP前缀（repeat, 反复之意），这些指令通常被叫做串操作指令。</p><p>举例来说，STOSD指令将EAX的内容保存到ES:DI，同时在DI上加或减四。类似的，STOSB和STOSW分别作1字节或1字的上述操作，在DI上加或减的数是1或2。</p><p>计算机语言通常是不允许二义性的。为什么我要说“加或减”呢？没错，孤立地看STOS?指令，并不能知道到底是加还是减，因为这取决于“方向”标志(DF, Direction Flag)。如果DF被复位，则加；反之则减。</p><p>置位、复位的指令分别是STD和CLD。</p><p>当然，REP只是几种可用前缀之一。常用的还包括REPNE，这个前缀通常被用来比较两个串，或搜索某个特定字符（字、双字）。REPZ、REPE、REPNZ也是非常常用的指令前缀，分别代表ZF(Zero Flag)在不同状态时重复执行。</p><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="33%" bgcolor="#008000"><font color="#ffffff">助记符</font></td><td align="middle" width="67%" bgcolor="#008000"><font color="#ffffff">意义</font></td></tr><tr><td align="middle" width="33%">movsb</td><td width="67%">将DS:SI的一字节复制到ES:DI，之后SI++、DI++</td></tr><tr><td align="middle" width="33%" bgcolor="#e0e0e0">movsw</td><td width="67%" bgcolor="#e0e0e0">将DS:SI的一字节复制到ES:DI，之后SI+=2、DI+=2</td></tr><tr><td align="middle" width="33%">movsd</td><td width="67%">将DS:SI的一字节复制到ES:DI，之后SI+=4、DI+=4</td></tr></tbody></table><p>于是上面的程序改写为</p><table class="code" id="AutoNumber3" style="BORDER-COLLAPSE: collapse" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td valign="top" width="50%">cld<br />mov cx, 128<br />rep movsd</td><td valign="top" width="50%"><font color="#008000">; 复位DF<br />; 512/4 = 128，共128个双字<br />; 行动！</font></td></tr></tbody></table><p>第一句cld很多时候是多余的，因为实际写程序时，很少会出现置DF的情况。不过在正式决定删掉它之前，建议你仔细地调试自己的程序，并确认每一个能够走到这里的路径中都不会将DF置位。</p><p>错误（非预期的）的DF是危险的。它很可能断送掉你的程序，因为这直接造成<b>缓冲区溢出</b>问题。</p><p>什么是缓冲区溢出呢？缓冲区溢出分为两类，一类是写入缓冲区以外的内容，一类是读取缓冲区以外的内容。后一种往往更隐蔽，但随便哪一个都有可能断送掉你的程序。</p><p>缓冲区溢出对于一个网络服务来说很可能更加危险。怀有恶意的用户能够利用它执行自己希望的指令。服务通常拥有更高的特权，而这很可能会造成特权提升；即使不能提升攻击者拥有的特权，他也可以利用这种问题使服务崩溃，从而形成一次成功的DoS（拒绝服务）攻击。每年CERT的安全公告中，都有6成左右的问题是由于缓冲区溢出造成的。</p><p>在使用汇编语言，或C语言编写程序时，很容易在无意中引入缓冲区溢出。然而并不是所有的语言都会引入缓冲区溢出问题，Java和C#，由于没有指针，并且缓冲区采取动态分配的方式，有效地消除了造成缓冲区溢出的土壤。</p><p>汇编语言中，由于REP*前缀都用CX作为计数器，因此情况会好一些（当然，有时也会更糟糕，因为由于CX的限制，很可能使原本可能改变程序行为的缓冲区溢出的范围缩小，从而更为隐蔽）。避免缓冲区溢出的一个主要方法就是仔细检查，这包括两方面：设置合理的缓冲区大小，和根据大小编写程序。除此之外，非常重要的一点就是，在汇编语言这个级别写程序，你肯定希望去掉所有的无用指令，然而再去掉之前，一定要进行严格的测试；更进一步，如果能加上注释，并通过善用宏来做调试模式检查，往往能够达到更好的效果。</p><h3>3.5 关于保护模式中内存操作的一点说明</h3><p>正如3.2节提到到的那样，保护模式中，你可以使用32位的线性地址，这意味着直接访问4GB的内存。由于这个原因，选择器不用像实模式中段寄存器那样频繁地修改。顺便提一句，这份教程中所说的保护模式指的是386以上的保护模式，或者，Microsoft通常称为“增强模式”的那种。</p><p>在为选择器装入数值的时候一定要非常小心。错误的数值往往会导致无效页面错误(在Windows中经常出现:)。同时，也不要忘记你的地址是32位的，这也是保护模式的主要优势之一。</p><p>现在假设存在一个描述符描述从物理的0:0开始的全部内存，并已经加载进DS(数据选择器)，则我们可以通过下面的程序来操作VGA的VRAM：</p><table class="code" id="AutoNumber4" style="BORDER-COLLAPSE: collapse" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="50%">mov edi,0a0000h<br />mov byte ptr [edi],0fh</td><td width="50%"><font color="#008000">; VGA显存的偏移量<br />; 将第一字节改为0fh</font></td></tr></tbody></table><p>很明显，这比实模式下的程序</p><table class="code" id="AutoNumber5" style="BORDER-COLLAPSE: collapse" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="50%">mov ax,0a000h<br />mov ds,ax<br />mov di,0<br />mov [di],0fh</td><td width="50%"><font color="#008000">; AX -&gt; VGA段地址<br />; 将AX值载入DS<br />; DI清零<br />; 修改第一字节</font></td></tr></tbody></table><p>看上去要舒服一些。</p><h3>3.6 堆栈</h3><p>到目前为止，您已经了解了基本的寄存器以及内存的操作知识。事实上，您现在已经可以写出很多的底层数据处理程序了。</p><p>下面我来说说堆栈。堆栈实在不是一个让人陌生的数据结构，它是一个<span class="tip" id="oFILO" title="">先进后出</span>(FILO)<font color="#a9a9a9">（<b>先进后出</b>(FILO)是这样一个概念：<b>最后</b>放进表中的数据在取出时<b>最先</b>出来。<b>先进后出</b>(FILO)和<b>先进先出</b>(FIFO, 和先进后出的规则相反)，以及<b>随机存取</b>是最主要的三种存储器访问方式。对于堆栈而言，最后放入的数据在取出时最先出现。对于子程序调用，特别是递归调用来说，这是一个非常有用的特性。）</font>的线性表，能够帮助你完成很多很好的工作。</p><p></p><p></p>一个铁杆的汇编语言程序员有时会发现系统提供的寄存器不够。很显然，你可以使用普通的内存操作来完成这个工作，就像C/C++中所做的那样。
<p>没错，没错，可是，如果数据段（数据选择器）以及偏移量发生变化怎么办？更进一步，如果希望保存某些在这种操作中可能受到影响的寄存器的时候怎么办？确实，你可以把他们也存到自己的那片内存中，自己实现堆栈。</p><p>太麻烦了……</p><p>既然系统提供了堆栈，并且性能比自己写一份更好，那么为什么不直接加以利用呢？</p><p>系统堆栈不仅仅是一段内存。由于CPU对它实施管理，因此你不需要考虑堆栈指针的修正问题。可以把寄存器内容，甚至一个立即数直接放到堆栈里，并在需要的时候将其取出。同时，系统并不要求取出的数据仍然回到原来的位置。</p><p>除了显式地操作堆栈（使用PUSH和POP指令）之外，很多指令也需要使用堆栈，如INT、CALL、LEAVE、RET、RETF、IRET等等。配对使用上述指令并不会造成什么问题，然而，如果你打算使用LEAVE、RET、RETF、IRET这样的指令实现跳转(比JMP更为麻烦，然而有时，例如在加密软件中，或者需要修改调用者状态时，这是必要的)的话，那么我的建议是，先搞清楚它们做的到底是什么，并且，精确地了解自己要做什么。</p><p>正如前面所说的，有两个显式地操作堆栈的指令：</p><table id="AutoNumber6" style="BORDER-COLLAPSE: collapse" bordercolor="#111111" cellspacing="0" cellpadding="0" width="100%" border="1"><tbody><tr><td align="middle" width="33%" bgcolor="#008000"><font color="#ffffff">助记符</font></td><td align="middle" width="67%" bgcolor="#008000"><p align="center"><font color="#ffffff">功能</font></p></td></tr><tr><td align="middle" width="33%">PUSH</td><td width="67%">将操作数存入堆栈，同时修正堆栈指针</td></tr><tr><td align="middle" width="33%">POP</td><td width="67%">将栈顶内容取出并存到目的操作数中，同时修正堆栈指针</td></tr></tbody></table><p>我们现在来看看堆栈的操作。</p><p>执行之前</p><p align="center"><img height="118" alt="o_5_1.gif" src="http://www.cppblog.com/images/cppblog_com/ispfcn/1308/o_5_1.gif" width="261" border="0" /></p><p>执行代码</p><table class="code" id="AutoNumber7" style="BORDER-COLLAPSE: collapse" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%">mov ax,1234h<br />mov bx,10<br />push ax<br />push bx</td></tr></tbody></table><p>之后，堆栈的状态为</p><p align="center"><img height="90" alt="o_5_2.gif" src="http://www.cppblog.com/images/cppblog_com/ispfcn/1308/o_5_2.gif" width="213" border="0" /></p><p>之后，再执行</p><table class="code" id="AutoNumber8" style="BORDER-COLLAPSE: collapse" cellspacing="0" cellpadding="0" width="100%" bgcolor="#e0e0e0" border="0"><tbody><tr><td width="100%">pop dx<br />pop cx</td></tr></tbody></table><p>堆栈的状态成为</p><p align="center"><img height="118" alt="o_5_3.gif" src="http://www.cppblog.com/images/cppblog_com/ispfcn/1308/o_5_3.gif" width="214" border="0" /></p><p>当然，dx、cx中的内容将分别是000ah和1234h。</p><p>注意，最后这张图中，我没有抹去1234h和000ah，因为POP指令并不从内存中抹去数值。不过尽管如此，我个人仍然非常反对继续使用这两个数（你可以通过修改SP来再次POP它们），然而这很容易导致错误。</p><p>一定要保证堆栈段有足够的空间来执行中断，以及其他一些隐式的堆栈操作。仅仅统计PUSH的数量并据此计算堆栈所需的大小很可能造成问题。</p><p>CALL指令将返回地址放到堆栈中。绝大多数C/C++编译器提供了“堆栈检查”这个编译选项，其作用在于保证C程序段中没有忘记对堆栈中多余的数据进行清理，从而保证返回地址有效。</p><h3>本章小结</h3><p>本章中介绍了内存的操作的一些入门知识。限于篇幅，我不打算展开细讲指令，如cmps*，lods*，stos*，等等。这些指令的用法和前面介绍的movs*基本一样，只是有不同的作用而已。</p><img src ="http://www.cppblog.com/ispfcn/aggbug/14718.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/ispfcn/" target="_blank">编程之道</a> 2006-11-06 10:33 <a href="http://www.cppblog.com/ispfcn/archive/2006/11/06/14718.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>