青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

不會(huì)飛的鳥(niǎo)

2010年12月10日 ... 不鳥(niǎo)他們!!! 我要用自己開(kāi)發(fā)的分布式文件系統(tǒng)、分布式調(diào)度系統(tǒng)、分布式檢索系統(tǒng), 做自己的搜索引擎!!!大魚(yú)有大志!!! ---楊書(shū)童

#

游戲引擎基礎(chǔ)(七)(網(wǎng)絡(luò)和連線游戲環(huán)境)

7部份: 網(wǎng)絡(luò)和連線游戲環(huán)境


網(wǎng)絡(luò)游戲
  我記得一些年前坐在GDC(游戲開(kāi)發(fā)者大會(huì))聽(tīng)負(fù)責(zé)開(kāi)發(fā)X-Wing Vs TIE Fighter的家伙們題為淹沒(méi)在Internet” 的演講,全是關(guān)于讓網(wǎng)絡(luò)游戲?qū)崟r(shí)地在Internet上工作的東西。他們選擇那個(gè)題目是多么的正確啊。當(dāng)它開(kāi)始處理數(shù)據(jù)包的丟失,亂序,潛伏(一個(gè)數(shù)據(jù)包發(fā)送到它的目的地所花的時(shí)間)等等時(shí),它確實(shí)淹沒(méi)了。然而它是可能的。對(duì)于Internet需要一些聰明和經(jīng)驗(yàn),但它是肯定可能的。看看今天大量的連線游戲,從Quake IIIUnreal TournamentCounter Strike一直到EverQuestUltima Online

  如今大多數(shù)真正有長(zhǎng)久生命力的游戲都至少有一些連線成分。最純粹的單人游戲容易玩一次,也許兩次,或者甚至三次如果它是非常好的游戲,但一旦游戲結(jié)束,就被束之高閣了。如果你想要有任何長(zhǎng)久生命力,那么多人連線游戲就是形勢(shì)的核心所在,并且那意味著和Internet打交道,為編碼者打開(kāi)了那個(gè)潘多拉的盒子。

  那么跟Internet打交道包括些什么呢?首先是要理解Internet是怎么工作的,和點(diǎn)對(duì)點(diǎn)與客戶(hù)機(jī)/服務(wù)器體系結(jié)構(gòu)的快速討論。點(diǎn)對(duì)點(diǎn)就是你在兩臺(tái)機(jī)器上運(yùn)行游戲,并簡(jiǎn)單地在它們之間共享輸入。每個(gè)單獨(dú)的游戲假定它是正確的,并僅僅在它一幀接一幀的刷新中合并來(lái)自另外一臺(tái)機(jī)器的輸入。客戶(hù)機(jī)/服務(wù)器是一臺(tái)機(jī)器有效地運(yùn)行游戲,別的機(jī)器僅僅是一個(gè)終端,接受來(lái)自玩家的輸入,并渲染服務(wù)器讓它渲染的任何東西。

  客戶(hù)機(jī)/服務(wù)器的優(yōu)點(diǎn)是每臺(tái)機(jī)器都將會(huì)展現(xiàn)相同的游戲,因?yàn)樗械奶幚矶荚谝粋€(gè)地方完成,沒(méi)有跨越多臺(tái)機(jī)器,你可以不用考慮每臺(tái)機(jī)器相互之間的同步問(wèn)題。不足之處是,服務(wù)器本身需要有一些重要的CPU可用時(shí)間來(lái)處理每一個(gè)連接的客戶(hù)機(jī),和一個(gè)合適的網(wǎng)絡(luò)連接來(lái)確保每一個(gè)客戶(hù)機(jī)及時(shí)地接收到它的更新。


了解IP
  我們都已經(jīng)聽(tīng)說(shuō)過(guò)TCP/IP(傳輸控制協(xié)議/網(wǎng)間協(xié)議)和UDP(用戶(hù)數(shù)據(jù)包協(xié)議), Web網(wǎng)絡(luò)上有大量關(guān)于這些協(xié)議的深?yuàn)W的技術(shù)資訊。實(shí)際上,在Cisco網(wǎng)站上有一些極好的TCP/IP指導(dǎo)。我們將在較高層面上介紹一些TCP/IP的基本知識(shí),目的是讓你更好地了解使用這些標(biāo)準(zhǔn)協(xié)議的網(wǎng)絡(luò)游戲設(shè)計(jì)者面臨的挑戰(zhàn)。

  TCP/IPUDP/IP是兩層的通信協(xié)議系統(tǒng)。IP層負(fù)責(zé)網(wǎng)際數(shù)據(jù)包的傳輸。UDP或者TCP層將大的數(shù)據(jù)包傳給IPIP將數(shù)據(jù)包分割為小的子數(shù)據(jù)包,為每個(gè)數(shù)據(jù)包加上一個(gè)信封,計(jì)算出目的地的IP地址,應(yīng)該如何到達(dá)那里,然后將數(shù)據(jù)包發(fā)送到你的ISP,或者不管怎樣你連接到網(wǎng)絡(luò)。 這實(shí)在象是在一張明信片上寫(xiě)下你要發(fā)送的,貼上郵票,寫(xiě)上地址,塞進(jìn)一個(gè)郵箱,它就送走了。

  UDPTCP是從你編碼者或者游戲接收數(shù)據(jù)包的高層協(xié)議,并決定該如何處理這些數(shù)據(jù)包。UDPTCP的區(qū)別在于TCP保證數(shù)據(jù)包的傳送和有序,而UDP不保證。UDP是一條直接和IP對(duì)話的小路,而TCP是在你和IP之間的一個(gè)接口。它像是在你和你的郵件之間有一個(gè)管理員助手。使用UDP你會(huì)自己為你的信打字,把它們放進(jìn)一個(gè)信封等等。使用TCP你會(huì)僅僅向你的管理員口授信稿,管理員會(huì)做全部的工作并追蹤確認(rèn)信件送到了。

  然而,所有這些令人驚奇的為你完成的工作伴隨著代價(jià)。為了確定數(shù)據(jù)包通過(guò)Internet完好無(wú)損地送到了目的方,TCP期待從目的方為它發(fā)送的每個(gè)數(shù)據(jù)包發(fā)回一個(gè)應(yīng)答包(網(wǎng)絡(luò)用語(yǔ)是ACK)。如果它在一定時(shí)間內(nèi)沒(méi)有收到ACK,它就停止發(fā)送任何新的數(shù)據(jù)包,重新發(fā)送丟失的數(shù)據(jù)包,并且將繼續(xù)這樣做直到收到目的方的回應(yīng)。當(dāng)你訪問(wèn)一個(gè)網(wǎng)頁(yè)時(shí),我們都已經(jīng)看到了這種情形,在半途中下載停止了一會(huì)然后又重新開(kāi)始了。可能是一個(gè)數(shù)據(jù)包在什么地方丟失了(假定不時(shí)ISP的問(wèn)題),在任何更多的數(shù)據(jù)包被發(fā)送以前TCP要求重新發(fā)送它。

  這一切的問(wèn)題是,在認(rèn)識(shí)到出了差錯(cuò)的發(fā)送者和實(shí)際上正在送達(dá)的數(shù)據(jù)包之間出現(xiàn)了延遲。有時(shí)這能花上數(shù)秒鐘,如果你僅僅只是下載一個(gè)文件或一個(gè)網(wǎng)頁(yè),這不是什么大礙,但如果這是一個(gè)游戲數(shù)據(jù)包而且每秒至少有十次,那么你真的是遇到麻煩了,尤其是因?yàn)樗V沽似渌磺惺虑椤?shí)際上就是這個(gè)問(wèn)題所以幾乎沒(méi)有游戲選擇使用TCP作為它們主要的Internet協(xié)議,除非它不是一個(gè)實(shí)時(shí)動(dòng)作游戲。大多數(shù)游戲使用 UDP--他們不能保證有序或可靠送達(dá),但它確實(shí)很快或者結(jié)果是至少通常比TCP/IP更快。現(xiàn)在我們了解這些了,接下來(lái)呢?


客戶(hù)端預(yù)測(cè)
  因?yàn)?/span> UDP 明顯的是快速響應(yīng)游戲的方式,我們將必須自己處理數(shù)據(jù)包的丟失和亂序。邊而且這是技巧所在。不用說(shuō)出太多的代碼秘密,我就能說(shuō)有方法。作為開(kāi)始,有客戶(hù)端預(yù)言,一個(gè)被談?wù)摰孟喈?dāng)多的詞語(yǔ)。當(dāng)你作為一個(gè)客戶(hù)端連接到一個(gè)大的服務(wù)器,但是不能連貫地看見(jiàn)來(lái)自服務(wù)器的更新,客戶(hù)端預(yù)言開(kāi)始起作用了。正在你的電腦上運(yùn)行的游戲部分看著你正給它的輸入,并在缺乏來(lái)自服務(wù)器的任何棄絕信息的情況下,對(duì)它認(rèn)為將繼續(xù)進(jìn)行的事情作出最好的猜測(cè)。它將會(huì)顯示被猜測(cè)的數(shù)據(jù),然后當(dāng)它得到來(lái)自服務(wù)器的世界的最新?tīng)顟B(tài)時(shí),改正它自己,如果需要。你可能會(huì)對(duì)這個(gè)方法的效力感到驚訝。大體而言,大部分時(shí)間數(shù)據(jù)包不容易丟失大多數(shù)時(shí)候是一秒的幾十分之一,這種情況下游戲沒(méi)有太多的時(shí)間偏離服務(wù)器實(shí)際上認(rèn)為正在發(fā)生的事情。偏離確實(shí)會(huì)隨著時(shí)間變的比較大,大多數(shù)游戲里面有一個(gè)超時(shí)功能,當(dāng)出現(xiàn)很長(zhǎng)時(shí)間沒(méi)有來(lái)自服務(wù)器的聯(lián)絡(luò)時(shí)就停止游戲。

  你正在創(chuàng)造的游戲類(lèi)型在這里有關(guān)系 -- 第一人稱(chēng)射擊游戲不需要這樣有效的客戶(hù)端預(yù)言,因?yàn)樗鄶?shù)情況下僅僅處理我在哪兒,我是否要射擊?。在第三人稱(chēng)游戲中,你必須更加精確,因此你能夠正確地預(yù)測(cè)你的角色正在播放的動(dòng)畫(huà),并且動(dòng)作流暢。在這種情形中流暢的動(dòng)畫(huà)是完全必要的。Heretic II在這方面有很大的問(wèn)題,并且是當(dāng)它開(kāi)始網(wǎng)絡(luò)編碼時(shí)Raven一直不得不處理的最困難的事情之一。

  當(dāng)然如果你有一個(gè)很不錯(cuò)的網(wǎng)絡(luò)連接,比如寬帶連接,那么這個(gè)問(wèn)題就遠(yuǎn)沒(méi)有那么重要。對(duì)比較大的數(shù)據(jù)包有一個(gè)更寬的管道,對(duì)你的網(wǎng)絡(luò)連通時(shí)間更快速。事實(shí)上,寬帶對(duì)于游戲的主要優(yōu)點(diǎn)不比較胖的管道多,但大大減少了延遲,特別是你到ISP的第一跳上。對(duì)于56K 調(diào)制解調(diào)器,第一跳典型的延遲是100ms,這已經(jīng)嚴(yán)重地增加了你到網(wǎng)絡(luò)上任意一臺(tái)游戲服務(wù)器的潛在連通時(shí)間。對(duì)于寬帶連接比如像DSL,第一跳的延遲時(shí)間多半是20ms。使用Windows中一個(gè)叫做TraceRouteTRACERT.EXE)的命令行程序并指定一個(gè)目標(biāo)IP地址或者域名,你能夠找出你的第一跳的連通時(shí)間。仔細(xì)觀察第一跳,因?yàn)檫@幾乎總是你到你的ISP的網(wǎng)絡(luò)連通時(shí)間。并且觀察你在你的ISP的網(wǎng)絡(luò)內(nèi)部用了多少跳直到你看見(jiàn)在一個(gè)給定跳上列出的一個(gè)不同的域名。

  請(qǐng)注意,寬帶并不總是能解決延遲問(wèn)題。你仍然受最慢的路由器/服務(wù)器和數(shù)據(jù)包從服務(wù)器穿越網(wǎng)絡(luò)到達(dá)你的跳數(shù)(反之亦然)的支配。有一個(gè)寬帶連接確實(shí)容易緩和這些,但不可能它們最后就消失了。當(dāng)然,如果你打算要運(yùn)行某種服務(wù)器,你將會(huì)需要一個(gè)具有足夠快速的向上游的數(shù)據(jù)速率的帶寬,因?yàn)閮H僅一個(gè)調(diào)制解調(diào)器不能夠處理一個(gè)服務(wù)器產(chǎn)生的負(fù)荷。

  值得一提的是,如果你想要在PS2或者Xbox上面玩網(wǎng)絡(luò)游戲,你將需要一個(gè)寬帶連接,因?yàn)樗鼈儍烧叨疾恢С终{(diào)制解調(diào)器。


包大小,智能數(shù)據(jù)傳輸,和反作弊
  別的必須被處理的事情是數(shù)據(jù)包的大小。如果你在一個(gè)游戲里面64個(gè)人都在跑來(lái)跑去相互攻擊,從一臺(tái)機(jī)器發(fā)送到另外一臺(tái)機(jī)器的數(shù)據(jù)包能變得相當(dāng)大,達(dá)到了一些調(diào)制解調(diào)器沒(méi)有帶寬處理這些數(shù)據(jù)的程度。這正在變得特別和那些有著很大的地表系統(tǒng)的游戲有關(guān)。這里增加的問(wèn)題是,因?yàn)槟阌羞@個(gè)很好的地表系統(tǒng),你能夠看得很遠(yuǎn),因此能夠看見(jiàn)許多其他游戲玩家,使得你為了精確渲染所需要的來(lái)自服務(wù)器的數(shù)據(jù)數(shù)量以很快的速率增長(zhǎng)。我們能做什么呢?

  好吧,首先必要的是只發(fā)送絕對(duì)必須的東西給任何給定的客戶(hù)端,因此他僅僅得到從他的角度觀察游戲所需要的東西。發(fā)送在他視野以外的人們的數(shù)據(jù)沒(méi)有一點(diǎn)意義他將看不見(jiàn)這些。同時(shí),你最好確保只發(fā)送那些每幀之間實(shí)際上發(fā)生改變的數(shù)據(jù)。如果一個(gè)家伙仍然在播放相同的動(dòng)畫(huà),重新發(fā)送數(shù)據(jù)沒(méi)有意義。當(dāng)然,如果數(shù)據(jù)包丟失時(shí)這確實(shí)帶來(lái)一些問(wèn)題,但這就是為什么好的網(wǎng)絡(luò)程序員被支付很多金錢(qián),來(lái)處理類(lèi)似這樣的東西。

  還有一些其他的事情也要處理。最近已經(jīng)有大量的令人苦惱的連線作弊正在發(fā)生。這是某些人修改游戲以給他們不正當(dāng)利益的地方。盡管?chē)?yán)格意義上這不是網(wǎng)絡(luò)的一部分,但它確實(shí)發(fā)生了。有時(shí)人們會(huì)創(chuàng)作一些模塊,允許他們立即瞄準(zhǔn)進(jìn)入視野的任何人,或者簡(jiǎn)單地允許他們看穿墻壁,或者讓其他游戲玩家看不見(jiàn)他們自己。大部份時(shí)間這些事情可以在網(wǎng)絡(luò)層內(nèi)部或者在服務(wù)器上被處理。任何有100%命中率的人被簡(jiǎn)單地踢出游戲,因?yàn)樵谌肆λ暗姆秶鷥?nèi)那是不可能的。

  游戲開(kāi)發(fā)者必須盡一切可能制止作弊行為,但很不幸,人做的東西可以被人突破。所有你能做的就是讓作弊變得困難,當(dāng)確實(shí)發(fā)生時(shí)去嘗試發(fā)現(xiàn)它。

  好吧,現(xiàn)在就到這里了。在第8部分中,我們將會(huì)看看游戲腳本系統(tǒng)的趣味世界,根據(jù)游戲過(guò)程中出現(xiàn)的事件來(lái)渲染或使能預(yù)先定義的場(chǎng)景和行為,協(xié)助故事敘述。



夢(mèng)在天涯 2007-12-04 13:24 發(fā)表評(píng)論

posted @ 2009-04-10 10:44 不會(huì)飛的鳥(niǎo) 閱讀(139) | 評(píng)論 (0)編輯 收藏

游戲引擎基礎(chǔ)(八)(腳本系統(tǒng))

     摘要: 第8部份: 腳本系統(tǒng)腳本系統(tǒng)  我們從第七部分的游戲網(wǎng)絡(luò)問(wèn)題來(lái)到了腳本系統(tǒng),因?yàn)槠涑尸F(xiàn)的故事敘述機(jī)會(huì),最近已經(jīng)形成一種很大的游戲元素。在一個(gè)需要以受控制的方式解釋的情景,預(yù)先編制的電影腳本是解決問(wèn)題的方法。在電影中,這通常用來(lái)處理或者由主角向一個(gè)伙伴解釋情形,或者敵人對(duì)英雄解釋。當(dāng)然,有其它的方法來(lái)做這件事情 -- 敘事者,倒敘,等等 – 但通常是使用實(shí)時(shí)情景的人們和事件來(lái)完成。當(dāng)然,...  閱讀全文

夢(mèng)在天涯 2007-12-04 13:27 發(fā)表評(píng)論

posted @ 2009-04-10 10:44 不會(huì)飛的鳥(niǎo) 閱讀(151) | 評(píng)論 (0)編輯 收藏

游戲引擎基礎(chǔ)(九)(現(xiàn)成產(chǎn)品與定做的游戲引擎設(shè)計(jì)工具,游戲特定主題)

     摘要: 第9部分: 現(xiàn)成產(chǎn)品與定做的游戲引擎設(shè)計(jì)工具,游戲特定主題現(xiàn)成產(chǎn)品與定做的設(shè)計(jì)工具  我們從第8部份的腳本引擎來(lái)到這一章節(jié)中的許多主題,我們認(rèn)為那些鐵桿游戲玩家和有志成為游戲開(kāi)發(fā)者的那些人將會(huì)發(fā)現(xiàn)它們相當(dāng)有趣。我們將開(kāi)始討論現(xiàn)成產(chǎn)品與定制的設(shè)計(jì)工具。   你的工具的選擇是你引擎設(shè)計(jì)的一個(gè)非常重要的部份,因?yàn)檫@是你將用來(lái)給你的游戲產(chǎn)生內(nèi)容的東西,是最耗時(shí)的部份。在這個(gè)過(guò)程中有助于節(jié)省時(shí)間和資源的任何...  閱讀全文

夢(mèng)在天涯 2007-12-04 13:28 發(fā)表評(píng)論

posted @ 2009-04-10 10:44 不會(huì)飛的鳥(niǎo) 閱讀(102) | 評(píng)論 (0)編輯 收藏

游戲引擎基礎(chǔ)(十)(人工智能和導(dǎo)航)

10部分: 人工智能和導(dǎo)航(路徑發(fā)現(xiàn))


人工智能(AI
  我們上面已經(jīng)用了其他九個(gè)章節(jié)介紹了游戲引擎,現(xiàn)在讓我們深入到非常有趣和重要的人工智能主題。人工智能如今正在變成被談?wù)摰米疃嗟膬H次于游戲引擎渲染能力的游戲開(kāi)發(fā)領(lǐng)域之一,確實(shí)如此。直到大約兩年半以前,游戲似乎主要是在考慮你能夠渲染多少個(gè)多邊形,眼睛是多么的漂亮,和勞拉的胸部是多么的有彈性...既然我們現(xiàn)在已經(jīng)能夠渲染出非常真實(shí)的乳房,中心就開(kāi)始轉(zhuǎn)移到我們實(shí)際上用那些多邊形做什么了(即玩游戲)。因?yàn)樗o你提供實(shí)際玩游戲的刺激作用和參與游戲世界中正在進(jìn)行的事情,所以人工智能在這個(gè)領(lǐng)域非常關(guān)鍵。

  人工智能包括了全部的東西,從在Tetris中決定哪一塊新磚頭掉落(這很大程度上知識(shí)一個(gè)隨即數(shù)產(chǎn)生器), 一直到創(chuàng)造基于小組的策略游戲,這些游戲和你交互,并且實(shí)際上在你玩的時(shí)候向你學(xué)習(xí)。人工智能包含了許多規(guī)則,如果你(作為一個(gè)游戲開(kāi)發(fā)者)沒(méi)有花費(fèi)足夠多的時(shí)間讓它正確地工作,它會(huì)反過(guò)來(lái)在你屁股上咬一口。所以讓我們談?wù)撘恍┠男┮?guī)則?這樣你能更好地理解人工智能系統(tǒng)會(huì)確實(shí)是多么的復(fù)雜。為了避免法律上的糾紛,我們將使用一個(gè)假設(shè)的游戲而不是一個(gè)真實(shí)的游戲作為例子。

  假設(shè)我們的游戲中有壞份子生活在3D世界中,干著他們的事情,而且如果你打攪了他們的正常次序他們就會(huì)反抗你(玩家)。你必須決定的第一件事情就是他們正在從事的到底是什么事情呢?他們正在守衛(wèi)什么東西嗎?在巡查?在計(jì)劃一個(gè)聚會(huì)?在購(gòu)買(mǎi)食品雜貨?在整理床鋪?建立行為的基線是游戲開(kāi)發(fā)者的工作之一。一旦有了這個(gè),你就總有NPC(非玩家角色)或計(jì)算機(jī)控制的能夠恢復(fù)去做的事情,玩家與他們的交互就應(yīng)當(dāng)能被完成。

  一旦我們知道一個(gè)NPC角色需要做什么比如它在守衛(wèi)一扇門(mén),并且在這個(gè)區(qū)域小巡邏,NPC也必須有世界意識(shí)。游戲設(shè)計(jì)者需要決定NPC的人工智能將如何看見(jiàn)世界,和它的知識(shí)范圍。你將會(huì)僅僅說(shuō)計(jì)算機(jī)知道正在進(jìn)行的每件事情嗎?這通常被認(rèn)為是一件糟糕的事情,因?yàn)榉浅C黠@計(jì)算機(jī)能夠看見(jiàn)和聽(tīng)見(jiàn)你不能看見(jiàn)和聽(tīng)見(jiàn)的事情,這被當(dāng)成是在作弊。不是一種有趣的經(jīng)歷。或者你將模擬他的視野,這樣他只能夠?qū)λ芸匆?jiàn)的事物作出反應(yīng)嗎?當(dāng)有墻壁出現(xiàn)時(shí)這里就有問(wèn)題了,因?yàn)槟汩_(kāi)始進(jìn)入那些我在第九部分提到的追蹤例程,看看NPC是否試圖對(duì)被墻壁擋住的人作出反應(yīng)。這是一個(gè)很明顯的人工智能問(wèn)題,但是當(dāng)涉及到門(mén)和窗戶(hù)時(shí),這個(gè)甚至變得更加復(fù)雜了。

  當(dāng)你開(kāi)始為AI刺激例程增加聽(tīng)覺(jué)意識(shí)時(shí),這依然變得更加復(fù)雜了。但是,這個(gè)意識(shí)是那些關(guān)鍵的小事情之一,這些使得假想的游戲世界似乎更加真實(shí),或者能夠去除懷疑的懸念。如果你碰到過(guò)這樣的事情,請(qǐng)舉手:你在槍?xiě)?zhàn)中跟一個(gè)NPC交戰(zhàn),免除了一個(gè)NPC,你繞著角落行走并遇到了另外一個(gè)NPC依然保持他的缺省行為模式,沒(méi)有意識(shí)到剛剛發(fā)生的事情。現(xiàn)在,槍是嘈雜的事物,槍?xiě)?zhàn)可能已經(jīng)明顯地提醒了一個(gè)傾聽(tīng)NPC有些事情正在進(jìn)行。避免這種事情的技巧在于找到一個(gè)有效的方式來(lái)決定聲源(即你武器的發(fā)射)的距離是否足夠接近到NPC能夠聽(tīng)見(jiàn)。

  接下來(lái)就是決策例程。當(dāng)我們的巡邏NPC角色能夠聽(tīng)到但不能看見(jiàn)某物時(shí),你試圖實(shí)現(xiàn)什么樣的行為呢?他去尋找它嗎?不理睬它?你如何決定什么是重要的聲音他應(yīng)該去或者不去調(diào)查?如同你看見(jiàn)的一樣,這會(huì)很快變得非常的復(fù)雜。有很多方法來(lái)建造處理這些事情的代碼,但通常這樣是一個(gè)好主意,建立一個(gè)不是對(duì)特定的NPC而是對(duì)所有的NPC都起作用的系統(tǒng),該系統(tǒng)基于你能夠在游戲引擎以外的文本文件中建立的屬性。這樣就不需要程序員為一個(gè)給定的角色而改變AI,并且如果你對(duì)游戲代碼做了改動(dòng),它將立即自動(dòng)地應(yīng)用到所有的角色,這在大多數(shù)情況下是一件好事情。

  其他的世界意識(shí)問(wèn)題會(huì)冒出來(lái),比如這樣的情形,兩個(gè)守衛(wèi)彼此緊挨著站立,你用狙擊武器干掉了一個(gè),而另外一個(gè)站在哪兒完全不知已經(jīng)發(fā)生的事情。再者,遵守真實(shí)世界行為的細(xì)節(jié)是一款好游戲和一款偉大游戲的之間的區(qū)別。

  讓我們說(shuō)你已經(jīng)把所有的刺激-響應(yīng)部分準(zhǔn)備好了你已經(jīng)掃描了世界,決定NPC應(yīng)當(dāng)對(duì)正在進(jìn)行的一些事情作出反應(yīng)他聽(tīng)到了玩家角色發(fā)出了聲響并且你(游戲開(kāi)發(fā)者)決定了他應(yīng)當(dāng)對(duì)這個(gè)做些什么他將去調(diào)查。現(xiàn)在更加復(fù)雜的事情來(lái)了。他如何離開(kāi)現(xiàn)在的位置,到達(dá)他認(rèn)為發(fā)出聲音的地方,而不會(huì)想通常的數(shù)字傻瓜一樣跑到墻壁里面,碰到家具呢?繼續(xù)往下看


有關(guān)正確的路徑 --- 世界導(dǎo)航
  快速,準(zhǔn)確的世界導(dǎo)航( 也叫做路徑-發(fā)現(xiàn)) 近來(lái)已經(jīng)成為游戲開(kāi)發(fā)者的圣杯。 讓它看起來(lái)非常信服是一件非常困難的事情。你需要有局部世界的地理知識(shí)墻壁的位置,臺(tái)階,懸崖和建筑物等的邊緣。你也需要世界中的對(duì)象的知識(shí)比如家具,汽車(chē),尤其是其他人的位置。真正最后的因素是問(wèn)題所在,一會(huì)兒我們將回到這一點(diǎn)上。

  世界導(dǎo)航通常被分為兩個(gè)領(lǐng)域,世界導(dǎo)航和局部導(dǎo)航。二者實(shí)際上只是范圍上的區(qū)別,但大多數(shù)的程序員分別對(duì)待它們,因?yàn)檫@樣處理起來(lái)容易一些。世界導(dǎo)航例程處理理解房間,門(mén)和一般的地理學(xué),并計(jì)算出讓玩家或者角色從世界中的A點(diǎn)到達(dá)B點(diǎn)的一條路徑。它將讓你從A點(diǎn)到達(dá)B點(diǎn),這是一句很容易說(shuō)的話,不是嗎?說(shuō)起來(lái)容易,但做起來(lái)很困難。理解世界是一個(gè)非常復(fù)雜問(wèn)題,我已經(jīng)看到過(guò)許多嘗試過(guò)的解決辦法。QuakeIII的機(jī)器人遵照建造的預(yù)先處理過(guò)的地圖,一般的說(shuō)法,使用原來(lái)地圖的地面。預(yù)處理器檢測(cè)地面元素,由地圖建造者作上標(biāo)記,并自己建造一個(gè)只使用地面的世界簡(jiǎn)化地圖。機(jī)器人并不關(guān)心墻壁,因?yàn)樗麄儚牟唤咏鼈儯拖袼麄冏裾盏孛娴牡貓D一樣,設(shè)計(jì)上已經(jīng)把避免墻壁構(gòu)造在里面了。

  其他方法在地圖本身里面建造一些小的結(jié)點(diǎn),AI可以追隨它們。這些結(jié)點(diǎn)通常被建造在彼此的視線里面,有從一個(gè)結(jié)點(diǎn)到其他所有結(jié)點(diǎn)的連接,角色AI能夠直接看見(jiàn),所以你就確保了從一個(gè)結(jié)點(diǎn)移動(dòng)到另外一個(gè)結(jié)點(diǎn)時(shí)AI不會(huì)試圖穿越墻壁。如果有門(mén)或者降落物,你能夠事先用這些結(jié)點(diǎn)對(duì)路徑的信息編碼,于是NPC能夠采用適當(dāng)?shù)男袨?/span>等候電梯,打開(kāi)一扇門(mén),或者從一點(diǎn)跳到另外一點(diǎn)。這實(shí)際上是HereticII使用的系統(tǒng),也是Raven在他們其他的大多數(shù)游戲中使用的系統(tǒng)。

  關(guān)于這個(gè)主題,3D RealmsJess Crable,現(xiàn)在為Duke Nukem Forever工作,如是說(shuō):

  "導(dǎo)航在許多方面是個(gè)巨大的挑戰(zhàn),主要是當(dāng)游戲中有大量正在發(fā)生的事情和一些非計(jì)劃性的東西,比如障礙。為了避免和(或)真實(shí)地對(duì)非計(jì)劃性的障礙物導(dǎo)航(例如像另外的AI),AI需要很好地知道正在它周?chē)l(fā)生的事情。比較而言另外一個(gè)巨大的挑戰(zhàn)就是真實(shí)感。如果AI正在表現(xiàn)玩家在實(shí)際生活中看到的一些東西,比如說(shuō)一個(gè)人,或者一條狗, 那么讓它看上去真實(shí)可信就更加困難。"

  然后就是局部導(dǎo)航。我們可能有一條路徑讓我們的 NPC 從他在世界中的位置,移動(dòng)到他認(rèn)為聽(tīng)到聲音的地方,但你不能盲目地按照這個(gè)執(zhí)行并期望得到看起來(lái)不錯(cuò)的結(jié)果。這種性質(zhì)的路徑傾向于非常特定于一個(gè)給定的目的。當(dāng)你沿著走廊從一個(gè)房間跑到另外一個(gè)房間時(shí),它很好,但如果你試圖指導(dǎo)他穿越一個(gè)巨大的房間時(shí),路徑結(jié)點(diǎn)方法容易最終得到一些看起來(lái)很奇怪的發(fā)現(xiàn)路徑。這些路徑也不是動(dòng)態(tài)的。因?yàn)樗麄儽活A(yù)先建造,他們不容易考慮到世界的任何動(dòng)態(tài)變化。桌子可能有被移動(dòng)過(guò)了,椅子被破壞了,墻壁被摧殘,當(dāng)然,人們會(huì)移動(dòng)。這就是局部導(dǎo)航不同于世界導(dǎo)航的地方。它必須考慮局部世界并導(dǎo)航NPC在里面穿越。它必須知道周?chē)沫h(huán)境,存在哪些可以選擇的路徑,并決定選擇哪一條。

  在局部導(dǎo)航中最大的問(wèn)題是其他的NPC。給定一個(gè)發(fā)現(xiàn)路徑的具體例程,如果你在相同的一般區(qū)域中有不止一個(gè)NPC,他們都試圖到達(dá)世界的同一地點(diǎn),結(jié)果是他們都非常容易有相同的路徑。然后他們?cè)噲D沿著這個(gè)路徑行進(jìn),結(jié)果彼此遇到一起,然后花費(fèi)他們所有的時(shí)間試圖將彼此分開(kāi),并且一旦成功地分開(kāi)了,他們?cè)俅卧噲D到達(dá)目標(biāo),然后我們又再次看到同樣的事情發(fā)生。這一切看起來(lái)都是非常的愚蠢,這不是大多數(shù)人想要的效果。所以需要一些路徑發(fā)現(xiàn)中的變化來(lái)避免這種情形,需要一些妥善處理避免的代碼。有大量能夠幫助解決這種情形的算法。


人工智能和角色動(dòng)畫(huà)問(wèn)題
  當(dāng)然,當(dāng)角色自己在世界中行走時(shí)你必須完全地決定你想要角色播放什么動(dòng)畫(huà)。聽(tīng)起來(lái)無(wú)足輕重?不是的。關(guān)于這個(gè)主題,Raven Chris Reed—Soldier of FortuneII使用名為LICHAI系統(tǒng)的現(xiàn)在的負(fù)責(zé)人如是說(shuō):

  "此刻我能告訴你,我們?cè)谄交苿?dòng)上正有著最大的困難。在一個(gè)多丘陵的長(zhǎng)滿(mǎn)草的叢林中試圖讓五個(gè)角色在彼此附近行走是一個(gè)非常困難的問(wèn)題。讓底層系統(tǒng)完美是重要的,因?yàn)槌墙巧谳^低層次上(避免墻壁,適當(dāng)?shù)膭?dòng)畫(huà))看起來(lái)真實(shí),他們不能夠有效地表達(dá)任何較高層次決定的智能。由于這個(gè)單獨(dú)的原因,動(dòng)畫(huà)和底層的移動(dòng)是最重要的和最難實(shí)現(xiàn)的。它確實(shí)需要完美。"

  因此我們已經(jīng)讓我們的角色從A點(diǎn)到達(dá)了B點(diǎn),他自己穿越世界,在途中避免障礙物,正確播放動(dòng)畫(huà),現(xiàn)在到達(dá)了這里。他看見(jiàn)了你。接下來(lái)做什么呢?很明顯更多的是作出決策。他將向你射擊。太棒了。你回應(yīng)射擊。現(xiàn)在干什么?當(dāng)他試著逃走的時(shí)候,現(xiàn)在你再次經(jīng)歷全部同樣的事情。

  為了讓這些情形看起來(lái)令人信服,你看見(jiàn)了這里必須要處理的大量問(wèn)題。如果你建立你的AI使用沒(méi)有動(dòng)畫(huà)的行為讓NPC執(zhí)行,這能被混合。一些Soldier of Fortune中的AI就是這樣的例子。他們受到了指責(zé),因?yàn)閴募一餂](méi)有以適當(dāng)?shù)姆绞綄?duì)刺激作出反應(yīng)。當(dāng)他們明顯應(yīng)該這樣做的時(shí)候,敵方NPC不掃射,或者不逃跑。部分問(wèn)題是他們沒(méi)有掃射敵人NPC的動(dòng)畫(huà),或者讓他們往回跑,因?yàn)榭臻g的問(wèn)題。因此世界上所有最偉大的AI代碼都不能夠解決這個(gè)問(wèn)題。這是所有要考慮的重要事情。

  想知道隱藏的難點(diǎn)嗎?看看我前面所有的描述,然后試著將它應(yīng)用到一組NPC上,這些NPC彼此必須說(shuō)話,設(shè)定目標(biāo),彼此溝通,但不妨礙彼此的方式。一旦你這么做了,試試那些代碼,作為玩家的隊(duì)友做上面所描述的這些,然而不要在槍?xiě)?zhàn)中妨礙他。現(xiàn)在這是復(fù)雜的。然后這成為樂(lè)趣。這是最困難的部分。Raven Chris Reed關(guān)于AI‘感覺(jué)的一些評(píng)論:

  "我認(rèn)為反饋是AI的一個(gè)極大的問(wèn)題。如果角色對(duì)于他周?chē)h(huán)境的變化不產(chǎn)生反應(yīng),游戲的真實(shí)感就被完全打破了。這有許多明顯的例子(聽(tīng)見(jiàn)槍炮聲,看見(jiàn)同伴被擊中...),以及一些更加微妙的事情(當(dāng)兩個(gè)人通過(guò)門(mén)廳時(shí)看著彼此并點(diǎn)頭致意)。玩家是樂(lè)意接受一些生硬和可預(yù)測(cè)性的,但是這些事物容易把游戲帶到現(xiàn)實(shí)生活。"

  并且Jess Crable 贊同:

  "平衡是非常重要的對(duì)玩家將會(huì)有多大的樂(lè)趣至關(guān)重要,但還有其他的問(wèn)題要平衡。游戲玩家時(shí)常說(shuō)他們想在游戲中看見(jiàn)更加真實(shí)的人工智能。然而,太多的真實(shí)感開(kāi)始把樂(lè)趣帶走。在這兩者之間必須要有一個(gè)好的平衡。變化和隨機(jī)同樣也很重要行為的變化,和保持在可信范圍內(nèi)的一定程度的不可預(yù)測(cè)性。"


游戲規(guī)則與自然發(fā)生的游戲
  在我們關(guān)于AI的所有描述中,我們采用的是FPS的方式。有不止一種的AI。我們已經(jīng)描述的是處理3D世界一組規(guī)則。AI遠(yuǎn)遠(yuǎn)不止這些。時(shí)常最好的AI實(shí)際上非常的簡(jiǎn)單。它就是一組規(guī)則,玩家必須響應(yīng)和處理的響應(yīng)(或開(kāi)始)動(dòng)作的規(guī)則。

  這里應(yīng)當(dāng)處理一個(gè)被稱(chēng)為自然發(fā)生的游戲的專(zhuān)業(yè)術(shù)語(yǔ)。 自然發(fā)生的游戲本質(zhì)上創(chuàng)造游戲?qū)⒆袷氐囊?guī)則,那將會(huì)造成游戲程序員不能預(yù)見(jiàn)的情形。

  舉例來(lái)說(shuō),象棋能被認(rèn)為是自然發(fā)生的游戲。有一組規(guī)則,但游戲能夠陷入各種程序員不能夠以個(gè)別方式處理的情形。你不能為每一種可能的棋局情形編碼規(guī)則。很清楚,游戲玩家每次不會(huì)總是面臨相同的游戲情景。一定程度上,進(jìn)行中的游戲情形會(huì)根據(jù)他的行動(dòng)而發(fā)生變化。Black and White是這種情形的一個(gè)完美的例子,和The Sims一樣游戲有它自己的規(guī)則,但你如何運(yùn)用和調(diào)和他們是你自己的事情。實(shí)際上,你在玩游戲的過(guò)程中創(chuàng)造著游戲,而不是照著游戲設(shè)計(jì)者/程序員已經(jīng)為你定義的路線進(jìn)行。

  有可能把基于規(guī)則的,自然發(fā)生的游戲方式和FPS環(huán)境混合在一起。Half Life中的一些海軍陸戰(zhàn)隊(duì)士兵的行為就是這樣做的壓制火力和側(cè)翼攻擊從設(shè)定的規(guī)則中動(dòng)態(tài)完成。它看起來(lái)是動(dòng)態(tài)的,而且一定程度上它是這樣。然而,在FPS世界中僅僅有一組規(guī)則時(shí)常是不夠的。幾何和其他AI時(shí)常能夠打敗簡(jiǎn)單的規(guī)則,這讓保持正確并依然有趣變得更加困難。所以對(duì)那些可憐的AI程序員有一些同情心吧。他們的工作不容易。


  好吧,下面還有一個(gè)章節(jié),僅僅還剩下一個(gè)章節(jié)了。在最后的章節(jié)里,我們將討論頭頂顯示,菜單系統(tǒng),游戲定制和配置,游戲引擎版權(quán)與建造,最后是游戲“mods”



夢(mèng)在天涯 2007-12-04 13:29 發(fā)表評(píng)論

posted @ 2009-04-10 10:44 不會(huì)飛的鳥(niǎo) 閱讀(204) | 評(píng)論 (0)編輯 收藏

游戲引擎基礎(chǔ)(十一)(最后的章節(jié))

     摘要: 第11部份: 最后的章節(jié)前端  你已經(jīng)看到了菜單系統(tǒng),你可能理解游戲內(nèi)的頭頂顯示(HUDs)時(shí)常是游戲經(jīng)歷中被忽視和誹謗的部分。最近,這個(gè)領(lǐng)域開(kāi)始被給人印象非常深刻的Black and White所關(guān)注,這款游戲?qū)嶋H上沒(méi)有HUD。在Peter Molyneux經(jīng)歷了Dungeon Keeper以后,它在屏幕上大量的圖標(biāo),他決定游戲的大部分被這些圖標(biāo)占用了,主要的屏幕沒(méi)有被足夠利用。因此他決定廢除所...  閱讀全文

夢(mèng)在天涯 2007-12-04 13:31 發(fā)表評(píng)論

posted @ 2009-04-10 10:44 不會(huì)飛的鳥(niǎo) 閱讀(81) | 評(píng)論 (0)編輯 收藏

[轉(zhuǎn)載]堆棧,堆棧,堆和棧的區(qū)別

堆和棧的區(qū)別
一、預(yù)備知識(shí)—程序的內(nèi)存分配
一個(gè)由c/C++編譯的程序占用的內(nèi)存分為以下幾個(gè)部分
1、棧區(qū)(stack)— 由編譯器自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類(lèi)似于數(shù)據(jù)結(jié)構(gòu)中的棧。
2、堆區(qū)(heap) — 一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類(lèi)似于鏈表,呵呵。
3、全局區(qū)(靜態(tài)區(qū))(static)—,全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。 - 程序結(jié)束后有系統(tǒng)釋放 
4、文字常量區(qū)—常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放
5、程序代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。
二、例子程序 
這是一個(gè)前輩寫(xiě)的,非常詳細(xì) 
//main.cpp 
int a = 0; 全局初始化區(qū) 
char *p1; 全局未初始化區(qū) 
main() 

int b; 棧 
char s[] = "abc"; 棧 
char *p2; 棧 
char *p3 = "123456"; 123456\0在常量區(qū),p3在棧上。 
static int c =0; 全局(靜態(tài))初始化區(qū) 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 
分配得來(lái)得10和20字節(jié)的區(qū)域就在堆區(qū)。 
strcpy(p1, "123456"); 123456\0放在常量區(qū),編譯器可能會(huì)將它與p3所指向的"123456"優(yōu)化成一個(gè)地方。 

 


二、堆和棧的理論知識(shí) 
2.1申請(qǐng)方式 
stack: 
由系統(tǒng)自動(dòng)分配。 例如,聲明在函數(shù)中一個(gè)局部變量 int b; 系統(tǒng)自動(dòng)在棧中為b開(kāi)辟空間 
heap: 
需要程序員自己申請(qǐng),并指明大小,在c中malloc函數(shù) 
如p1 = (char *)malloc(10); 
在C++中用new運(yùn)算符 
如p2 = (char *)malloc(10); 
但是注意p1、p2本身是在棧中的。 


2.2 
申請(qǐng)后系統(tǒng)的響應(yīng) 
棧:只要棧的剩余空間大于所申請(qǐng)空間,系統(tǒng)將為程序提供內(nèi)存,否則將報(bào)異常提示棧溢出。 
堆:首先應(yīng)該知道操作系統(tǒng)有一個(gè)記錄空閑內(nèi)存地址的鏈表,當(dāng)系統(tǒng)收到程序的申請(qǐng)時(shí), 
會(huì)遍歷該鏈表,尋找第一個(gè)空間大于所申請(qǐng)空間的堆結(jié)點(diǎn),然后將該結(jié)點(diǎn)從空閑結(jié)點(diǎn)鏈表中刪除,并將該結(jié)點(diǎn)的空間分配給程序,另外,對(duì)于大多數(shù)系統(tǒng),會(huì)在這塊內(nèi)存空間中的首地址處記錄本次分配的大小,這樣,代碼中的delete語(yǔ)句才能正確的釋放本內(nèi)存空間。另外,由于找到的堆結(jié)點(diǎn)的大小不一定正好等于申請(qǐng)的大小,系統(tǒng)會(huì)自動(dòng)的將多余的那部分重新放入空閑鏈表中。 

2.3申請(qǐng)大小的限制 
棧:在Windows下,棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是一塊連續(xù)的內(nèi)存的區(qū)域。這句話的意思是棧頂?shù)牡刂泛蜅5淖畲笕萘渴窍到y(tǒng)預(yù)先規(guī)定好的,在WINDOWS下,棧的大小是2M(也有的說(shuō)是1M,總之是一個(gè)編譯時(shí)就確定的常數(shù)),如果申請(qǐng)的空間超過(guò)棧的剩余空間時(shí),將提示overflow。因此,能從棧獲得的空間較小。 
堆:堆是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域。這是由于系統(tǒng)是用鏈表來(lái)存儲(chǔ)的空閑內(nèi)存地址的,自然是不連續(xù)的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計(jì)算機(jī)系統(tǒng)中有效的虛擬內(nèi)存。由此可見(jiàn),堆獲得的空間比較靈活,也比較大。 


2.4申請(qǐng)效率的比較: 
棧由系統(tǒng)自動(dòng)分配,速度較快。但程序員是無(wú)法控制的。 
堆是由new分配的內(nèi)存,一般速度比較慢,而且容易產(chǎn)生內(nèi)存碎片,不過(guò)用起來(lái)最方便. 
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內(nèi)存,他不是在堆,也不是在棧是直接在進(jìn)程的地址空間中保留一快內(nèi)存,雖然用起來(lái)最不方便。但是速度快,也最靈活。 

2.5堆和棧中的存儲(chǔ)內(nèi)容 
棧: 在函數(shù)調(diào)用時(shí),第一個(gè)進(jìn)棧的是主函數(shù)中后的下一條指令(函數(shù)調(diào)用語(yǔ)句的下一條可執(zhí)行語(yǔ)句)的地址,然后是函數(shù)的各個(gè)參數(shù),在大多數(shù)的C編譯器中,參數(shù)是由右往左入棧的,然后是函數(shù)中的局部變量。注意靜態(tài)變量是不入棧的。 
當(dāng)本次函數(shù)調(diào)用結(jié)束后,局部變量先出棧,然后是參數(shù),最后棧頂指針指向最開(kāi)始存的地址,也就是主函數(shù)中的下一條指令,程序由該點(diǎn)繼續(xù)運(yùn)行。 
堆:一般是在堆的頭部用一個(gè)字節(jié)存放堆的大小。堆中的具體內(nèi)容有程序員安排。 

2.6存取效率的比較 

char s1[] = "aaaaaaaaaaaaaaa"; 
char *s2 = "bbbbbbbbbbbbbbbbb"; 
aaaaaaaaaaa是在運(yùn)行時(shí)刻賦值的; 
而bbbbbbbbbbb是在編譯時(shí)就確定的; 
但是,在以后的存取中,在棧上的數(shù)組比指針?biāo)赶虻淖址?例如堆)快。 
比如: 
#include 
void main() 

char a = 1; 
char c[] = "1234567890"; 
char *p ="1234567890"; 
a = c[1]; 
a = p[1]; 
return; 

對(duì)應(yīng)的匯編代碼 
10: a = c[1]; 
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh] 
0040106A 88 4D FC mov byte ptr [ebp-4],cl 
11: a = p[1]; 
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h] 
00401070 8A 42 01 mov al,byte ptr [edx+1] 
00401073 88 45 FC mov byte ptr [ebp-4],al 
第一種在讀取時(shí)直接就把字符串中的元素讀到寄存器cl中,而第二種則要先把指針值讀到edx中,在根據(jù)edx讀取字符,顯然慢了。 


2.7小結(jié): 
堆和棧的區(qū)別可以用如下的比喻來(lái)看出: 
使用棧就象我們?nèi)ワ堭^里吃飯,只管點(diǎn)菜(發(fā)出申請(qǐng))、付錢(qián)、和吃(使用),吃飽了就走,不必理會(huì)切菜、洗菜等準(zhǔn)備工作和洗碗、刷鍋等掃尾工作,他的好處是快捷,但是自由度小。 
使用堆就象是自己動(dòng)手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由度大。 



windows進(jìn)程中的內(nèi)存結(jié)構(gòu)


在閱讀本文之前,如果你連堆棧是什么多不知道的話,請(qǐng)先閱讀文章后面的基礎(chǔ)知識(shí)。 

接觸過(guò)編程的人都知道,高級(jí)語(yǔ)言都能通過(guò)變量名來(lái)訪問(wèn)內(nèi)存中的數(shù)據(jù)。那么這些變量在內(nèi)存中是如何存放的呢?程序又是如何使用這些變量的呢?下面就會(huì)對(duì)此進(jìn)行深入的討論。下文中的C語(yǔ)言代碼如沒(méi)有特別聲明,默認(rèn)都使用VC編譯的release版。 

首先,來(lái)了解一下 C 語(yǔ)言的變量是如何在內(nèi)存分部的。C 語(yǔ)言有全局變量(Global)、本地變量(Local),靜態(tài)變量(Static)、寄存器變量(Regeister)。每種變量都有不同的分配方式。先來(lái)看下面這段代碼: 

#include <stdio.h> 

int g1=0, g2=0, g3=0; 

int main() 

static int s1=0, s2=0, s3=0; 
int v1=0, v2=0, v3=0; 

//打印出各個(gè)變量的內(nèi)存地址 

printf("0x%08x\n",&v1); //打印各本地變量的內(nèi)存地址 
printf("0x%08x\n",&v2); 
printf("0x%08x\n\n",&v3); 
printf("0x%08x\n",&g1); //打印各全局變量的內(nèi)存地址 
printf("0x%08x\n",&g2); 
printf("0x%08x\n\n",&g3); 
printf("0x%08x\n",&s1); //打印各靜態(tài)變量的內(nèi)存地址 
printf("0x%08x\n",&s2); 
printf("0x%08x\n\n",&s3); 
return 0; 

編譯后的執(zhí)行結(jié)果是: 

0x0012ff78 
0x0012ff7c 
0x0012ff80 

0x004068d0 
0x004068d4 
0x004068d8 

0x004068dc 
0x004068e0 
0x004068e4 

輸出的結(jié)果就是變量的內(nèi)存地址。其中v1,v2,v3是本地變量,g1,g2,g3是全局變量,s1,s2,s3是靜態(tài)變量。你可以看到這些變量在內(nèi)存是連續(xù)分布的,但是本地變量和全局變量分配的內(nèi)存地址差了十萬(wàn)八千里,而全局變量和靜態(tài)變量分配的內(nèi)存是連續(xù)的。這是因?yàn)楸镜刈兞亢腿?靜態(tài)變量是分配在不同類(lèi)型的內(nèi)存區(qū)域中的結(jié)果。對(duì)于一個(gè)進(jìn)程的內(nèi)存空間而言,可以在邏輯上分成3個(gè)部份:代碼區(qū),靜態(tài)數(shù)據(jù)區(qū)和動(dòng)態(tài)數(shù)據(jù)區(qū)。動(dòng)態(tài)數(shù)據(jù)區(qū)一般就是“堆棧”。“棧(stack)”和“堆(heap)”是兩種不同的動(dòng)態(tài)數(shù)據(jù)區(qū),棧是一種線性結(jié)構(gòu),堆是一種鏈?zhǔn)浇Y(jié)構(gòu)。進(jìn)程的每個(gè)線程都有私有的“棧”,所以每個(gè)線程雖然代碼一樣,但本地變量的數(shù)據(jù)都是互不干擾。一個(gè)堆棧可以通過(guò)“基地址”和“棧頂”地址來(lái)描述。全局變量和靜態(tài)變量分配在靜態(tài)數(shù)據(jù)區(qū),本地變量分配在動(dòng)態(tài)數(shù)據(jù)區(qū),即堆棧中。程序通過(guò)堆棧的基地址和偏移量來(lái)訪問(wèn)本地變量。 


├———————┤低端內(nèi)存區(qū)域 
│ …… │ 
├———————┤ 
│ 動(dòng)態(tài)數(shù)據(jù)區(qū) │ 
├———————┤ 
│ …… │ 
├———————┤ 
│ 代碼區(qū) │ 
├———————┤ 
│ 靜態(tài)數(shù)據(jù)區(qū) │ 
├———————┤ 
│ …… │ 
├———————┤高端內(nèi)存區(qū)域 


堆棧是一個(gè)先進(jìn)后出的數(shù)據(jù)結(jié)構(gòu),棧頂?shù)刂房偸切∮诘扔跅5幕刂贰N覀兛梢韵攘私庖幌潞瘮?shù)調(diào)用的過(guò)程,以便對(duì)堆棧在程序中的作用有更深入的了解。不同的語(yǔ)言有不同的函數(shù)調(diào)用規(guī)定,這些因素有參數(shù)的壓入規(guī)則和堆棧的平衡。windows API的調(diào)用規(guī)則和ANSI C的函數(shù)調(diào)用規(guī)則是不一樣的,前者由被調(diào)函數(shù)調(diào)整堆棧,后者由調(diào)用者調(diào)整堆棧。兩者通過(guò)“__stdcall”和“__cdecl”前綴區(qū)分。先看下面這段代碼: 

#include <stdio.h> 

void __stdcall func(int param1,int param2,int param3) 

int var1=param1; 
int var2=param2; 
int var3=param3; 
printf("0x%08x\n",¶m1); //打印出各個(gè)變量的內(nèi)存地址 
printf("0x%08x\n",¶m2); 
printf("0x%08x\n\n",¶m3); 
printf("0x%08x\n",&var1); 
printf("0x%08x\n",&var2); 
printf("0x%08x\n\n",&var3); 
return; 

int main() 

func(1,2,3); 
return 0; 

編譯后的執(zhí)行結(jié)果是: 

0x0012ff78 
0x0012ff7c 
0x0012ff80 

0x0012ff68 
0x0012ff6c 
0x0012ff70 


├———————┤<—函數(shù)執(zhí)行時(shí)的棧頂(ESP)、低端內(nèi)存區(qū)域 
│ …… │ 
├———————┤ 
│ var 1 │ 
├———————┤ 
│ var 2 │ 
├———————┤ 
│ var 3 │ 
├———————┤ 
│ RET │ 
├———————┤<—“__cdecl”函數(shù)返回后的棧頂(ESP) 
│ parameter 1 │ 
├———————┤ 
│ parameter 2 │ 
├———————┤ 
│ parameter 3 │ 
├———————┤<—“__stdcall”函數(shù)返回后的棧頂(ESP) 
│ …… │ 
├———————┤<—棧底(基地址 EBP)、高端內(nèi)存區(qū)域 


上圖就是函數(shù)調(diào)用過(guò)程中堆棧的樣子了。首先,三個(gè)參數(shù)以從又到左的次序壓入堆棧,先壓“param3”,再壓“param2”,最后壓入“param1”;然后壓入函數(shù)的返回地址(RET),接著跳轉(zhuǎn)到函數(shù)地址接著執(zhí)行(這里要補(bǔ)充一點(diǎn),介紹UNIX下的緩沖溢出原理的文章中都提到在壓入RET后,繼續(xù)壓入當(dāng)前EBP,然后用當(dāng)前ESP代替EBP。然而,有一篇介紹windows下函數(shù)調(diào)用的文章中說(shuō),在windows下的函數(shù)調(diào)用也有這一步驟,但根據(jù)我的實(shí)際調(diào)試,并未發(fā)現(xiàn)這一步,這還可以從param3和var1之間只有4字節(jié)的間隙這點(diǎn)看出來(lái));第三步,將棧頂(ESP)減去一個(gè)數(shù),為本地變量分配內(nèi)存空間,上例中是減去12字節(jié)(ESP=ESP-3*4,每個(gè)int變量占用4個(gè)字節(jié));接著就初始化本地變量的內(nèi)存空間。由于“__stdcall”調(diào)用由被調(diào)函數(shù)調(diào)整堆棧,所以在函數(shù)返回前要恢復(fù)堆棧,先回收本地變量占用的內(nèi)存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前壓入?yún)?shù)占用的內(nèi)存(ESP=ESP+3*4),繼續(xù)執(zhí)行調(diào)用者的代碼。參見(jiàn)下列匯編代碼: 

;--------------func 函數(shù)的匯編代碼------------------- 

:00401000 83EC0C sub esp, 0000000C //創(chuàng)建本地變量的內(nèi)存空間 
:00401003 8B442410 mov eax, dword ptr [esp+10] 
:00401007 8B4C2414 mov ecx, dword ptr [esp+14] 
:0040100B 8B542418 mov edx, dword ptr [esp+18] 
:0040100F 89442400 mov dword ptr [esp], eax 
:00401013 8D442410 lea eax, dword ptr [esp+10] 
:00401017 894C2404 mov dword ptr [esp+04], ecx 

……………………(省略若干代碼) 

:00401075 83C43C add esp, 0000003C ;恢復(fù)堆棧,回收本地變量的內(nèi)存空間 
:00401078 C3 ret 000C ;函數(shù)返回,恢復(fù)參數(shù)占用的內(nèi)存空間 
;如果是“__cdecl”的話,這里是“ret”,堆棧將由調(diào)用者恢復(fù) 

;-------------------函數(shù)結(jié)束------------------------- 


;--------------主程序調(diào)用func函數(shù)的代碼-------------- 

:00401080 6A03 push 00000003 //壓入?yún)?shù)param3 
:00401082 6A02 push 00000002 //壓入?yún)?shù)param2 
:00401084 6A01 push 00000001 //壓入?yún)?shù)param1 
:00401086 E875FFFFFF call 00401000 //調(diào)用func函數(shù) 
;如果是“__cdecl”的話,將在這里恢復(fù)堆棧,“add esp, 0000000C” 

聰明的讀者看到這里,差不多就明白緩沖溢出的原理了。先來(lái)看下面的代碼: 

#include <stdio.h> 
#include <string.h> 

void __stdcall func() 

char lpBuff[8]="\0"; 
strcat(lpBuff,"AAAAAAAAAAA"); 
return; 

int main() 

func(); 
return 0; 

編譯后執(zhí)行一下回怎么樣?哈,“"0x00414141"指令引用的"0x00000000"內(nèi)存。該內(nèi)存不能為"read"。”,“非法操作”嘍!"41"就是"A"的16進(jìn)制的ASCII碼了,那明顯就是strcat這句出的問(wèn)題了。"lpBuff"的大小只有8字節(jié),算進(jìn)結(jié)尾的\0,那strcat最多只能寫(xiě)入7個(gè)"A",但程序?qū)嶋H寫(xiě)入了11個(gè)"A"外加1個(gè)\0。再來(lái)看看上面那幅圖,多出來(lái)的4個(gè)字節(jié)正好覆蓋了RET的所在的內(nèi)存空間,導(dǎo)致函數(shù)返回到一個(gè)錯(cuò)誤的內(nèi)存地址,執(zhí)行了錯(cuò)誤的指令。如果能精心構(gòu)造這個(gè)字符串,使它分成三部分,前一部份僅僅是填充的無(wú)意義數(shù)據(jù)以達(dá)到溢出的目的,接著是一個(gè)覆蓋RET的數(shù)據(jù),緊接著是一段shellcode,那只要著個(gè)RET地址能指向這段shellcode的第一個(gè)指令,那函數(shù)返回時(shí)就能執(zhí)行shellcode了。但是軟件的不同版本和不同的運(yùn)行環(huán)境都可能影響這段shellcode在內(nèi)存中的位置,那么要構(gòu)造這個(gè)RET是十分困難的。一般都在RET和shellcode之間填充大量的NOP指令,使得exploit有更強(qiáng)的通用性。 


├———————┤<—低端內(nèi)存區(qū)域 
│ …… │ 
├———————┤<—由exploit填入數(shù)據(jù)的開(kāi)始 
│ │ 
│ buffer │<—填入無(wú)用的數(shù)據(jù) 
│ │ 
├———————┤ 
│ RET │<—指向shellcode,或NOP指令的范圍 
├———————┤ 
│ NOP │ 
│ …… │<—填入的NOP指令,是RET可指向的范圍 
│ NOP │ 
├———————┤ 
│ │ 
│ shellcode │ 
│ │ 
├———————┤<—由exploit填入數(shù)據(jù)的結(jié)束 
│ …… │ 
├———————┤<—高端內(nèi)存區(qū)域 


windows下的動(dòng)態(tài)數(shù)據(jù)除了可存放在棧中,還可以存放在堆中。了解C++的朋友都知道,C++可以使用new關(guān)鍵字來(lái)動(dòng)態(tài)分配內(nèi)存。來(lái)看下面的C++代碼: 

#include <stdio.h> 
#include <iostream.h> 
#include <windows.h> 

void func() 

char *buffer=new char[128]; 
char bufflocal[128]; 
static char buffstatic[128]; 
printf("0x%08x\n",buffer); //打印堆中變量的內(nèi)存地址 
printf("0x%08x\n",bufflocal); //打印本地變量的內(nèi)存地址 
printf("0x%08x\n",buffstatic); //打印靜態(tài)變量的內(nèi)存地址 

void main() 

func(); 
return; 

程序執(zhí)行結(jié)果為: 

0x004107d0 
0x0012ff04 
0x004068c0 

可以發(fā)現(xiàn)用new關(guān)鍵字分配的內(nèi)存即不在棧中,也不在靜態(tài)數(shù)據(jù)區(qū)。VC編譯器是通過(guò)windows下的“堆(heap)”來(lái)實(shí)現(xiàn)new關(guān)鍵字的內(nèi)存動(dòng)態(tài)分配。在講“堆”之前,先來(lái)了解一下和“堆”有關(guān)的幾個(gè)API函數(shù): 

HeapAlloc 在堆中申請(qǐng)內(nèi)存空間 
HeapCreate 創(chuàng)建一個(gè)新的堆對(duì)象 
HeapDestroy 銷(xiāo)毀一個(gè)堆對(duì)象 
HeapFree 釋放申請(qǐng)的內(nèi)存 
HeapWalk 枚舉堆對(duì)象的所有內(nèi)存塊 
GetProcessHeap 取得進(jìn)程的默認(rèn)堆對(duì)象 
GetProcessHeaps 取得進(jìn)程所有的堆對(duì)象 
LocalAlloc 
GlobalAlloc 

當(dāng)進(jìn)程初始化時(shí),系統(tǒng)會(huì)自動(dòng)為進(jìn)程創(chuàng)建一個(gè)默認(rèn)堆,這個(gè)堆默認(rèn)所占內(nèi)存的大小為1M。堆對(duì)象由系統(tǒng)進(jìn)行管理,它在內(nèi)存中以鏈?zhǔn)浇Y(jié)構(gòu)存在。通過(guò)下面的代碼可以通過(guò)堆動(dòng)態(tài)申請(qǐng)內(nèi)存空間: 

HANDLE hHeap=GetProcessHeap(); 
char *buff=HeapAlloc(hHeap,0,8); 

其中hHeap是堆對(duì)象的句柄,buff是指向申請(qǐng)的內(nèi)存空間的地址。那這個(gè)hHeap究竟是什么呢?它的值有什么意義嗎?看看下面這段代碼吧: 

#pragma comment(linker,"/entry:main") //定義程序的入口 
#include <windows.h> 

_CRTIMP int (__cdecl *printf)(const char *, ...); //定義STL函數(shù)printf 
/*--------------------------------------------------------------------------- 
寫(xiě)到這里,我們順便來(lái)復(fù)習(xí)一下前面所講的知識(shí): 
(*注)printf函數(shù)是C語(yǔ)言的標(biāo)準(zhǔn)函數(shù)庫(kù)中函數(shù),VC的標(biāo)準(zhǔn)函數(shù)庫(kù)由msvcrt.dll模塊實(shí)現(xiàn)。 
由函數(shù)定義可見(jiàn),printf的參數(shù)個(gè)數(shù)是可變的,函數(shù)內(nèi)部無(wú)法預(yù)先知道調(diào)用者壓入的參數(shù)個(gè)數(shù),函數(shù)只能通過(guò)分析第一個(gè)參數(shù)字符串的格式來(lái)獲得壓入?yún)?shù)的信息,由于這里參數(shù)的個(gè)數(shù)是動(dòng)態(tài)的,所以必須由調(diào)用者來(lái)平衡堆棧,這里便使用了__cdecl調(diào)用規(guī)則。BTW,Windows系統(tǒng)的API函數(shù)基本上是__stdcall調(diào)用形式,只有一個(gè)API例外,那就是wsprintf,它使用__cdecl調(diào)用規(guī)則,同printf函數(shù)一樣,這是由于它的參數(shù)個(gè)數(shù)是可變的緣故。 
---------------------------------------------------------------------------*/ 
void main() 

HANDLE hHeap=GetProcessHeap(); 
char *buff=HeapAlloc(hHeap,0,0x10); 
char *buff2=HeapAlloc(hHeap,0,0x10); 
HMODULE hMsvcrt=LoadLibrary("msvcrt.dll"); 
printf=(void *)GetProcAddress(hMsvcrt,"printf"); 
printf("0x%08x\n",hHeap); 
printf("0x%08x\n",buff); 
printf("0x%08x\n\n",buff2); 

執(zhí)行結(jié)果為: 

0x00130000 
0x00133100 
0x00133118 

hHeap的值怎么和那個(gè)buff的值那么接近呢?其實(shí)hHeap這個(gè)句柄就是指向HEAP首部的地址。在進(jìn)程的用戶(hù)區(qū)存著一個(gè)叫PEB(進(jìn)程環(huán)境塊)的結(jié)構(gòu),這個(gè)結(jié)構(gòu)中存放著一些有關(guān)進(jìn)程的重要信息,其中在PEB首地址偏移0x18處存放的ProcessHeap就是進(jìn)程默認(rèn)堆的地址,而偏移0x90處存放了指向進(jìn)程所有堆的地址列表的指針。windows有很多API都使用進(jìn)程的默認(rèn)堆來(lái)存放動(dòng)態(tài)數(shù)據(jù),如windows 2000下的所有ANSI版本的函數(shù)都是在默認(rèn)堆中申請(qǐng)內(nèi)存來(lái)轉(zhuǎn)換ANSI字符串到Unicode字符串的。對(duì)一個(gè)堆的訪問(wèn)是順序進(jìn)行的,同一時(shí)刻只能有一個(gè)線程訪問(wèn)堆中的數(shù)據(jù),當(dāng)多個(gè)線程同時(shí)有訪問(wèn)要求時(shí),只能排隊(duì)等待,這樣便造成程序執(zhí)行效率下降。 

最后來(lái)說(shuō)說(shuō)內(nèi)存中的數(shù)據(jù)對(duì)齊。所位數(shù)據(jù)對(duì)齊,是指數(shù)據(jù)所在的內(nèi)存地址必須是該數(shù)據(jù)長(zhǎng)度的整數(shù)倍,DWORD數(shù)據(jù)的內(nèi)存起始地址能被4除盡,WORD數(shù)據(jù)的內(nèi)存起始地址能被2除盡,x86 CPU能直接訪問(wèn)對(duì)齊的數(shù)據(jù),當(dāng)他試圖訪問(wèn)一個(gè)未對(duì)齊的數(shù)據(jù)時(shí),會(huì)在內(nèi)部進(jìn)行一系列的調(diào)整,這些調(diào)整對(duì)于程序來(lái)說(shuō)是透明的,但是會(huì)降低運(yùn)行速度,所以編譯器在編譯程序時(shí)會(huì)盡量保證數(shù)據(jù)對(duì)齊。同樣一段代碼,我們來(lái)看看用VC、Dev-C++和lcc三個(gè)不同編譯器編譯出來(lái)的程序的執(zhí)行結(jié)果: 

#include <stdio.h> 

int main() 

int a; 
char b; 
int c; 
printf("0x%08x\n",&a); 
printf("0x%08x\n",&b); 
printf("0x%08x\n",&c); 
return 0; 

這是用VC編譯后的執(zhí)行結(jié)果: 
0x0012ff7c 
0x0012ff7b 
0x0012ff80 
變量在內(nèi)存中的順序:b(1字節(jié))-a(4字節(jié))-c(4字節(jié))。 

這是用Dev-C++編譯后的執(zhí)行結(jié)果: 
0x0022ff7c 
0x0022ff7b 
0x0022ff74 
變量在內(nèi)存中的順序:c(4字節(jié))-中間相隔3字節(jié)-b(占1字節(jié))-a(4字節(jié))。 

這是用lcc編譯后的執(zhí)行結(jié)果: 
0x0012ff6c 
0x0012ff6b 
0x0012ff64 
變量在內(nèi)存中的順序:同上。 

三個(gè)編譯器都做到了數(shù)據(jù)對(duì)齊,但是后兩個(gè)編譯器顯然沒(méi)VC“聰明”,讓一個(gè)char占了4字節(jié),浪費(fèi)內(nèi)存哦。 


基礎(chǔ)知識(shí): 
堆棧是一種簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu),是一種只允許在其一端進(jìn)行插入或刪除的線性表。允許插入或刪除操作的一端稱(chēng)為棧頂,另一端稱(chēng)為棧底,對(duì)堆棧的插入和刪除操作被稱(chēng)為入棧和出棧。有一組CPU指令可以實(shí)現(xiàn)對(duì)進(jìn)程的內(nèi)存實(shí)現(xiàn)堆棧訪問(wèn)。其中,POP指令實(shí)現(xiàn)出棧操作,PUSH指令實(shí)現(xiàn)入棧操作。CPU的ESP寄存器存放當(dāng)前線程的棧頂指針,EBP寄存器中保存當(dāng)前線程的棧底指針。CPU的EIP寄存器存放下一個(gè)CPU指令存放的內(nèi)存地址,當(dāng)CPU執(zhí)行完當(dāng)前的指令后,從EIP寄存器中讀取下一條指令的內(nèi)存地址,然后繼續(xù)執(zhí)行。 


參考:《Windows下的HEAP溢出及其利用》by: isno 
《windows核心編程》by: Jeffrey Richter 



摘要: 討論常見(jiàn)的堆性能問(wèn)題以及如何防范它們。(共 9 頁(yè))

前言
您是否是動(dòng)態(tài)分配的 C/C++ 對(duì)象忠實(shí)且幸運(yùn)的用戶(hù)?您是否在模塊間的往返通信中頻繁地使用了“自動(dòng)化”?您的程序是否因堆分配而運(yùn)行起來(lái)很慢?不僅僅您遇到這樣的問(wèn)題。幾乎所有項(xiàng)目遲早都會(huì)遇到堆問(wèn)題。大家都想說(shuō),“我的代碼真正好,只是堆太慢”。那只是部分正確。更深入理解堆及其用法、以及會(huì)發(fā)生什么問(wèn)題,是很有用的。

什么是堆?
(如果您已經(jīng)知道什么是堆,可以跳到“什么是常見(jiàn)的堆性能問(wèn)題?”部分)

在程序中,使用堆來(lái)動(dòng)態(tài)分配和釋放對(duì)象。在下列情況下,調(diào)用堆操作: 

事先不知道程序所需對(duì)象的數(shù)量和大小。


對(duì)象太大而不適合堆棧分配程序。
堆使用了在運(yùn)行時(shí)分配給代碼和堆棧的內(nèi)存之外的部分內(nèi)存。下圖給出了堆分配程序的不同層。


GlobalAlloc/GlobalFree:Microsoft Win32 堆調(diào)用,這些調(diào)用直接與每個(gè)進(jìn)程的默認(rèn)堆進(jìn)行對(duì)話。

LocalAlloc/LocalFree:Win32 堆調(diào)用(為了與 Microsoft Windows NT 兼容),這些調(diào)用直接與每個(gè)進(jìn)程的默認(rèn)堆進(jìn)行對(duì)話。

COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函數(shù)使用每個(gè)進(jìn)程的默認(rèn)堆。自動(dòng)化程序使用“組件對(duì)象模型 (COM)”的分配程序,而申請(qǐng)的程序使用每個(gè)進(jìn)程堆。

C/C++ 運(yùn)行時(shí) (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。如 Microsoft Visual Basic 和 Java 等語(yǔ)言也提供了新的操作符并使用垃圾收集來(lái)代替堆。CRT 創(chuàng)建自己的私有堆,駐留在 Win32 堆的頂部。

Windows NT 中,Win32 堆是 Windows NT 運(yùn)行時(shí)分配程序周?chē)谋印K?nbsp;API 轉(zhuǎn)發(fā)它們的請(qǐng)求給 NTDLL。

Windows NT 運(yùn)行時(shí)分配程序提供 Windows NT 內(nèi)的核心堆分配程序。它由具有 128 個(gè)大小從 8 到 1,024 字節(jié)的空閑列表的前端分配程序組成。后端分配程序使用虛擬內(nèi)存來(lái)保留和提交頁(yè)。

在圖表的底部是“虛擬內(nèi)存分配程序”,操作系統(tǒng)使用它來(lái)保留和提交頁(yè)。所有分配程序使用虛擬內(nèi)存進(jìn)行數(shù)據(jù)的存取。

分配和釋放塊不就那么簡(jiǎn)單嗎?為何花費(fèi)這么長(zhǎng)時(shí)間?

堆實(shí)現(xiàn)的注意事項(xiàng)
傳統(tǒng)上,操作系統(tǒng)和運(yùn)行時(shí)庫(kù)是與堆的實(shí)現(xiàn)共存的。在一個(gè)進(jìn)程的開(kāi)始,操作系統(tǒng)創(chuàng)建一個(gè)默認(rèn)堆,叫做“進(jìn)程堆”。如果沒(méi)有其他堆可使用,則塊的分配使用“進(jìn)程堆”。語(yǔ)言運(yùn)行時(shí)也能在進(jìn)程內(nèi)創(chuàng)建單獨(dú)的堆。(例如,C 運(yùn)行時(shí)創(chuàng)建它自己的堆。)除這些專(zhuān)用的堆外,應(yīng)用程序或許多已載入的動(dòng)態(tài)鏈接庫(kù) (DLL) 之一可以創(chuàng)建和使用單獨(dú)的堆。Win32 提供一整套 API 來(lái)創(chuàng)建和使用私有堆。有關(guān)堆函數(shù)(英文)的詳盡指導(dǎo),請(qǐng)參見(jiàn) MSDN。

當(dāng)應(yīng)用程序或 DLL 創(chuàng)建私有堆時(shí),這些堆存在于進(jìn)程空間,并且在進(jìn)程內(nèi)是可訪問(wèn)的。從給定堆分配的數(shù)據(jù)將在同一個(gè)堆上釋放。(不能從一個(gè)堆分配而在另一個(gè)堆釋放。)

在所有虛擬內(nèi)存系統(tǒng)中,堆駐留在操作系統(tǒng)的“虛擬內(nèi)存管理器”的頂部。語(yǔ)言運(yùn)行時(shí)堆也駐留在虛擬內(nèi)存頂部。某些情況下,這些堆是操作系統(tǒng)堆中的層,而語(yǔ)言運(yùn)行時(shí)堆則通過(guò)大塊的分配來(lái)執(zhí)行自己的內(nèi)存管理。不使用操作系統(tǒng)堆,而使用虛擬內(nèi)存函數(shù)更利于堆的分配和塊的使用。

典型的堆實(shí)現(xiàn)由前、后端分配程序組成。前端分配程序維持固定大小塊的空閑列表。對(duì)于一次分配調(diào)用,堆嘗試從前端列表找到一個(gè)自由塊。如果失敗,堆被迫從后端(保留和提交虛擬內(nèi)存)分配一個(gè)大塊來(lái)滿(mǎn)足請(qǐng)求。通用的實(shí)現(xiàn)有每塊分配的開(kāi)銷(xiāo),這將耗費(fèi)執(zhí)行周期,也減少了可使用的存儲(chǔ)空間。

Knowledge Base 文章 Q10758,“用 calloc() 和 malloc() 管理內(nèi)存” (搜索文章編號(hào)), 包含了有關(guān)這些主題的更多背景知識(shí)。另外,有關(guān)堆實(shí)現(xiàn)和設(shè)計(jì)的詳細(xì)討論也可在下列著作中找到:“Dynamic Storage Allocation: A Survey and Critical Review”,作者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;“International Workshop on Memory Management”, 作者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。

Windows NT 的實(shí)現(xiàn)(Windows NT 版本 4.0 和更新版本) 使用了 127 個(gè)大小從 8 到 1,024 字節(jié)的 8 字節(jié)對(duì)齊塊空閑列表和一個(gè)“大塊”列表。“大塊”列表(空閑列表[0]) 保存大于 1,024 字節(jié)的塊。空閑列表容納了用雙向鏈表鏈接在一起的對(duì)象。默認(rèn)情況下,“進(jìn)程堆”執(zhí)行收集操作。(收集是將相鄰空閑塊合并成一個(gè)大塊的操作。)收集耗費(fèi)了額外的周期,但減少了堆塊的內(nèi)部碎片。

單一全局鎖保護(hù)堆,防止多線程式的使用。(請(qǐng)參見(jiàn)“Server Performance and Scalability Killers”中的第一個(gè)注意事項(xiàng), George Reilly 所著,在 “MSDN Online Web Workshop”上(站點(diǎn):http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)單一全局鎖本質(zhì)上是用來(lái)保護(hù)堆數(shù)據(jù)結(jié)構(gòu),防止跨多線程的隨機(jī)存取。若堆操作太頻繁,單一全局鎖會(huì)對(duì)性能有不利的影響。

什么是常見(jiàn)的堆性能問(wèn)題?
以下是您使用堆時(shí)會(huì)遇到的最常見(jiàn)問(wèn)題: 

分配操作造成的速度減慢。光分配就耗費(fèi)很長(zhǎng)時(shí)間。最可能導(dǎo)致運(yùn)行速度減慢原因是空閑列表沒(méi)有塊,所以運(yùn)行時(shí)分配程序代碼會(huì)耗費(fèi)周期尋找較大的空閑塊,或從后端分配程序分配新塊。


釋放操作造成的速度減慢。釋放操作耗費(fèi)較多周期,主要是啟用了收集操作。收集期間,每個(gè)釋放操作“查找”它的相鄰塊,取出它們并構(gòu)造成較大塊,然后再把此較大塊插入空閑列表。在查找期間,內(nèi)存可能會(huì)隨機(jī)碰到,從而導(dǎo)致高速緩存不能命中,性能降低。


堆競(jìng)爭(zhēng)造成的速度減慢。當(dāng)兩個(gè)或多個(gè)線程同時(shí)訪問(wèn)數(shù)據(jù),而且一個(gè)線程繼續(xù)進(jìn)行之前必須等待另一個(gè)線程完成時(shí)就發(fā)生競(jìng)爭(zhēng)。競(jìng)爭(zhēng)總是導(dǎo)致麻煩;這也是目前多處理器系統(tǒng)遇到的最大問(wèn)題。當(dāng)大量使用內(nèi)存塊的應(yīng)用程序或 DLL 以多線程方式運(yùn)行(或運(yùn)行于多處理器系統(tǒng)上)時(shí)將導(dǎo)致速度減慢。單一鎖定的使用—常用的解決方案—意味著使用堆的所有操作是序列化的。當(dāng)?shù)却i定時(shí)序列化會(huì)引起線程切換上下文。可以想象交叉路口閃爍的紅燈處走走停停導(dǎo)致的速度減慢。 
競(jìng)爭(zhēng)通常會(huì)導(dǎo)致線程和進(jìn)程的上下文切換。上下文切換的開(kāi)銷(xiāo)是很大的,但開(kāi)銷(xiāo)更大的是數(shù)據(jù)從處理器高速緩存中丟失,以及后來(lái)線程復(fù)活時(shí)的數(shù)據(jù)重建。

堆破壞造成的速度減慢。造成堆破壞的原因是應(yīng)用程序?qū)Χ褖K的不正確使用。通常情形包括釋放已釋放的堆塊或使用已釋放的堆塊,以及塊的越界重寫(xiě)等明顯問(wèn)題。(破壞不在本文討論范圍之內(nèi)。有關(guān)內(nèi)存重寫(xiě)和泄漏等其他細(xì)節(jié),請(qǐng)參見(jiàn) Microsoft Visual C++(R) 調(diào)試文檔 。)


頻繁的分配和重分配造成的速度減慢。這是使用腳本語(yǔ)言時(shí)非常普遍的現(xiàn)象。如字符串被反復(fù)分配,隨重分配增長(zhǎng)和釋放。不要這樣做,如果可能,盡量分配大字符串和使用緩沖區(qū)。另一種方法就是盡量少用連接操作。
競(jìng)爭(zhēng)是在分配和釋放操作中導(dǎo)致速度減慢的問(wèn)題。理想情況下,希望使用沒(méi)有競(jìng)爭(zhēng)和快速分配/釋放的堆。可惜,現(xiàn)在還沒(méi)有這樣的通用堆,也許將來(lái)會(huì)有。

在所有的服務(wù)器系統(tǒng)中(如 IIS、MSProxy、DatabaseStacks、網(wǎng)絡(luò)服務(wù)器、 Exchange 和其他), 堆鎖定實(shí)在是個(gè)大瓶頸。處理器數(shù)越多,競(jìng)爭(zhēng)就越會(huì)惡化。

盡量減少堆的使用
現(xiàn)在您明白使用堆時(shí)存在的問(wèn)題了,難道您不想擁有能解決這些問(wèn)題的超級(jí)魔棒嗎?我可希望有。但沒(méi)有魔法能使堆運(yùn)行加快—因此不要期望在產(chǎn)品出貨之前的最后一星期能夠大為改觀。如果提前規(guī)劃堆策略,情況將會(huì)大大好轉(zhuǎn)。調(diào)整使用堆的方法,減少對(duì)堆的操作是提高性能的良方。

如何減少使用堆操作?通過(guò)利用數(shù)據(jù)結(jié)構(gòu)內(nèi)的位置可減少堆操作的次數(shù)。請(qǐng)考慮下列實(shí)例:

struct ObjectA {
   // objectA 的數(shù)據(jù) 
}

struct ObjectB {
   // objectB 的數(shù)據(jù) 
}

// 同時(shí)使用 objectA 和 objectB

//
// 使用指針 
//
struct ObjectB {
   struct ObjectA * pObjA;
   // objectB 的數(shù)據(jù) 
}

//
// 使用嵌入
//
struct ObjectB {
   struct ObjectA pObjA;
   // objectB 的數(shù)據(jù) 
}

//
// 集合 – 在另一對(duì)象內(nèi)使用 objectA 和 objectB
//

struct ObjectX {
   struct ObjectA  objA;
   struct ObjectB  objB;
}

避免使用指針關(guān)聯(lián)兩個(gè)數(shù)據(jù)結(jié)構(gòu)。如果使用指針關(guān)聯(lián)兩個(gè)數(shù)據(jù)結(jié)構(gòu),前面實(shí)例中的對(duì)象 A 和 B 將被分別分配和釋放。這會(huì)增加額外開(kāi)銷(xiāo)—我們要避免這種做法。


把帶指針的子對(duì)象嵌入父對(duì)象。當(dāng)對(duì)象中有指針時(shí),則意味著對(duì)象中有動(dòng)態(tài)元素(百分之八十)和沒(méi)有引用的新位置。嵌入增加了位置從而減少了進(jìn)一步分配/釋放的需求。這將提高應(yīng)用程序的性能。


合并小對(duì)象形成大對(duì)象(聚合)。聚合減少分配和釋放的塊的數(shù)量。如果有幾個(gè)開(kāi)發(fā)者,各自開(kāi)發(fā)設(shè)計(jì)的不同部分,則最終會(huì)有許多小對(duì)象需要合并。集成的挑戰(zhàn)就是要找到正確的聚合邊界。


內(nèi)聯(lián)緩沖區(qū)能夠滿(mǎn)足百分之八十的需要(aka 80-20 規(guī)則)。個(gè)別情況下,需要內(nèi)存緩沖區(qū)來(lái)保存字符串/二進(jìn)制數(shù)據(jù),但事先不知道總字節(jié)數(shù)。估計(jì)并內(nèi)聯(lián)一個(gè)大小能滿(mǎn)足百分之八十需要的緩沖區(qū)。對(duì)剩余的百分之二十,可以分配一個(gè)新的緩沖區(qū)和指向這個(gè)緩沖區(qū)的指針。這樣,就減少分配和釋放調(diào)用并增加數(shù)據(jù)的位置空間,從根本上提高代碼的性能。


在塊中分配對(duì)象(塊化)。塊化是以組的方式一次分配多個(gè)對(duì)象的方法。如果對(duì)列表的項(xiàng)連續(xù)跟蹤,例如對(duì)一個(gè) {名稱(chēng),值} 對(duì)的列表,有兩種選擇:選擇一是為每一個(gè)“名稱(chēng)-值”對(duì)分配一個(gè)節(jié)點(diǎn);選擇二是分配一個(gè)能容納(如五個(gè))“名稱(chēng)-值”對(duì)的結(jié)構(gòu)。例如,一般情況下,如果存儲(chǔ)四對(duì),就可減少節(jié)點(diǎn)的數(shù)量,如果需要額外的空間數(shù)量,則使用附加的鏈表指針。 
塊化是友好的處理器高速緩存,特別是對(duì)于 L1-高速緩存,因?yàn)樗峁┝嗽黾拥奈恢?nbsp;—不用說(shuō)對(duì)于塊分配,很多數(shù)據(jù)塊會(huì)在同一個(gè)虛擬頁(yè)中。

正確使用 _amblksiz。C 運(yùn)行時(shí) (CRT) 有它的自定義前端分配程序,該分配程序從后端(Win32 堆)分配大小為 _amblksiz 的塊。將 _amblksiz 設(shè)置為較高的值能潛在地減少對(duì)后端的調(diào)用次數(shù)。這只對(duì)廣泛使用 CRT 的程序適用。
使用上述技術(shù)將獲得的好處會(huì)因?qū)ο箢?lèi)型、大小及工作量而有所不同。但總能在性能和可升縮性方面有所收獲。另一方面,代碼會(huì)有點(diǎn)特殊,但如果經(jīng)過(guò)深思熟慮,代碼還是很容易管理的。

其他提高性能的技術(shù)
下面是一些提高速度的技術(shù): 

使用 Windows NT5 堆 
由于幾個(gè)同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了幾個(gè)重大改進(jìn):

改進(jìn)了堆代碼內(nèi)的鎖定。堆代碼對(duì)每堆一個(gè)鎖。全局鎖保護(hù)堆數(shù)據(jù)結(jié)構(gòu),防止多線程式的使用。但不幸的是,在高通信量的情況下,堆仍受困于全局鎖,導(dǎo)致高競(jìng)爭(zhēng)和低性能。Windows 2000 中,鎖內(nèi)代碼的臨界區(qū)將競(jìng)爭(zhēng)的可能性減到最小,從而提高了可伸縮性。


使用 “Lookaside”列表。堆數(shù)據(jù)結(jié)構(gòu)對(duì)塊的所有空閑項(xiàng)使用了大小在 8 到 1,024 字節(jié)(以 8-字節(jié)遞增)的快速高速緩存。快速高速緩存最初保護(hù)在全局鎖內(nèi)。現(xiàn)在,使用 lookaside 列表來(lái)訪問(wèn)這些快速高速緩存空閑列表。這些列表不要求鎖定,而是使用 64 位的互鎖操作,因此提高了性能。


內(nèi)部數(shù)據(jù)結(jié)構(gòu)算法也得到改進(jìn)。
這些改進(jìn)避免了對(duì)分配高速緩存的需求,但不排除其他的優(yōu)化。使用 Windows NT5 堆評(píng)估您的代碼;它對(duì)小于 1,024 字節(jié) (1 KB) 的塊(來(lái)自前端分配程序的塊)是最佳的。GlobalAlloc() 和 LocalAlloc() 建立在同一堆上,是存取每個(gè)進(jìn)程堆的通用機(jī)制。如果希望獲得高的局部性能,則使用 Heap(R) API 來(lái)存取每個(gè)進(jìn)程堆,或?yàn)榉峙洳僮鲃?chuàng)建自己的堆。如果需要對(duì)大塊操作,也可以直接使用 VirtualAlloc() / VirtualFree() 操作。

上述改進(jìn)已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改進(jìn)后,堆鎖的競(jìng)爭(zhēng)率顯著降低。這使所有 Win32 堆的直接用戶(hù)受益。CRT 堆建立于 Win32 堆的頂部,但它使用自己的小塊堆,因而不能從 Windows NT 改進(jìn)中受益。(Visual C++ 版本 6.0 也有改進(jìn)的堆分配程序。)

使用分配高速緩存 
分配高速緩存允許高速緩存分配的塊,以便將來(lái)重用。這能夠減少對(duì)進(jìn)程堆(或全局堆)的分配/釋放調(diào)用的次數(shù),也允許最大限度的重用曾經(jīng)分配的塊。另外,分配高速緩存允許收集統(tǒng)計(jì)信息,以便較好地理解對(duì)象在較高層次上的使用。

典型地,自定義堆分配程序在進(jìn)程堆的頂部實(shí)現(xiàn)。自定義堆分配程序與系統(tǒng)堆的行為很相似。主要的差別是它在進(jìn)程堆的頂部為分配的對(duì)象提供高速緩存。高速緩存設(shè)計(jì)成一套固定大小(如 32 字節(jié)、64 字節(jié)、128 字節(jié)等)。這一個(gè)很好的策略,但這種自定義堆分配程序丟失與分配和釋放的對(duì)象相關(guān)的“語(yǔ)義信息”。 

與自定義堆分配程序相反,“分配高速緩存”作為每類(lèi)分配高速緩存來(lái)實(shí)現(xiàn)。除能夠提供自定義堆分配程序的所有好處之外,它們還能夠保留大量語(yǔ)義信息。每個(gè)分配高速緩存處理程序與一個(gè)目標(biāo)二進(jìn)制對(duì)象關(guān)聯(lián)。它能夠使用一套參數(shù)進(jìn)行初始化,這些參數(shù)表示并發(fā)級(jí)別、對(duì)象大小和保持在空閑列表中的元素的數(shù)量等。分配高速緩存處理程序?qū)ο缶S持自己的私有空閑實(shí)體池(不超過(guò)指定的閥值)并使用私有保護(hù)鎖。合在一起,分配高速緩存和私有鎖減少了與主系統(tǒng)堆的通信量,因而提供了增加的并發(fā)、最大限度的重用和較高的可伸縮性。

需要使用清理程序來(lái)定期檢查所有分配高速緩存處理程序的活動(dòng)情況并回收未用的資源。如果發(fā)現(xiàn)沒(méi)有活動(dòng),將釋放分配對(duì)象的池,從而提高性能。

可以審核每個(gè)分配/釋放活動(dòng)。第一級(jí)信息包括對(duì)象、分配和釋放調(diào)用的總數(shù)。通過(guò)查看它們的統(tǒng)計(jì)信息可以得出各個(gè)對(duì)象之間的語(yǔ)義關(guān)系。利用以上介紹的許多技術(shù)之一,這種關(guān)系可以用來(lái)減少內(nèi)存分配。

分配高速緩存也起到了調(diào)試助手的作用,幫助您跟蹤沒(méi)有完全清除的對(duì)象數(shù)量。通過(guò)查看動(dòng)態(tài)堆棧返回蹤跡和除沒(méi)有清除的對(duì)象之外的簽名,甚至能夠找到確切的失敗的調(diào)用者。

MP 堆 
MP 堆是對(duì)多處理器友好的分布式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中可以得到。最初由 JVert 實(shí)現(xiàn),此處堆抽象建立在 Win32 堆程序包的頂部。MP 堆創(chuàng)建多個(gè) Win32 堆,并試圖將分配調(diào)用分布到不同堆,以減少在所有單一鎖上的競(jìng)爭(zhēng)。

本程序包是好的步驟 —一種改進(jìn)的 MP-友好的自定義堆分配程序。但是,它不提供語(yǔ)義信息和缺乏統(tǒng)計(jì)功能。通常將 MP 堆作為 SDK 庫(kù)來(lái)使用。如果使用這個(gè) SDK 創(chuàng)建可重用組件,您將大大受益。但是,如果在每個(gè) DLL 中建立這個(gè) SDK 庫(kù),將增加工作設(shè)置。

重新思考算法和數(shù)據(jù)結(jié)構(gòu) 
要在多處理器機(jī)器上伸縮,則算法、實(shí)現(xiàn)、數(shù)據(jù)結(jié)構(gòu)和硬件必須動(dòng)態(tài)伸縮。請(qǐng)看最經(jīng)常分配和釋放的數(shù)據(jù)結(jié)構(gòu)。試問(wèn),“我能用不同的數(shù)據(jù)結(jié)構(gòu)完成此工作嗎?”例如,如果在應(yīng)用程序初始化時(shí)加載了只讀項(xiàng)的列表,這個(gè)列表不必是線性鏈接的列表。如果是動(dòng)態(tài)分配的數(shù)組就非常好。動(dòng)態(tài)分配的數(shù)組將減少內(nèi)存中的堆塊和碎片,從而增強(qiáng)性能。

減少需要的小對(duì)象的數(shù)量減少堆分配程序的負(fù)載。例如,我們?cè)诜?wù)器的關(guān)鍵處理路徑上使用五個(gè)不同的對(duì)象,每個(gè)對(duì)象單獨(dú)分配和釋放。一起高速緩存這些對(duì)象,把堆調(diào)用從五個(gè)減少到一個(gè),顯著減少了堆的負(fù)載,特別當(dāng)每秒鐘處理 1,000 個(gè)以上的請(qǐng)求時(shí)。

如果大量使用“Automation”結(jié)構(gòu),請(qǐng)考慮從主線代碼中刪除“Automation BSTR”,或至少避免重復(fù)的 BSTR 操作。(BSTR 連接導(dǎo)致過(guò)多的重分配和分配/釋放操作。)

摘要
對(duì)所有平臺(tái)往往都存在堆實(shí)現(xiàn),因此有巨大的開(kāi)銷(xiāo)。每個(gè)單獨(dú)代碼都有特定的要求,但設(shè)計(jì)能采用本文討論的基本理論來(lái)減少堆之間的相互作用。 

評(píng)價(jià)您的代碼中堆的使用。


改進(jìn)您的代碼,以使用較少的堆調(diào)用:分析關(guān)鍵路徑和固定數(shù)據(jù)結(jié)構(gòu)。


在實(shí)現(xiàn)自定義的包裝程序之前使用量化堆調(diào)用成本的方法。


如果對(duì)性能不滿(mǎn)意,請(qǐng)要求 OS 組改進(jìn)堆。更多這類(lèi)請(qǐng)求意味著對(duì)改進(jìn)堆的更多關(guān)注。


要求 C 運(yùn)行時(shí)組針對(duì) OS 所提供的堆制作小巧的分配包裝程序。隨著 OS 堆的改進(jìn),C 運(yùn)行時(shí)堆調(diào)用的成本將減小。


操作系統(tǒng)(Windows NT 家族)正在不斷改進(jìn)堆。請(qǐng)隨時(shí)關(guān)注和利用這些改進(jìn)。
Murali Krishnan 是 Internet Information Server (IIS) 組的首席軟件設(shè)計(jì)工程師。從 1.0 版本開(kāi)始他就設(shè)計(jì) IIS,并成功發(fā)行了 1.0 版本到 4.0 版本。Murali 組織并領(lǐng)導(dǎo) IIS 性能組三年 (1995-1998), 從一開(kāi)始就影響 IIS 性能。他擁有威斯康星州 Madison 大學(xué)的 M.S.和印度 Anna 大學(xué)的 B.S.。工作之外,他喜歡閱讀、打排球和家庭烹飪。



http://community.csdn.net/Expert/FAQ/FAQ_Index.asp?id=172835
我在學(xué)習(xí)對(duì)象的生存方式的時(shí)候見(jiàn)到一種是在堆棧(stack)之中,如下  
CObject  object;  
還有一種是在堆(heap)中  如下  
CObject*  pobject=new  CObject();  
 
請(qǐng)問(wèn)  
(1)這兩種方式有什么區(qū)別?  
(2)堆棧與堆有什么區(qū)別??  
 
 
---------------------------------------------------------------  
 
1)  about  stack,  system  will  allocate  memory  to  the  instance  of  object  automatically,  and  to  the
 heap,  you  must  allocate  memory  to  the  instance  of  object  with  new  or  malloc  manually.  
2)  when  function  ends,  system  will  automatically  free  the  memory  area  of  stack,  but  to  the 
heap,  you  must  free  the  memory  area  manually  with  free  or  delete,  else  it  will  result  in  memory
leak.  
3)棧內(nèi)存分配運(yùn)算內(nèi)置于處理器的指令集中,效率很高,但是分配的內(nèi)存容量有限。  
4)堆上分配的內(nèi)存可以有我們自己決定,使用非常靈活。  
---------------------------------------------------------------  

posted @ 2009-04-10 09:11 不會(huì)飛的鳥(niǎo) 閱讀(140) | 評(píng)論 (0)編輯 收藏

[交互設(shè)計(jì)工具/原型創(chuàng)作工具]Axure RP Pro 5.0新特性 破解版 注冊(cè)碼

先感謝木的{moond},要不是一次提供了那么多畫(huà)原型圖的工具,我現(xiàn)在還在Word和Visio里面打轉(zhuǎn)呢。

這些軟件能試用的我差不多都用了一下,其中覺(jué)得Axure的RP Pro 4挺好的,功能包括站點(diǎn)地圖、流程設(shè)計(jì)、頁(yè)面框架設(shè)計(jì)、簡(jiǎn)單交互設(shè)計(jì)等,可以生成HTML、Word等格式。

RP操作比用Dreamweaver簡(jiǎn)單多了,圖片、文字、輸入框等等所有組件全是可視化操作,可以很方便的實(shí)現(xiàn)網(wǎng)站交互內(nèi)容的原型設(shè)計(jì),可將以前死板的頁(yè)面版式全部替換為有點(diǎn)擊、鏈接效果的網(wǎng)頁(yè),nice~

同時(shí)可以為網(wǎng)站設(shè)計(jì)提供AJAX式的交互處理,RP提供一種動(dòng)態(tài)層的組件,在同一頁(yè)設(shè)定不同狀態(tài)的效果,然后用鏈接、按鈕等觸發(fā)即可產(chǎn)生動(dòng)態(tài)效果,這樣網(wǎng)站設(shè)計(jì)就變得更加生動(dòng),意圖也就更加直觀。

可惜,這么好的軟件只能試用30天,哪位兄臺(tái)要是有解決方法一定要通知我,這里先謝了~

以下對(duì)RP5的新特性做了下簡(jiǎn)單翻譯:

Axure RP Pro 5.0增加了眾多新特性,并且著重增強(qiáng)了設(shè)計(jì)師間的協(xié)作、交互功能和更多的定制功能,生成也變的更快。

新功能包括:

1. 共享工程
1.1 可以在網(wǎng)絡(luò)驅(qū)動(dòng)器上創(chuàng)建共享工程,而不必非安裝在服務(wù)器上
1.2 管理和便捷頁(yè)面只需要通過(guò)簡(jiǎn)單的 check in / check out 即可
1.3 維護(hù)和瀏覽歷史版本
1.4 更多介紹

2. 交互功能的增強(qiáng)
2.1 OnFocus 和 OnLostFocus 事件 (更多)
2.2 使用和禁止Widgets行為
2.3 模仿在圖片上的錨點(diǎn)/熱區(qū)行為 (更多)
2.4 移動(dòng)動(dòng)態(tài)層(Dynamic Panels)行為 (更多)
2.5 在其他行為運(yùn)行之前暫停當(dāng)前行為
2.6 頁(yè)面間多達(dá)25個(gè)變量的儲(chǔ)存

3. 規(guī)范的增強(qiáng)
3.1 可以更快的把演示生成為Microsoft Word 2007格式(使用Microsoft Office兼容包可與Microsoft Word 2000+兼容)
3.2 有1欄和2欄布局的選項(xiàng)
3.3 可以更簡(jiǎn)單的定制Word模板
3.4 有更多新選項(xiàng)去配置規(guī)范內(nèi)容

4. 更多增強(qiáng)
4.1 優(yōu)化Tab交互
4.2 可以選擇與主窗體分離(比如Sitemap和Widgets)
4.3 有更好的保存機(jī)制來(lái)處理關(guān)閉和重開(kāi)一個(gè)文件
4.4 帶有梯度和透明設(shè)置的顏色工具
4.5 打印設(shè)置是被保存的

查看原文

同時(shí)提示一下:如果之前裝過(guò)RP4,并且有l(wèi)icense的話,RP5可以直接使用,
是作為免費(fèi)升級(jí)的,當(dāng)然,你也可以只當(dāng)下來(lái)看看,有30天試用

=======================================================================

互聯(lián)網(wǎng)行業(yè)產(chǎn)品經(jīng)理的一項(xiàng)重要工作,就是進(jìn)行產(chǎn)品原型設(shè)計(jì)(Prototype Design)。而產(chǎn)品原型設(shè)計(jì)最基礎(chǔ)的工作,就是結(jié)合批注、大量的說(shuō)明以及流程圖畫(huà)框架圖wireframe,將自己的產(chǎn)品原型完整而準(zhǔn)確的表述給UI、UE、程序工程師,市場(chǎng)人員,并通過(guò)溝通會(huì)議,反復(fù)修改prototype 直至最終確認(rèn),開(kāi)始投入執(zhí)行。

進(jìn)行產(chǎn)品原型設(shè)計(jì)的軟件工具也有很多種,我寫(xiě)的這個(gè)教程所介紹的Axure RP,是taobao、dangdang等國(guó)內(nèi)大型網(wǎng)絡(luò)公司的團(tuán)隊(duì)在推廣使用的原型設(shè)計(jì)軟件。同時(shí),此軟件也在產(chǎn)品經(jīng)理圈子中廣為流傳。之所以Axure RP得到了大家的認(rèn)同和推廣,正是因?yàn)槠浜?jiǎn)便的操作和使用,符合了產(chǎn)品經(jīng)理、交互設(shè)計(jì)師們的需求。

在正式談Axure RP之前,我們先來(lái)看看做產(chǎn)品原型設(shè)計(jì),現(xiàn)在大致有哪些工具可以使用,而他們的利弊何在。
紙筆:簡(jiǎn)單易得,上手難度為零。有力于瞬間創(chuàng)意的產(chǎn)生與記錄,有力于對(duì)文檔即時(shí)的討論與修改。但是保真度不高,難以表述頁(yè)面流程,更難以表述交互信息與程序需求細(xì)節(jié)。
Word:上手難度普通。可以畫(huà)wireframe,能夠畫(huà)頁(yè)面流程,能夠使用批注與文字說(shuō)明。但是對(duì)交互表達(dá)不好,也不利于演示。
PPT:上手難度普通。易于畫(huà)框架圖,易于做批注,也可以表達(dá)交互流程,也擅長(zhǎng)演示。但是不利于大篇幅的文檔表達(dá)。
Visio:功能相對(duì)比較復(fù)雜。善于畫(huà)流程圖,框架圖。不利于批注與大篇幅的文字說(shuō)明。同樣不利于交互的表達(dá)與演示。
Photshop/fireworks:操作難度相對(duì)較大,易于畫(huà)框架圖、流程圖。不利于表達(dá)交互設(shè)計(jì),不擅長(zhǎng)文字說(shuō)明與批注。
Dreamweave:操作難度大,需要基礎(chǔ)的html知識(shí)。易于畫(huà)框架圖、流程圖、表達(dá)交互設(shè)計(jì)。不擅長(zhǎng)文字說(shuō)明與批注。

以上這些工具,都是產(chǎn)品經(jīng)理經(jīng)常會(huì)使用到的,但是從根本上來(lái)說(shuō),這些工具都不是做prototype design的專(zhuān)門(mén)利器,需要根據(jù)產(chǎn)品開(kāi)發(fā)不同的目的,不同的開(kāi)發(fā)階段,選擇不同的工具搭配使用,才能達(dá)到表達(dá)、溝通的目的。

比如使用紙筆,更適合在產(chǎn)品創(chuàng)意階段使用,可以快速記錄閃電般的思路和靈感;也可以在即時(shí)討論溝通時(shí)使用,通過(guò)圖形快速表達(dá)自己的產(chǎn)品思路,及時(shí)的畫(huà)出來(lái),是再好不過(guò)的方法。而word則適合在用文字詳細(xì)表達(dá)產(chǎn)品,對(duì)產(chǎn)品進(jìn)行細(xì)節(jié)說(shuō)明時(shí)使用,圖片結(jié)合文字的排版,是word最擅長(zhǎng)的工作。而ppt自然是演示時(shí)更好。visio則可以適用于各種流程圖、關(guān)系圖的表達(dá),更可通過(guò)畫(huà)use case 獲取用戶(hù)需求。PS/FW是圖片處理的工具,DW則是所見(jiàn)即所得的網(wǎng)頁(yè)開(kāi)發(fā)軟件,這些是設(shè)計(jì)師的看家本領(lǐng),對(duì)于普通的產(chǎn)品經(jīng)理來(lái)說(shuō),需要耗費(fèi)太多的精力去掌握。

其實(shí)每件工具,每個(gè)軟件,在創(chuàng)造它的初期,軟件設(shè)計(jì)師們都給它賦予了性格、氣質(zhì)。因?yàn)槊總€(gè)工具的產(chǎn)生,都是為了滿(mǎn)足人類(lèi)的某一方面需求。比如鋤頭是鋤土的,起子是起螺絲的,電熨斗是燙衣服的。但是不同的工具都有自己的工作領(lǐng)域,在其他領(lǐng)域它并不擅長(zhǎng)。而以上的軟件在創(chuàng)造的初期,并非為了幫助產(chǎn)品經(jīng)理、ue完成產(chǎn)品原型設(shè)計(jì),因此他們都不能在prototype design 這件工作上得心應(yīng)手。而Axure RP正是在互聯(lián)網(wǎng)產(chǎn)品大張其道的前提下,為滿(mǎn)足prototype design創(chuàng)建的需求,應(yīng)運(yùn)而生。

Axure RP 能幫助網(wǎng)站需求設(shè)計(jì)者,快捷而簡(jiǎn)便的創(chuàng)建 基于目錄組織的原型文檔、功能說(shuō)明、交互界面以及帶注釋的wireframe網(wǎng)頁(yè),并可自動(dòng)生成用于演示的網(wǎng)頁(yè)文件和word文檔,以提供演示與開(kāi)發(fā)。

沒(méi)錯(cuò)!Axure RP 的特點(diǎn)是:

  • 快速創(chuàng)建帶注釋的wireframe文件,并可根據(jù)所設(shè)置的時(shí)間周期,軟件自動(dòng)保存文檔,確保文件安全。
    ?
  • 在不寫(xiě)任何一條html與javascript語(yǔ)句的情況下,通過(guò)創(chuàng)建的文檔以及相關(guān)條件和注釋?zhuān)绘I生成html prototype演示。
    ?
  • 根據(jù)設(shè)計(jì)稿,一鍵生成一致而專(zhuān)業(yè)的word版本的原型設(shè)計(jì)文檔。

說(shuō)到這里相信很多人已經(jīng)激起了興趣,但是在開(kāi)始學(xué)習(xí)之前,我認(rèn)為我們還是有必要先了解一下軟件短短的歷史,創(chuàng)造這一軟件的公司-Axure Software Solutions, Inc.該公司創(chuàng)建于2002年五月,Axure RP是這一軟件公司的旗艦產(chǎn)品,2003年一月Axure RP第一版本上線發(fā)表,至今已經(jīng)正式發(fā)行到了第四個(gè)版本,而我提筆寫(xiě)到這里的時(shí)候,Axure 5.0版本beta版本已經(jīng)正式提供下載試用,雖然我已經(jīng)下載使用,但是我想,寫(xiě)教程還是應(yīng)該先從穩(wěn)定的4.6版說(shuō)起,至于5.0版,我們可以伴隨著軟件一起成長(zhǎng)。

接下來(lái)我會(huì)結(jié)合圖片,分幾個(gè)步驟分享我對(duì)Axure的認(rèn)識(shí),
一、 界面與功能
二、 工具欄
三、 站點(diǎn)地圖
四、 組件與使用方法
五、 復(fù)用模板與使用
六、 交互功能與注釋
七、 實(shí)例

當(dāng)然,在書(shū)寫(xiě)的過(guò)程中我會(huì)根據(jù)具體的情況再進(jìn)行調(diào)整,盡量做到圖文并茂,易于理解。寫(xiě)這個(gè)教程的目的,一方面為自己熟悉與更加理解Axure,另一方面也鞭策自己完善自己的blog網(wǎng)站www.2tan.net,同時(shí)也希望以自己的綿薄之力,為希望學(xué)習(xí)Axure的朋友分享一點(diǎn)經(jīng)驗(yàn)。由于這是我第一次嘗試寫(xiě)教程,難免會(huì)出現(xiàn)偏頗,也希望朋友們能夠不吝賜教,共同進(jìn)步。

另,為e文好的朋友附上自學(xué)Axure RP的幾個(gè)途徑:
1、 打開(kāi)軟件,按F1調(diào)取幫助文檔,對(duì)照文檔學(xué)習(xí)。
2、 登錄http://www.axure.com/au-home.aspx 查看flash視頻學(xué)習(xí)。
3、 登錄 Axure 博客 http://axure.com/cs/blogs/Default.aspx,了解軟件最新信息。
4、 登錄討論組http://axure.com/cs/forums/Default.aspx,參與討論。

并提供Axure RP pro 4.6版本的下載
http://www.2tan.net/LoadMod.asp?plugins=downloadForPJBlog
由于放存軟件的網(wǎng)絡(luò)硬盤(pán)在下載數(shù)量不多的情況下可能會(huì)刪除文件,如遇到死鏈接,可留言給我,我將重新上傳:)
(軟件僅供學(xué)習(xí)使用,反對(duì)商業(yè)用途 -_-!!!)


Name:3ddown
Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq+7w1RH97k5MWctqVHA

posted @ 2009-03-20 13:23 不會(huì)飛的鳥(niǎo) 閱讀(771) | 評(píng)論 (1)編輯 收藏

PowerDesigner下建索引、自增列、檢查設(shè)計(jì)模型

From: http://www.qqread.com/java/2006/07/u996183002.html

這段時(shí)間,使用PD做數(shù)據(jù)庫(kù)模型,感覺(jué)很不錯(cuò),將自已的經(jīng)驗(yàn)總給一下.還有許多功能我沒(méi)時(shí)間總結(jié),以后有時(shí)間,繼續(xù)補(bǔ)吧
  
. 如何在PowerDesigner下建索引
  
 1. 雙擊表設(shè)計(jì)圖,出來(lái)Table Properties,在Tab 頁(yè)中選擇 Indexes
  
   數(shù)據(jù)庫(kù)建模工具PowerDesigner總結(jié)(組圖)



 2. 單擊新建索引的屬性,出現(xiàn)Indexex Properties
  
 數(shù)據(jù)庫(kù)建模工具PowerDesigner總結(jié)(組圖)



 3. 增加一個(gè)索引包含的字段
  
 數(shù)據(jù)庫(kù)建模工具PowerDesigner總結(jié)(組圖)



點(diǎn)擊"下圖中的增加列圖標(biāo)出現(xiàn)圖如下"



選中相關(guān)字段后,點(diǎn)OK
最后返回屬性頁(yè),點(diǎn)preview出現(xiàn)圖如下



好了,索引建立成功

======================================

.如何在PowerDesigner 下建自增列
  
 1. 使用SqlServer 數(shù)據(jù)庫(kù)中的下列語(yǔ)句來(lái)完成
  
  建表語(yǔ)句中,在要做為自增列的字段中,加上如下
  IDENTITY(1,1)
  
  還有可以使用下面語(yǔ)句,重置自增種子
  dbcc checkident(ConfigSys,reseed,0);
  
.如何在PowerDesigner 下檢查設(shè)計(jì)模型
  
  1. 在菜單欄中選擇 Tools -? Check Model, 如下圖
  
 數(shù)據(jù)庫(kù)建模工具PowerDesigner總結(jié)(組圖)



  2. 選擇要檢查的每項(xiàng)設(shè)置
  
 數(shù)據(jù)庫(kù)建模工具PowerDesigner總結(jié)(組圖)



  3. 確定后,將出來(lái)檢查結(jié)果匯總信息
  
 數(shù)據(jù)庫(kù)建模工具PowerDesigner總結(jié)(組圖)

posted @ 2009-03-18 09:03 不會(huì)飛的鳥(niǎo) 閱讀(2050) | 評(píng)論 (0)編輯 收藏

c語(yǔ)言中rand()函數(shù)怎么用?

rand(產(chǎn)生隨機(jī)數(shù))
相關(guān)函數(shù)
srand

表頭文件
#include<stdlib.h>

定義函數(shù)
int rand(void)

函數(shù)說(shuō)明
rand()會(huì)返回一隨機(jī)數(shù)值,范圍在0至RAND_MAX 間。在調(diào)用此函數(shù)產(chǎn)生隨機(jī)數(shù)前,必須先利用srand()設(shè)好隨機(jī)數(shù)種子,如果未設(shè)隨機(jī)數(shù)種子,rand()在調(diào)用時(shí)會(huì)自動(dòng)設(shè)隨機(jī)數(shù)種子為1。關(guān)于隨機(jī)數(shù)種子請(qǐng)參考srand()。

返回值
返回0至RAND_MAX之間的隨機(jī)數(shù)值,RAND_MAX定義在stdlib.h,其值為2147483647。

范例
/* 產(chǎn)生介于1 到10 間的隨機(jī)數(shù)值,此范例未設(shè)隨機(jī)數(shù)種子,完整的隨機(jī)數(shù)產(chǎn)生請(qǐng)參考
srand()*/
#include<stdlib.h>
main()
{
int i,j;
for(i=0;i<10;i++)
{
j=1+(int)(10.0*rand()/(RAND_MAX+1.0));
printf("%d ",j);
}
}

執(zhí)行
9 4 8 8 10 2 4 8 3 6
9 4 8 8 10 2 4 8 3 6





srand(設(shè)置隨機(jī)數(shù)種子)
相關(guān)函數(shù)
rand

表頭文件
#include<stdlib.h>

定義函數(shù)
void srand (unsigned int seed);

函數(shù)說(shuō)明
srand()用來(lái)設(shè)置rand()產(chǎn)生隨機(jī)數(shù)時(shí)的隨機(jī)數(shù)種子。參數(shù)seed必須是個(gè)整數(shù),通常可以利用geypid()或time(0)的返回值來(lái)當(dāng)做seed。如果每次seed都設(shè)相同值,rand()所產(chǎn)生的隨機(jī)數(shù)值每次就會(huì)一樣。

返回值

范例
/* 產(chǎn)生介于1 到10 間的隨機(jī)數(shù)值,此范例與執(zhí)行結(jié)果可與rand()參照*/
#include<time.h>
#include<stdlib.h>
main()
{
int i,j;
srand((int)time(0));
for(i=0;i<10;i++)
{
j=1+(int)(10.0*rand()/(RAND_MAX+1.0));
printf(" %d ",j);
}
}

執(zhí)行
5 8 8 8 10 2 10 8 9 9
2 9 7 4 10 3 2 10 8 7

posted @ 2009-03-11 11:04 不會(huì)飛的鳥(niǎo) 閱讀(6810) | 評(píng)論 (0)編輯 收藏

如何使用 類(lèi)進(jìn)行文件的 I/O 處理

原文出處:How to Use <fstream> Classes for File I/O
摘要:傳統(tǒng)的文件 I/O 庫(kù)如 Unix 的 <io.h> 和 <stdio.h> ,由于其程序接口的原因,在很大程度上強(qiáng)制程序員進(jìn)行某些處理,缺乏類(lèi)型安全和國(guó)際化支持。C++ 的 <fstream> 庫(kù)則在文件的 I/O 方面提供了一個(gè)增強(qiáng)的、面向?qū)ο蟮摹⒕哂袊?guó)際化意識(shí)的庫(kù)。本文將介紹如何使用這個(gè)庫(kù)進(jìn)行文件的 I/O 處理并利用它來(lái)編寫(xiě)易于跨平臺(tái)的代碼。


  大多數(shù) C++ 程序員都熟悉不止一個(gè)文件 I/O 庫(kù)。首先是傳統(tǒng)的 Unix 風(fēng)格的庫(kù),它由一些低級(jí)函數(shù)如 read() 和 open()組成。其次是 ANSI C 的 <stdio.h> 庫(kù),它包含 fopen() 和 fread()等函數(shù)。其它的還有一些具備所有權(quán)的庫(kù)或框架,比如 MFC,它有很多自己的文件處理類(lèi)。
  這些庫(kù)一般都很難跨平臺(tái)使用。更糟的是,上述提到的 C 庫(kù)由于其程序接口的原因,在很大程度上強(qiáng)制程序員進(jìn)行某些處理,而且缺乏類(lèi)型安全支持。
  標(biāo)準(zhǔn) C++ 提供提供了一個(gè)增強(qiáng)的、面向?qū)ο蟮摹⒕哂袊?guó)際化意識(shí)的  <fstream> 庫(kù)。這個(gè)庫(kù)包含一系列派生于標(biāo)準(zhǔn) ios_base 和 ios 類(lèi)的類(lèi)模板。因此, <fstream> 提供了高級(jí)的自動(dòng)控制機(jī)制和健壯性。本文下面將示范如何使用  <fstream> 類(lèi)實(shí)現(xiàn)文件的輸入/輸出處理:

第一步:創(chuàng)建文件流
  輸入文件流(ifstream)支持重載的 >> 操作符,同樣,輸出文件流(ofstream)支持重載的 << 操作符。結(jié)合了輸入和輸出的文件流被稱(chēng)為 fstream。下面的程序創(chuàng)建了一個(gè) ifstream 對(duì)象:dict,并將該對(duì)象中的每一個(gè)單字顯示到屏幕上:

#include <iostream>
#include <string>
#include <fstream>
#include <cstdlib>
using namespace std;
int main()
{
string s;
cout<<"enter dictionary file: ";
cin>>s;
ifstream dict (s.c_str());
if (!dictionary) // were there any errors on opening?
exit(-1);
while (dictionary >> s) cout << s <<''\n'';
}      
  我們必須調(diào)用 string::c_str() 成員函數(shù),因?yàn)?fstream 對(duì)象只接受常量字符串作為文件名。當(dāng)你將文件名作為參數(shù)傳遞時(shí),構(gòu)造函數(shù)試圖打開(kāi)指定的文件。接著,我們用重載的 !操作符來(lái)檢查文件的狀態(tài)。如果出錯(cuò),該操作符估值為 true。最后一行是個(gè)循環(huán),每次反復(fù)都從文件讀取一個(gè)單字,將它拷貝到 s,然后顯示出來(lái)。注意我們不必顯式地檢查 EOF,因?yàn)橹剌d操作符 >> 會(huì)自動(dòng)處理。此外,我們不用顯式地關(guān)閉此文件,因?yàn)槲鰳?gòu)函數(shù)會(huì)為我們做這件事情。
  過(guò)時(shí)和荒廢的 <fstream.h> 庫(kù)支持 ios::nocreate 和 ios::noreplace 標(biāo)志。但新的 <fstream> 庫(kù)已經(jīng)取代了 <fstream.h> 并不再支持這兩個(gè)標(biāo)志。
 
文件的打開(kāi)模式
  如果你不顯式指定打開(kāi)模式,fstream 類(lèi)將使用默認(rèn)值。例如,ifstream 默認(rèn)以讀方式打開(kāi)某個(gè)文件并將文件指針置為文件的開(kāi)始處。為了向某個(gè)文件寫(xiě)入數(shù)據(jù),你需要?jiǎng)?chuàng)建一個(gè) ofstream 對(duì)象。<fstream> 定義了下列打開(kāi)模式和文件屬性:
ios::app // 從后面添加
ios::ate // 打開(kāi)并找到文件尾
ios::binary // 二進(jìn)制模式 I/O (與文本模式相對(duì))
ios::in // 只讀打開(kāi)
ios::out // 寫(xiě)打開(kāi)
ios::trunc // 將文件截為 0 長(zhǎng)度

你可以用位域操作符 OR 組合這些標(biāo)志:

ofstream logfile("login.dat", ios::binary | ios::app);

fstream 類(lèi)型對(duì)象同時(shí)支持讀和寫(xiě)操作:

fstream logfile("database.dat", ios::in | ios::out);

第二步:設(shè)置文件的位置
  文件具備一個(gè)邏輯指針,它指向該文件中的某個(gè)偏移位置。你可以通過(guò)調(diào)用seekp()成員函數(shù),以字節(jié)為單位將這個(gè)指針定位到文件的任意位置。為了獲取從文件開(kāi)始處到當(dāng)前偏移的字節(jié)數(shù),調(diào)用seekp()即可。在下面的例子中,程序?qū)⑽募恢们耙?0個(gè)字節(jié),然后調(diào)用 tellp()報(bào)告新位置:

ofstream fout("parts.txt");
fout.seekp(10); // 從0偏移開(kāi)始前進(jìn) 10 個(gè)字節(jié)
cout<<"new position: "<<fout.tellp(); // 顯示 10

你可以用下面的常量重新定位文ian指針:

ios::beg // 文件開(kāi)始位置
ios::cur // 當(dāng)前位置,例如: ios::cur+5
ios::end // 文件尾

第三步:讀寫(xiě)數(shù)據(jù)
  fstream 類(lèi)為所有內(nèi)建數(shù)據(jù)類(lèi)型以及 std::string 和 std::complex 類(lèi)型重載 << 和 >> 操作符。下面的例子示范了這些操作符的使用方法:

fstream logfile("log.dat");
logfile<<time(0)<<"danny"<<''\n''; // 寫(xiě)一條新記錄
logfile.seekp(ios::beg); // 位置重置
logfile>>login>>user; // 讀取以前寫(xiě)入的值

posted @ 2009-03-05 11:52 不會(huì)飛的鳥(niǎo) 閱讀(201) | 評(píng)論 (0)編輯 收藏

僅列出標(biāo)題
共9頁(yè): 1 2 3 4 5 6 7 8 9 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            男女激情视频一区| 久久资源在线| 久久丁香综合五月国产三级网站| 亚洲视频欧美在线| 欧美极品aⅴ影院| 亚洲成人在线视频播放| 欧美va天堂在线| 欧美黑人多人双交| 亚洲精品在线一区二区| 欧美精品福利| 久久综合色8888| 亚洲国产小视频| 日韩手机在线导航| 国产精品成人播放| 亚洲欧美日韩一区二区在线 | 激情六月婷婷久久| 久久亚洲精品欧美| 亚洲国产精品v| 免费不卡中文字幕视频| 日韩网站免费观看| 亚洲国产日韩欧美在线99| 欧美激情视频在线免费观看 欧美视频免费一| 午夜精品一区二区三区四区| 麻豆精品91| 日韩视频在线你懂得| 亚洲盗摄视频| 欧美视频一区二区三区| 小嫩嫩精品导航| 亚洲在线播放电影| 欧美成人精品在线观看| 在线一区亚洲| 国产一区亚洲| 欧美岛国在线观看| 亚洲欧美一区二区三区在线| 亚洲天堂久久| 亚洲电影在线播放| 欧美日韩国产免费| 欧美一区二区私人影院日本| 亚洲国内欧美| 亚洲国产欧美在线| 亚洲三级免费电影| 久久视频在线看| 久久亚洲精品一区二区| 久久亚洲综合色| 欧美jjzz| 亚洲欧洲一二三| 玖玖视频精品| 欧美黄在线观看| 亚洲欧洲精品天堂一级| 亚洲日韩视频| 亚洲视频1区2区| 亚洲国产精品久久久久婷婷老年| 亚洲国产精品成人| 一本色道久久综合亚洲精品高清 | 国产一区二区三区的电影 | 免费h精品视频在线播放| 一区二区三区欧美激情| 亚洲一区二区三区乱码aⅴ蜜桃女| 亚洲一区网站| 久久av最新网址| 亚洲欧美日韩在线播放| 久久成人一区二区| 你懂的一区二区| 欧美日韩精品一区二区三区四区 | 欧美日韩性视频在线| 欧美~级网站不卡| 欧美日韩激情网| 国产美女一区二区| 欧美三级日本三级少妇99| 国产精品一区二区三区观看| 狠狠色狠狠色综合系列| 国产欧美日韩一区二区三区在线| 欧美日韩另类综合| 国产精品亚洲片夜色在线| 欧美三区美女| 国产在线视频欧美| 日韩一级裸体免费视频| 欧美一区二区三区视频| 亚洲欧美韩国| avtt综合网| 亚洲精品美女91| 亚洲精品乱码久久久久久日本蜜臀| 亚洲一区二区三区涩| 亚洲午夜国产一区99re久久 | 久久精品国产视频| 欧美日韩国产精品一区二区亚洲| 国产一区二区三区久久 | 亚洲欧美日本视频在线观看| 美女视频一区免费观看| 在线一区二区视频| 免费日韩成人| 国产视频欧美| 国产视频久久| 一区二区三区四区五区视频 | 亚洲视频自拍偷拍| 女仆av观看一区| 亚洲欧美日韩精品| 欧美日韩国产小视频在线观看| 狠狠入ady亚洲精品经典电影| 亚洲一区二区三区在线视频| 亚洲第一页中文字幕| 亚洲国产精彩中文乱码av在线播放| 亚洲一区二区三区成人在线视频精品 | 欧美国产亚洲视频| 亚洲欧美区自拍先锋| 亚洲视频一区在线观看| 免费观看成人www动漫视频| 国产日韩欧美一二三区| 亚洲一级二级| 亚洲高清自拍| 久久久久91| 欧美国产日韩一区二区| 激情亚洲网站| 久久九九电影| 欧美aa在线视频| 欧美一级久久久久久久大片| 国产精品久久久久久久久久久久久久| 国产精品高清网站| 99精品视频免费全部在线| 欧美激情女人20p| 久久躁狠狠躁夜夜爽| 狠狠色丁香婷综合久久| 久久久99精品免费观看不卡| 亚洲欧美激情视频| 国产精品午夜视频| 性久久久久久久久| 美女视频一区免费观看| 久久成人资源| 韩国美女久久| 久久亚洲综合网| 久久久av毛片精品| 伊人久久婷婷色综合98网| 在线视频日本亚洲性| 亚洲黄色高清| 欧美精品在线播放| 国产在线观看精品一区二区三区| 欧美一区网站| 销魂美女一区二区三区视频在线| 国产一区二区0| 久久婷婷国产综合国色天香 | 久久亚洲精品伦理| 在线观看国产日韩| 欧美电影免费观看| 欧美成人综合在线| 在线中文字幕不卡| 亚洲午夜精品网| 国产农村妇女毛片精品久久麻豆| 久久av免费一区| 日韩视频精品| 久久精品国产亚洲一区二区| 激情综合视频| 欧美大片在线观看一区| 欧美69视频| 亚洲视频欧洲视频| 亚洲综合色在线| 国内精品久久久久影院色| 男人的天堂亚洲在线| 欧美激情va永久在线播放| 亚洲视频一二| 午夜精品一区二区三区电影天堂| 激情小说亚洲一区| 亚洲欧洲综合另类| 国产精品久久久久久久第一福利| 久久精品中文字幕免费mv| 免费观看日韩av| 亚洲一区激情| 亚洲精品专区| 欧美成人免费在线观看| 一区二区高清在线| 亚洲在线观看视频网站| 一区在线影院| 亚洲精品一区二区三区福利| 国产精品视屏| 欧美福利一区二区| 欧美性事免费在线观看| 99在线热播精品免费| 亚洲一区二区三区三| 黄色欧美日韩| 亚洲精选一区| 国内一区二区在线视频观看| 亚洲欧洲一区二区在线播放| 国产模特精品视频久久久久| 欧美激情视频网站| 国产伦精品一区二区三区照片91 | 性欧美video另类hd性玩具| 亚洲国产成人精品视频| 一个人看的www久久| 今天的高清视频免费播放成人| 亚洲精品一区二区三区四区高清 | 午夜精品美女久久久久av福利| 亚洲理论电影网| 国产视频一区欧美| 亚洲精品人人| 激情五月婷婷综合| 久久久久久欧美| 久久国产视频网| 国产精品99久久久久久人 | 久久久久一区二区| 在线精品国精品国产尤物884a| 一本色道久久综合亚洲精品小说|