• <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>

            colorful

            zc qq:1337220912

             

            關于DBSvr啟動時加載所有數據到內存

            關于DBSvr啟動時加載所有數據到內存
                最近在做這一塊,在網上討論了如何做一個高效的DBSvr, 有人提出這個想法,
                仔細想想有兩個問題,
                   數據加載到內存,以什么樣的形式存貯,像DB里的表一樣,還是按游戲邏輯來存,
                  如果像表一樣,那實現一個像DBMS一樣高效的查表功能應該是個問題。
                  如果按游戲邏輯來存, 那DBSvr又會跟游戲邏輯層扯上太多聯系。
                
            看來,讀所有數據到內存是不現實的。不知道各個有什么看法,請多多指教

            posted @ 2012-07-13 12:13 多彩人生 閱讀(233) | 評論 (0)編輯 收藏

            游戲服務器架構設計中的一些思考

            http://www.shnenglu.com/jaxe/archive/2010/04/22/113255.html

             

            1、 游戲世界由很多個游戲對象組成(游戲角色、物品、NPC、技能等);

             

            2、 一個游戲對象的有效數據主要存放在客戶端、游戲服務器和持久性數據庫中;

             

            3、 游戲對象的處理可劃分為與位置有關的和與位置無關的,如公會處理、物品處理等主要行為可以看作是與位置無關的處理,而NPC(AI)、戰斗、移動這類的主要行為可以看成是與位置有關的。

             

            4、 從客戶端的角度來看,游戲行為可分為四類動作:

            a)         來自服務器端的動作,如另外一個玩家跳起來。

            b)        本地動作。僅僅發生在本地客戶端的動作,不需要與服務器端或其他客戶端通訊。

            c)         先執行后驗證的可撤銷的動作。客戶端先執行,再提交服務器端驗證,驗證不成功通知客戶端將執行的動作撤銷。比如玩家控制的游戲角色執行移動處理。

            d)        嚴格服務器端驗證的動作。客戶端執行動作前必須經過服務器端驗證后才能執行。如交易行為、攻擊其他玩家/NPC。

             

            5、 客戶端和服務器,服務器進程之間的相互的通信從邏輯上看就是就是向RemoteObject 發起的遠程過程調用(RPC),RPC主要有兩種類型:

            a)         通知(Notify)。只通知對方,而不關心和需要對方返回結果。

            b)        請求(Request)。向對方發起請求,對方處理請求后返回結果,發起請求和返回結果這個過程可以是同步或異步。游戲服務器中絕大部分RPC請求都是異步的。

             

            6、 響應延遲主要是由于網絡帶寬和服務器處理效率引起的。應盡可能的通過一些技巧來隱藏和減少玩家的響應延遲。但不是所有的最新消息都能立刻發送出去(或接收處理到),因此,要在服務器端采用優先隊列來減少重要消息的響應時間。延遲也會由客戶端產生,如收到消息后的對消息的處理速度。

             

             

            7、 服務器負載,除了升級硬件設備外,可以通過一些方式來提高服務器負載。

             

            a)         保證足夠的網絡帶寬。

            b)        分布式運算,合理的集群式架構。

            c)         游戲策劃從游戲內容上避免設計高并發,高消耗的游戲行為。

             

             

             

            8、 從服務器的可伸縮性,穩定性和高效率方面來考慮,要試著避免所有事情都在一個地方處理,盡量讓系統分布式運行,但是過多的劃分功能到不同的進程/機器上運行,又會帶來數據的大量同步的問題。因此可以將游戲對象的處理主要劃分為與位置無關和有關兩種。像公會,玩家信息,物品信息,組隊,拍賣等等這類與位置無關的但是占用CPU資源較少的處理可以盡可能的放在一個進程中,避免進程間對象同步,而像NPC,尋路,AOI運算,戰斗處理等與位置有關的,處理過程中特別關心對象坐標位置的、運算量特別大的,但是進程間對象同步較少的,都可以單獨劃分成多個進程。

             

            每類進程服務的功能盡量單一。負責路由的就盡量只負責網絡包轉發,而不再承擔其他繁重的任務,負責游戲處理的就盡量讓網絡包流向簡單。


            大規模應用服務器(不只包含游戲服務器)是否成功主要看架構師對問題的解構能力。
            問題是什么?
            問題的邊界在哪里?
            功能粒度劃分多細?
            解決這些問題都需要經驗。

            posted @ 2012-07-12 16:06 多彩人生 閱讀(162) | 評論 (0)編輯 收藏

            stl之map erase方法的正確使用

            STL的map表里有一個erase方法用來從一個map中刪除掉指令的節點
            eg:
            map<string,string> mapTest;
            typedef map<string,string>::iterator ITER;

            ITER iter=mapTest.find(key);
            mapTest.erase(iter);

            像上面這樣只是刪除單個節點,map的形為不會出現任務問題,
            但是當在一個循環里用的時候,往往會被誤用,那是因為使用者沒有正確理解iterator的概念.
            像下面這樣的一個例子就是錯誤的寫法,
            eg.
            for(ITER iter=mapTest.begin();iter!=mapTest.end();++iter)
            {
            cout<<iter->first<<":"<<iter->second<<endl;
            mapTest.erase(iter);
            }

            這是一種錯誤的寫法,會導致程序行為不可知.究其原因是map 是關聯容器,對于關聯容器來說,如果某一個元素已經被刪除,那么其對應的迭代器就失效了,不應該再被使用;否則會導致程序無定義的行為。
            可以用以下方法解決這問題:
            正確的寫法
            1.使用刪除之前的迭代器定位下一個元素。STL建議的使用方式
            for(ITER iter=mapTest.begin();iter!=mapTest.end();)
            {
            cout<<iter->first<<":"<<iter->second<<endl;
            mapTest.erase(iter++);
            }

            2. erase() 成員函數返回下一個元素的迭代器
            for(ITER iter=mapTest.begin();iter!=mapTest.end();)
            {
            cout<<iter->first<<":"<<iter->second<<endl;
            iter=mapTest.erase(iter);
            }

            注意:

            map的earse應注意:
            map這種容器的下邊訪問和Vector等容器的下標訪問有本質的區別。
            對于Vector容器,用aVector[i]訪問第i個元素時,如果元素不存在,容器不會增加元素,
            而對于map,用aMap[key]
            訪問鍵key對應的對象時,如果該鍵對應的對象存在,則返回該對象(這和Vector一樣),但是,當鍵值為key的元素不存在時,容器會自動的增加一個 pair,鍵為key,而值則為一個容器定義時指定的類型并默認初始化(即,如果該類型為基本類型,則初始化為0,比如本例中,aMap[1]的使用會產 生一個pair,<1,NULL>,若該類型為類類型,則調用默認構造函數初始化之)

            顯然,本例中,aMap[1]為NULL,后面的erase()不會執行,使得后面的
            插入語句aMap.insert(1,new A())鍵值沖突

            eg:如下代碼會導致錯誤

            #include <iostream>
            #include <map>

            using namespace std;

            struct A
            {
            A(int i)
            {
            x=i;
            }
            int x;
            };

            int main()
            {
            map<int,A*> amap;
            if ( amap[1] != NULL )
            amap.erase(1);
            amap.insert(make_pair(1,new A(1)));
            amap.insert(make_pair(2,new A(2)));
            amap.insert(make_pair(3,new A(3)));
            return 0;
            }

            posted @ 2012-07-06 10:20 多彩人生 閱讀(243) | 評論 (0)編輯 收藏

            c++中的.hpp文件

              hpp,其實質就是將.cpp的實現代碼混入.h頭文件當中,定義與實現都包含在同一文件,則該類的調用者只需要include該cpp文件即可,無需再 將cpp加入到project中進行編譯。而實現代碼將直接編譯到調用者的obj文件中,不再生成單獨的obj,采用hpp將大幅度減少調用 project中的cpp文件數與編譯次數,也不用再發布煩人的lib與dll,因此非常適合用來編寫公用的開源庫。

            1、是Header Plus Plus 的簡寫。

            2、與*.h類似,hpp是C++程序頭文件 。

            3、是VCL 專用的頭文件,已預編譯。

            4、是一般模板類的頭文件。

            5、一般來說,*.h里面只有聲明,沒有實現,而*.hpp里聲明實現都有,后者可以減 少.cpp的數量。

            6、*.h里面可以有using namespace std,而*.hpp里則無。

            7、*.hpp要注意的問題有:

                  a)不可包含全局對象和全局函數

                 由于hpp本質上是作為.h被調用者include,所以當hpp文件中存在全局對象或者全局函數,而該hpp被多個

                調用者include時,將在鏈接時導致符號重定義錯誤。要避免這種情況,需要去除全局對象,將全局函數封

                裝為類的靜態方法。

                  b)類之間不可循環調用

                  在.h和.cpp的場景中,當兩個類或者多個類之間有循環調用關系時,只要預先在頭文件做被調用類的聲明

                即可,如下:

                class B;

                class A{

                public:

                     void someMethod(B b);

                };

                class B{

                public:

                     void someMethod(A a);

                };

                在hpp場景中,由于定義與實現都已經存在于一個文件,調用者必需明確知道被調用者的所有定義,而不能等到cpp

                中去編譯。因此hpp中必須整理類之間調用關系,不可產生循環調用。同理,對于當兩個類A和B分別定義在各自的

                hpp文件中,形如以下的循環調用也將導致編譯錯誤:

                //a.hpp

                #include "b.hpp"

                class A{

                public:

                    void someMethod(B b);

                };

                //b.hpp

                #include "a.hpp"

                class B{

                public:

                    void someMethod(A a);

                }

                  c)不可使用靜態成員

                  靜態成員的使用限制在于如果類含有靜態成員,則在hpp中必需加入靜態成員初始化代碼,當該hpp被多個文檔include時,將產生符號重定義錯誤。唯 一的例外是const static整型成員,因為在vs2003中,該類型允許在定義時初始化,如:

                class A{

                 public:

                   const static int intValue = 123;

                 };

                由于靜態成員的使用是很常見的場景,無法強制清除,因此可以考慮以下幾種方式(以下示例均為同一類中方法)

               一、類中僅有一個靜態成員時,且僅有一個調用者時,可以通過局域靜態變量模擬

                //方法模擬獲取靜態成員

                someType getMember()

                {

                   static someType value(xxx);//作用域內靜態變量

                   return value;

                }

               二、.類中有多個方法需要調用靜態成員,而且可能存在多個靜態成員時,可以將每個靜態成員封裝一個模擬方法,供其他方法調用。

                someType getMemberA()

                {

                   static someType value(xxx);//作用域內靜態變量

                   return value;

                }

                someType getMemberB()

                {

                   static someType value(xxx);//作用域內靜態變量

                   return value;

                }

               void accessMemberA()

                {

                   someType member = getMemberA();//獲取靜態成員

                 };

                //獲取兩個靜態成員

                void accessStaticMember()

                {

                   someType a = getMemberA();//獲取靜態成員

                   someType b = getMemberB();

                 };

                三、第二種方法對于大部分情況是通用的,但是當所需的靜態成員過多時,編寫封裝方法的工作量將非常

                巨大,在此種情況下,建議使用Singleton模式,將被調用類定義成普通類,然后使用Singleton將其變為

               全局唯一的對象進行調用。

                 如原h+cpp下的定義如下:

                 class A{

                 public:

                    type getMember(){

                       return member;

                    }

                    static type member;//靜態成員

                }

                采用singleton方式,實現代碼可能如下(singleton實現請自行查閱相關文檔)

                //實際實現類

                 class Aprovider{

                 public:

                    type getMember(){

                       return member;

                    }

                   type member;//變為普通成員

                }

                //提供給調用者的接口類

                 class A{

                 public:

                    type getMember(){

                       return Singleton<AProvider>::getInstance()->getMember();

                    }

                }

            posted @ 2012-07-03 17:47 多彩人生 閱讀(324) | 評論 (0)編輯 收藏

            剖析為什么在多核多線程程序中要慎用volatile關鍵字?

            這篇文章詳細剖析了為什么在多核時代進行多線程編程時需要慎用volatile關鍵字。

            主要內容有:
            1. C/C++中的volatile關鍵字
            2. Visual Studio對C/C++中volatile關鍵字的擴展
            3. Java/.NET中的volatile關鍵字
            4. Memory Model(內存模型)
            5. Volatile使用建議

            1. C/C++中的volatile關鍵字

            1.1 傳統用途

            C/C++作為系統級語言,它們與硬件的聯系是很緊密的。volatile的意思是“易變的”,這個關鍵字最早就是為了針對那些“異常”的內存操作 而準備的。它的效果是讓編譯器不要對這個變量的讀寫操作做任何優化,每次讀的時候都直接去該變量的內存地址中去讀,每次寫的時候都直接寫到該變量的內存地 址中去,即不做任何緩存優化。它經常用在需要處理中斷的嵌入式系統中,其典型的應用有下面幾種:

            a. 避免用通用寄存器對內存讀寫的優化。編譯器常做的一種優化就是:把常用變量的頻繁讀寫弄到通用寄存器中,最后不用的時候再存回內存中。但是如果某個內存地址中的值是由片外決定的(例如另一個線程或是另一個設備可能更改它),那就需要volatile關鍵字了。(感謝Kenny老師指正)
            b. 硬件寄存器可能被其他設備改變的情況。例如一個嵌入式板子上的某個寄存器直接與一個測試儀器連在一起,這樣在這個寄存器的值隨時可能被那個測試儀器更改。 在這種情況下如果把該值設為volatile屬性的,那么編譯器就會每次都直接從內存中去取這個值的最新值,而不是自作聰明的把這個值保留在緩存中而導致 讀不到最新的那個被其他設備寫入的新值。
            c. 同一個物理內存地址M有兩個不同的內存地址的情況。例如兩個程序同時對同一個物理地址進行讀寫,那么編譯器就不能假設這個地址只會有一個程序訪問而做緩存優化,所以程序員在這種情況下也需要把它定義為volatile的。

            1.2 多線程程序中的錯誤用法

            看到這里,很多朋友自然會想到:恩,那么如果是兩個線程需要同時訪問一個共享變量,為了讓其中兩個線程每次都能讀到這個變量的最新值,我們就把它定 義為volatile的就好了嘛!我想這個就是多線程程序中volatile之所以引起那么多爭議的最大原因。可惜的是,這個想法是錯誤的。

            舉例來說,想用volatile變量來做同步(例如一個flag)?錯!為什么?很簡單,雖然volatile意味著每次讀和寫都是直接去內存地址中去操作,但是volatile在C/C++現有標準中即不能保證原子性(Atomicity)也不能保證順序性(Ordering),所以幾乎所有試圖用volatile來進行多線程同步的方案都是錯的。我之前一篇文章介紹了Sequential Consistency模型(后 面簡稱SC),它其實就是我們印象中多線程程序應該有的執行順序。但是,SC最大的問題是性能太低了,因為CPU/編譯器完全沒有必要嚴格按代碼規定的順 序(program order)來執行每一條指令。學過體系結構的同學應該知道不管是編譯器也好CPU也好,他們最擅長做的事情就是幫你做亂序優化。在串行時代這些亂序優化 對程序員來說都是透明的,封裝好了的,你不用關心它們到底給你亂序成啥樣了,因為它們會保證優化后的程序的運行結果跟你寫程序時預期的結果是一模一樣的。 但是進入多核時代之后,CPU和編譯器還會繼續做那些串行時代的優化,更重要的是這些優化還會打破你多線程程序的SC模型語義,從而使得多線程程序的實際 運行結果與我們所期待的運行結果不一致!

            拿X86來說,它的多核內存模型沒有嚴格執行SC,即屬于weak ordering(或者叫relax ordering?)。它唯一允許的亂序優化是可以把對不同地址的load操作提到store之前去(即把store x->load y亂序優化成load y -> store x)。而store x -> store y、load x -> load y,以及store x -> load y不允許交換執行順序。在X86這樣的內存模型下,volatile關鍵字根本就不能保證對不同volatile變量x和y的store x -> load y的操作不會被CPU亂序優化成load y -> store x。

            而對多線程讀寫操作的原子性來說,諸如volatile x=1這樣的寫操作的原子性其實是由X86硬件保證的,跟volatile沒有任何關系。事實上,volatile根本不能保證對沒有內存對齊的變量(或者超出機器字長的變量)的讀寫操作的原子性。

            為了有個更直觀的理解,我們來看看CPU的亂序優化是如何讓volatile在多線程程序中顯得如此無力的。下面這個著名的Dekker算法是想用 flag1/2和turn來實現兩個線程情況下的臨界區互斥訪問。這個算法關鍵就在于對flag1/2和turn的讀操作(load)是在其寫操作 (store)之后的,因此這個多線程算法能保證dekker1和dekker2中對gSharedCounter++的操作是互斥的,即等于是把 gSharedCounter++放到臨界區里去了。但是,多核X86可能會對這個store->load操作做亂序優化,例如dekker1中對 flag2的讀操作可能會被提到對flag1和turn的寫操作之前,這樣就會最終導致臨界區的互斥訪問失效,而gSharedCounter++也會因 此產生data race從而出現錯誤的計算結果。那么為什么多核CPU會對多線程程序做這樣的亂序優化呢?因為從單線程的視角來看flag2和flag1、turn是沒 有依賴關系的,所以CPU當然可以對他們進行亂序優化以便充分利用好CPU里面的流水線(想了解更多細節請參考計算機體系結構相關書籍)。這樣的優化雖然 從單線程角度來講沒有錯,但是它卻違反了我們設計這個多線程算法時所期望的那個多線程語義。(想要解決這個bug就需要自己手動添加memory barrier,或者干脆別去實現這樣的算法,而是使用類似pthread_mutex_lock這樣的庫函數,后面我會再講到這點)

            當然,對不同的CPU來說他們的內存模型是不同的。比如說,如果這個程序是在單核上以多線程的方式執行那么它肯定不會出錯,因為單核CPU的內存模 型是符合SC的。而在例如PowerPC,ARM之類的架構上運行結果到底如何就得去翻它們的硬件手冊中內存模型是怎么定義的了。

            2. Visual Studio對C/C++中volatile關鍵字的擴展

            雖然C/C++中的volatile關鍵字沒有對ordering做任何保證,但是微軟從Visual Studio 2005開始就對volatile關鍵字添加了同步語義(保證ordering),即:對volatile變量的讀操作具有acquire語義,對 volatile變量的寫操作具有release語義。Acquire和Release語義是來自data-race-free模型的概念。為了理解這個 acquire語義和release語義有什么作用,我們來看看MSDN中的一個例子

            例子中的 while (Sentinel) Sleep(0); // volatile spin lock 是對volatile變量的讀操作,它具有acquire語義,acquire語義的隱義是當前線程在對sentinel的這個讀操作之后的所有的對全局 變量的訪問都必須在該操作之后執行;同理,例子中的Sentinel = false; // exit critical section 是對volatile變量的寫操作,它具有release語義,release語義的隱義是當前線程在對sentinel這個寫操作之前的所有對全局變量 的訪問都必須在該操作之前執行完畢。所以ThreadFunc1()讀CriticalData時必定已經在ThreadFunc2()執行完 CriticalData++之后,即CriticalData最后輸出的值必定為1。建議大家用紙畫一下acquire/release來加深理解。一個比較形象的解釋就是把acquire當成lock,把release當成unlock,它倆組成了一個臨界區,所有臨界區外面的操作都只能往這個里面移,但是臨界區里面的操作都不能往外移,簡單吧?

            其實這個程序就相當于用volatile變量的acquire和release語義實現了一個臨界區,在臨界區內部的代碼就是 Sleep(2000); CriticalData++; 或者更貼切點也可以看成是一對pthread_cond_wait和pthread_cond_signal。

            這個volatile的acquire和release語義是VS自己的擴展,C/C++標準里是沒有的,所以同樣的代碼用gcc編譯執行結果就可 能是錯的,因為編譯器/CPU可能做違反正確性的亂序優化。Acquire和release語義本質上就是為了保證程序執行時memory order的正確性。但是,雖然這個VS擴展使得volatile變量能保證ordering,它還是不能保證對volatile變量讀寫的原子性。事 實上,如果我們的程序是跑在X86上面的話,內存對齊了的變量的讀寫的原子性是由硬件保證的,跟volatile沒有任何關系。而像volatile g_nCnt++這樣的語句本身就不是原子操作,想要保證這個操作是原子的,就必須使用帶LOCK語義的++操作,具體請看我這篇文章

            另外,VS生成的volatile變量的匯編代碼是否真的調用了memory barrier也得看具體的硬件平臺,例如x86上就不需要使用memory barrier也能保證acquire和release語義,因為X86硬件本身就有比較強的memory模型了,但是Itanium上面VS就會生成帶 memory barrier的匯編代碼。具體可以參考這篇

            但是,雖然VS對volatile關鍵字加入了acquire/release語義,有一種情況還是會出錯,即我們之前看到的dekker算法的例子。這 個其實蠻好理解的,因為讀操作的acquire語義不允許在其之后的操作往前移,但是允許在其之前的操作往后移;同理,寫操作的release語義允許在 其之后的操作往前移,但是不允許在其之前的操作往后移;這樣的話對一個volatile變量的讀操作(acquire)當然可以放到對另一個 volatile變量的寫操作(release)之前了!Bug就是這樣產生的!下面這個程序大家拿Visual Studio跑一下就會發現bug了(我試了VS2008和VS2010,都有這個bug)。多線程編程復雜吧?希望大家還沒被弄暈,要是暈了的話也很正 常,仔仔細細重新再看一遍吧:)

            想解決這個Bug也很簡單,直接在dekker1和dekker2中對flag1/flag2/turn賦值操作之后都分別加入full memory barrier就可以了,即保證load一定是在store之后執行即可。具體的我就不詳述了。

            3. Java/.NET中的volatile關鍵字

            3.1 多線程語義

            Java和.NET分別有JVM和CLR這樣的虛擬機,保證多線程的語義就容易多了。說簡單點,Java和.NET中的volatile關鍵字也是限制虛擬機做優化,都具有acquire和release語義,而且由虛擬機直接保證了對volatile變量讀寫操作的原子性。 (volatile 只保證可見性,不保證原子性。java中,對volatile修飾的long和double的讀寫就不是原子的 (http://java.sun.com/docs/books/jvms/second_edition/html /Threads.doc.html#22244),除此之外的基本類型和引用類型都是原子的。– 多謝liuchangit指正) 這 里需要注意的一點是,Java和.NET里面的volatile沒有對應于我們最開始提到的C/C++中對“異常操作”用volatile修飾的傳統用 法。原因很簡單,Java和.NET的虛擬機對安全性的要求比C/C++高多了,它們才不允許不安全的“異常”訪問存在呢。

            而且像JVM/.NET這樣的程序可移植性都非常好。雖然現在C++1x正在把多線程模型添加到標準中去,但是因為C++本身的性質導致它的硬件平 臺依賴性很高,可移植性不是特別好,所以在移植C/C++多線程程序時理解硬件平臺的內存模型是非常重要的一件事情,它直接決定你這個程序是否會正確執 行。

            至于Java和.NET中是否也存在類似VS 2005那樣的bug我沒時間去測試,道理其實是相同的,真有需要的同學自己應該能測出來。好像這篇InfoQ的文章中顯示Java運行這個dekker算法沒有問題,因為JVM給它添加了mfence。另一個臭名昭著的例子就應該是Double-Checked Locking了。

            3.2 volatile int與AtomicInteger區別

            Java和.NET中這兩者還是有些區別的,主要就是后者提供了類似incrementAndGet()這樣的方法可以直接調用(保證了原子性), 而如果是volatile x進行++操作則不是原子的。increaseAndGet()的實現調用了類似CAS這樣的原子指令,所以能保證原子性,同時又不會像使用 synchronized關鍵字一樣損失很多性能,用來做全局計數器非常合適。

            4. Memory Model(內存模型)

            說了這么多,還是順帶介紹一下Memory Model吧。就像前面說的,CPU硬件有它自己的內存模型,不同的編程語言也有它自己的內存模型。如果用一句話來介紹什么是內存模型,我會說它就是程序 員,編程語言和硬件之間的一個契約,它保證了共享的內存地址里的值在需要的時候是可見的。下次我會專門詳細寫一篇關于它的內容。它最大的作用是取得可編程 性與性能優化之間的一個平衡。

            5. volatile使用建議

            總的來說,volatile關鍵字有兩種用途:一個是ISO C/C++中用來處理“異常”內存行為(此用途只保證不讓編譯器做任何優化,對多核CPU是否會進行亂序優化沒有任何約束力),另一種是在Java /.NET(包括Visual Studio添加的擴展)中用來實現高性能并行算法(此種用途通過使用memory barrier保證了CPU/編譯器的ordering,以及通過JVM或者CLR保證了對該volatile變量讀寫操作的原子性)。

            一句話,volatile對多線程編程是非常危險的,使用的時候千萬要小心你的代碼在多核上到底是不是按你所想的方式執行的,特別是對現在暫時還沒 有引入內存模型的C/C++程序更是如此。安全起見,大家還是用Pthreads,Java.util.concurrent,TBB等并行庫提供的 lock/spinlock,conditional variable, barrier, Atomic Variable之類的同步方法來干活的好,因為它們的內部實現都調用了相應的memory barrier來保證memory ordering,你只要保證你的多線程程序沒有data race,那么它們就能幫你保證你的程序是正確的(是的,Pthreads庫也是有它自己的內存模型的,只不過它的內存模型還些缺點,所以把多線程內存模 型直接集成到C/C++中是更好的辦法,也是將來的趨勢,但是C++1x中將不會像Java/.NET一樣給volatile關鍵字添加acquire和 release語義,而是轉而提供另一種具有同步語義的atomic variables,此為后話)。如果你想實現更高性能的lock free算法,或是使用volatile來進行同步,那么你就需要先把CPU和編程語言的memory model搞清楚,然后再時刻注意Atomicity和Ordering是否被保證了。(注 意,用沒有acquire/release語義的volatile變量來進行同步是錯誤的,但是你仍然可以在C/C++中用volatile來修飾一個不 是用來做同步(例如一個event flag)而只是被不同線程讀寫的共享變量,只不過它的新值什么時候能被另一個線程讀到是沒有保證的,需要你自己做相應的處理)

            Herb Sutter 在他的那篇volatile vs. volatile中對這兩種用法做了很仔細的區分,我把其中兩張表格鏈接貼過來供大家參考:

            volatile的兩種用途
            volatile兩種用途的異同

            最后附上《Java Concurrency in Practice》3.1.4節中對Java語言的volatile關鍵字的使用建議(不要被英語嚇到,這些內容確實對你有用,而且還能順便幫練練英語,哈哈):

            So from a memory visibility perspective, writing a volatile variable is like exiting a synchronized block and reading a volatile variable is like entering a synchronized block. However, we do not recommend relying too heavily on volatile variables for visibility; code that relies on volatile variables for visibility of arbitrary state is more fragile and harder to understand than code that uses locking.

            Use volatile variables only when they simplify implementing and verifying your synchronization policy; avoid using volatile variables when veryfing correctness would require subtle reasoning about visibility. Good uses of volatile variables include ensuring the visibility of their own state, that of the object they refer to, or indicating that an important lifecycle event (such as initialization or shutdown) has occurred.

            Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility.

            You can use volatile variables only when all the following criteria are met:
            (1) Writes to the variable do not depend on its current value, or you can ensure that only a single thread ever updates the value;
            (2) The variable does not participate in invariants with other state variables; and
            (3) Locking is not required for any other reason while the variable is being accessed.

            參考資料

            1. 《Java Concurrency in Practice》3.1.4節
            2. volatile vs. volatile(Herb Sutter對volatile的闡述,必看)
            3. The “Double-Checked Locking is Broken” Declaration
            4. Threading in C#
            5. Volatile: Almost Useless for Multi-Threaded Programming
            6. Memory Ordering in Modern Microprocessors
            7. Memory Ordering @ Wikipedia
            8. 內存屏障什么的
            9. The memory model of x86
            10. VC 下 volatile 變量能否建立 Memory Barrier 或并發鎖
            11. Sayonara volatile(Concurrent Programming on Windows作者的文章 跟我觀點幾乎一致)
            12. Java 理論與實踐: 正確使用 Volatile 變量
            13. Java中的Volatile關鍵字

             

            轉自:http://www.parallellabs.com/2010/12/04/why-should-we-be-care-of-volatile-keyword-in-multithreaded-applications/

            posted @ 2012-07-02 17:03 多彩人生 閱讀(455) | 評論 (0)編輯 收藏

            關于c++異常 和錯誤

            以前寫程序一般是通過返回錯誤代碼或者設置錯誤標志位為實現,但是這里有個問題,就是你不能保證用戶會去檢驗這個返回代碼或者錯誤標志位,這樣的話程序出 錯了還繼續運行,最終是離出錯的地方越來越遠。而異常其實就是一個錯誤信息,如果有異常,而該異常沒有被任何程序捕捉的話,程序就會中斷。可以更好的讓客 戶診斷錯誤。

            我想以上只不過是使用異常的一個原因。應該還有其他吧,歡迎ding   。
            ///////////////////////////////////////////////////////////////////////////////////////
            異常有時并不是由于你的程序的問題引起的,如用new   申請一個內存塊失敗時,數據庫操作失敗時,就會產生一個異常,異常往往是程序的執行過程中不可預料的。如果不對產生的異常進行處理,程序往往崩潰,會使軟件顯得很脆弱。

            錯誤而言,不管是語法錯誤,邏輯錯誤都是可以通過檢查發現的。
            /////////////////////////////////////////////////////////////////////////////////////////////

            posted @ 2012-06-30 12:01 多彩人生 閱讀(252) | 評論 (0)編輯 收藏

            c++文件操作

            C++ 通過以下幾個類支持文件的輸入輸出:

            • ofstream: 寫操作(輸出)的文件類 (由ostream引申而來)
            • ifstream: 讀操作(輸入)的文件類(由istream引申而來)
            • fstream: 可同時讀寫操作的文件類 (由iostream引申而來)

            打開文件(Open a file)

            對這些類的一個對象所做的第一個操作通常就是將它和一個真正的文件聯系起來,也就是說打開一個文件。被打開的文件在程序中由一個流對象(stream object)來表示 (這些類的一個實例) ,而對這個流對象所做的任何輸入輸出操作實際就是對該文件所做的操作。

            要通過一個流對象打開一個文件,我們使用它的成員函數open():

            void open (const char * filename, openmode mode);

            這里filename 是一個字符串,代表要打開的文件名,mode 是以下標志符的一個組合:

            ios::in 為輸入(讀)而打開文件
            ios::out 為輸出(寫)而打開文件
            ios::ate 初始位置:文件尾
            ios::app 所有輸出附加在文件末尾
            ios::trunc 如果文件已存在則先刪除該文件
            ios::binary 二進制方式

            這些標識符可以被組合使用,中間以”或”操作符(|)間隔。例如,如果我們想要以二進制方式打開文件"example.bin" 來寫入一些數據,我們可以通過以下方式調用成員函數open()來實現:

            ofstream file;
            file.open ("example.bin", ios::out | ios::app | ios::binary);

            ofstream, ifstream 和 fstream所有這些類的成員函數open 都包含了一個默認打開文件的方式,這三個類的默認方式各不相同:

            參數的默認方式
            ofstream ios::out | ios::trunc
            ifstream ios::in
            fstream ios::in | ios::out

            只有當函數被調用時沒有聲明方式參數的情況下,默認值才會被采用。如果函數被調用時聲明了任何參數,默認值將被完全改寫,而不會與調用參數組合。

            由于對類ofstream, ifstream 和 fstream 的對象所進行的第一個操作通常都是打開文件,這些類都有一個構造函數可以直接調用open 函數,并擁有同樣的參數。這樣,我們就可以通過以下方式進行與上面同樣的定義對象和打開文件的操作:

            ofstream file ("example.bin", ios::out | ios::app | ios::binary);

            兩種打開文件的方式都是正確的。

            你可以通過調用成員函數is_open()來檢查一個文件是否已經被順利的打開了:

            bool is_open();

            它返回一個布爾(bool)值,為真(true)代表文件已經被順利打開,假( false )則相反。


            關閉文件(Closing a file)

            當文件讀寫操作完成之后,我們必須將文件關閉以使文件重新變為可訪問的。關閉文件需要調用成員函數close(),它負責將緩存中的數據排放出來并關閉文件。它的格式很簡單:

            void close ();

            這個函數一旦被調用,原先的流對象(stream object)就可以被用來打開其它的文件了,這個文件也就可以重新被其它的進程(process)所有訪問了。

            為防止流對象被銷毀時還聯系著打開的文件,析構函數(destructor)將會自動調用關閉函數close。


            文本文件(Text mode files)

            類ofstream, ifstream 和fstream 是分別從ostream, istream 和iostream 中引申而來的。這就是為什么 fstream 的對象可以使用其父類的成員來訪問數據。

            一般來說,我們將使用這些類與同控制臺(console)交互同樣的成員函數(cin 和 cout)來進行輸入輸出。如下面的例題所示,我們使用重載的插入操作符<<:

                // writing on a text file
            #include <fiostream.h>

            int main () {
            ofstream examplefile ("example.txt");
            if (examplefile.is_open()) {
            examplefile << "This is a line.\n";
            examplefile << "This is another line.\n";
            examplefile.close();
            }
            return 0;
            }
            file example.txt
            This is a line.
            This is another line.

            從文件中讀入數據也可以用與 cin的使用同樣的方法:

                // reading a text file
            #include <iostream.h>
            #include <fstream.h>
            #include <stdlib.h>

            int main () {
            char buffer[256];
            ifstream examplefile ("example.txt");
            if (! examplefile.is_open())
            { cout << "Error opening file"; exit (1); }
            while (! examplefile.eof() ) {
            examplefile.getline (buffer,100);
            cout << buffer << endl;
            }
            return 0;
            }
            This is a line.
            This is another line.

            上面的例子讀入一個文本文件的內容,然后將它打印到屏幕上。注意我們使用了一個新的成員函數叫做eof ,它是ifstream 從類 ios 中繼承過來的,當到達文件末尾時返回true 。


            狀態標志符的驗證(Verification of state flags)

            除了eof()以外,還有一些驗證流的狀態的成員函數(所有都返回bool型返回值):

            • bad()

              如果在讀寫過程中出錯,返回 true 。例如:當我們要對一個不是打開為寫狀態的文件進行寫入時,或者我們要寫入的設備沒有剩余空間的時候。

            • fail()

              除了與bad() 同樣的情況下會返回 true 以外,加上格式錯誤時也返回true ,例如當想要讀入一個整數,而獲得了一個字母的時候。

            • eof()

              如果讀文件到達文件末尾,返回true。

            • good()

              這是最通用的:如果調用以上任何一個函數返回true 的話,此函數返回 false 。

            要想重置以上成員函數所檢查的狀態標志,你可以使用成員函數clear(),沒有參數。


            獲得和設置流指針(get and put stream pointers)

            所有輸入/輸出流對象(i/o streams objects)都有至少一個流指針:

            • ifstream, 類似istream, 有一個被稱為get pointer的指針,指向下一個將被讀取的元素。
            • ofstream, 類似 ostream, 有一個指針 put pointer ,指向寫入下一個元素的位置。
            • fstream, 類似 iostream, 同時繼承了get 和 put

            我們可以通過使用以下成員函數來讀出或配置這些指向流中讀寫位置的流指針:

            • tellg() 和 tellp()

              這兩個成員函數不用傳入參數,返回pos_type 類型的值(根據ANSI-C++ 標準) ,就是一個整數,代表當前get 流指針的位置 (用tellg) 或 put 流指針的位置(用tellp).

            • seekg() 和seekp()

              這對函數分別用來改變流指針get 和put的位置。兩個函數都被重載為兩種不同的原型:

              seekg ( pos_type position );
              seekp ( pos_type position );

              使用這個原型,流指針被改變為指向從文件開始計算的一個絕對位置。要求傳入的參數類型與函數 tellg 和tellp 的返回值類型相同。

              seekg ( off_type offset, seekdir direction );
              seekp ( off_type offset, seekdir direction );

              使用這個原型可以指定由參數direction決定的一個具體的指針開始計算的一個位移(offset)。它可以是:

              ios::beg 從流開始位置計算的位移
              ios::cur 從流指針當前位置開始計算的位移
              ios::end 從流末尾處開始計算的位移

            流指針 get 和 put 的值對文本文件(text file)和二進制文件(binary file)的計算方法都是不同的,因為文本模式的文件中某些特殊字符可能被修改。由于這個原因,建議對以文本文件模式打開的文件總是使用seekg 和 seekp的第一種原型,而且不要對tellg 或 tellp 的返回值進行修改。對二進制文件,你可以任意使用這些函數,應該不會有任何意外的行為產生。

            以下例子使用這些函數來獲得一個二進制文件的大小:

                // obtaining file size
            #include <iostream.h>
            #include <fstream.h>

            const char * filename = "example.txt";

            int main () {
            long l,m;
            ifstream file (filename, ios::in|ios::binary);
            l = file.tellg();
            file.seekg (0, ios::end);
            m = file.tellg();
            file.close();
            cout << "size of " << filename;
            cout << " is " << (m-l) << " bytes.\n";
            return 0;
            }
            size of example.txt is 40 bytes.

            二進制文件(Binary files)

            在二進制文件中,使用<< 和>>,以及函數(如getline)來操作符輸入和輸出數據,沒有什么實際意義,雖然它們是符合語法的。

            文件流包括兩個為順序讀寫數據特殊設計的成員函數:write 和 read。第一個函數 (write) 是ostream 的一個成員函數,都是被ofstream所繼承。而read 是istream 的一個成員函數,被ifstream 所繼承。類 fstream 的對象同時擁有這兩個函數。它們的原型是:

            write ( char * buffer, streamsize size );
            read ( char * buffer, streamsize size );

            這里 buffer 是一塊內存的地址,用來存儲或讀出數據。參數size 是一個整數值,表示要從緩存(buffer)中讀出或寫入的字符數。

                // reading binary file
            #include <iostream>
            #include <fstream.h>

            const char * filename = "example.txt";

            int main () {
            char * buffer;
            long size;
            ifstream file (filename, ios::in|ios::binary|ios::ate);
            size = file.tellg();
            file.seekg (0, ios::beg);
            buffer = new char [size];
            file.read (buffer, size);
            file.close();

            cout << "the complete file is in a buffer";

            delete[] buffer;
            return 0;
            }
            The complete file is in a buffer

            緩存和同步(Buffers and Synchronization)

            當我們對文件流進行操作的時候,它們與一個streambuf 類型的緩存(buffer)聯系在一起。這個緩存(buffer)實際是一塊內存空間,作為流(stream)和物理文件的媒介。例如,對于一個輸出流, 每次成員函數put (寫一個單個字符)被調用,這個字符不是直接被寫入該輸出流所對應的物理文件中的,而是首先被插入到該流的緩存(buffer)中。

            當緩存被排放出來(flush)時,它里面的所有數據或者被寫入物理媒質中(如果是一個輸出流的話),或者簡單的被抹掉(如果是一個輸入流的話)。這個過程稱為同步(synchronization),它會在以下任一情況下發生:

            • 當文件被關閉時: 在文件被關閉之前,所有還沒有被完全寫出或讀取的緩存都將被同步。
            • 當緩存buffer 滿時:緩存Buffers 有一定的空間限制。當緩存滿時,它會被自動同步。
            • 控制符明確指明:當遇到流中某些特定的控制符時,同步會發生。這些控制符包括:flush 和endl。
            • 明確調用函數sync(): 調用成員函數sync() (無參數)可以引發立即同步。這個函數返回一個int 值,等于-1 表示流沒有聯系的緩存或操作失敗。
            • 在C++中,有一個stream這個類,所有的I/O都以這個“流”類為基礎的,包括我們要認識的文件I/O,stream這個類有兩個重要的運算符:

              1、插入器(<<)
              向流輸出數據。比如說系統有一個默認的標準輸出流(cout),一般情況下就是指的顯示器,所以,cout<<"Write Stdout"<<'n';就表示把字符串"Write Stdout"和換行字符('n')輸出到標準輸出流。

              2、析取器(>>)
              從流中輸入數據。比如說系統有一個默認的標準輸入流(cin),一般情況下就是指的鍵盤,所以,cin>>x;就表示從標準輸入流中讀取一個指定類型(即變量x的類型)的數據。

              在C++中,對文件的操作是通過stream的子類fstream(file stream)來實現的,所以,要用這種方式操作文件,就必須加入頭文件fstream.h。下面就把此類的文件操作過程一一道來。

              一、打開文件
              在fstream類中,有一個成員函數open(),就是用來打開文件的,其原型是:

              void open(const char* filename,int mode,int access);

              參數:

              filename: 要打開的文件名
              mode: 要打開文件的方式
              access: 打開文件的屬性
              打開文件的方式在類ios(是所有流式I/O類的基類)中定義,常用的值如下:

              ios::app: 以追加的方式打開文件
              ios::ate: 文件打開后定位到文件尾,ios:app就包含有此屬性
              ios::binary: 以二進制方式打開文件,缺省的方式是文本方式。兩種方式的區別見前文
              ios::in: 文件以輸入方式打開
              ios::out: 文件以輸出方式打開
              ios::nocreate: 不建立文件,所以文件不存在時打開失敗
              ios::noreplace:不覆蓋文件,所以打開文件時如果文件存在失敗
              ios::trunc: 如果文件存在,把文件長度設為0
              可以用“或”把以上屬性連接起來,如ios::out|ios::binary

              打開文件的屬性取值是:

              0:普通文件,打開訪問
              1:只讀文件
              2:隱含文件
              4:系統文件
              可以用“或”或者“+”把以上屬性連接起來 ,如3或1|2就是以只讀和隱含屬性打開文件。

              例如:以二進制輸入方式打開文件c:config.sys

              fstream file1;
              file1.open("c:config.sys",ios::binary|ios::in,0);

              如果open函數只有文件名一個參數,則是以讀/寫普通文件打開,即:

              file1.open("c:config.sys");<=>file1.open("c:config.sys",ios::in|ios::out,0);

              另外,fstream還有和open()一樣的構造函數,對于上例,在定義的時侯就可以打開文件了:

              fstream file1("c:config.sys");

              特別提出的是,fstream有兩個子類:ifstream(input file stream)和ofstream(outpu file stream),ifstream默認以輸入方式打開文件,而ofstream默認以輸出方式打開文件。

              ifstream file2("c:pdos.def");//以輸入方式打開文件
              ofstream file3("c:x.123");//以輸出方式打開文件

              所以,在實際應用中,根據需要的不同,選擇不同的類來定義:如果想以輸入方式打開,就用ifstream來定義;如果想以輸出方式打開,就用ofstream來定義;如果想以輸入/輸出方式來打開,就用fstream來定義。

              二、關閉文件
              打開的文件使用完成后一定要關閉,fstream提供了成員函數close()來完成此操作,如:file1.close();就把file1相連的文件關閉。

              三、讀寫文件
              讀寫文件分為文本文件和二進制文件的讀取,對于文本文件的讀取比較簡單,用插入器和析取器就可以了;而對于二進制的讀取就要復雜些,下要就詳細的介紹這兩種方式

              1、文本文件的讀寫
              文本文件的讀寫很簡單:用插入器(<<)向文件輸出;用析取器(>>)從文件輸入。假設file1是以輸入方式打開,file2以輸出打開。示例如下:

              file2<<"I Love You";//向文件寫入字符串"I Love You"
              int i;
              file1>>i;//從文件輸入一個整數值。

              這種方式還有一種簡單的格式化能力,比如可以指定輸出為16進制等等,具體的格式有以下一些

              操縱符 功能 輸入/輸出
              dec 格式化為十進制數值數據 輸入和輸出
              endl 輸出一個換行符并刷新此流 輸出
              ends 輸出一個空字符 輸出
              hex 格式化為十六進制數值數據 輸入和輸出
              oct 格式化為八進制數值數據 輸入和輸出
              setpxecision(int p) 設置浮點數的精度位數 輸出

              比如要把123當作十六進制輸出:file1<<hex<<123;要把3.1415926以5位精度輸出:file1<<setpxecision(5)<<3.1415926。

              2、二進制文件的讀寫
              ①put()
              put()函數向流寫入一個字符,其原型是ofstream &put(char ch),使用也比較簡單,如file1.put('c');就是向流寫一個字符'c'。

              ②get()
              get()函數比較靈活,有3種常用的重載形式:

              一種就是和put()對應的形式:ifstream &get(char &ch);功能是從流中讀取一個字符,結果保存在引用ch中,如果到文件尾,返回空字符。如file2.get(x);表示從文件中讀取一個字符,并把讀取的字符保存在x中。

              另一種重載形式的原型是: int get();這種形式是從流中返回一個字符,如果到達文件尾,返回EOF,如x=file2.get();和上例功能是一樣的。

              還 有一種形式的原型是:ifstream &get(char *buf,int num,char delim='n');這種形式把字符讀入由 buf 指向的數組,直到讀入了 num 個字符或遇到了由 delim 指定的字符,如果沒使用 delim 這個參數,將使用缺省值換行符'n'。例如:

              file2.get(str1,127,'A');//從文件中讀取字符到字符串str1,當遇到字符'A'或讀取了127個字符時終止。

              ③讀寫數據塊
              要讀寫二進制數據塊,使用成員函數read()和write()成員函數,它們原型如下:

              read(unsigned char *buf,int num);
              write(const unsigned char *buf,int num);

              read() 從文件中讀取 num 個字符到 buf 指向的緩存中,如果在還未讀入 num 個字符時就到了文件尾,可以用成員函數 int gcount();來取得實際讀取的字符數;而 write() 從buf 指向的緩存寫 num 個字符到文件中,值得注意的是緩存的類型是 unsigned char *,有時可能需要類型轉換。

              例:

              unsigned char str1[]="I Love You";
              int n[5];
              ifstream in("xxx.xxx");
              ofstream out("yyy.yyy");
              out.write(str1,strlen(str1));//把字符串str1全部寫到yyy.yyy中
              in.read((unsigned char*)n,sizeof(n));//從xxx.xxx中讀取指定個整數,注意類型轉換
              in.close();out.close();

              四、檢測EOF
              成員函數eof()用來檢測是否到達文件尾,如果到達文件尾返回非0值,否則返回0。原型是int eof();

              例: if(in.eof())ShowMessage("已經到達文件尾!");

              五、文件定位
              和C的文件操作方式不同的是,C++ I/O系統管理兩個與一個文件相聯系的指針。一個是讀指針,它說明輸入操作在文件中的位置;另一個是寫指針,它下次寫操作的位置。每次執行輸入或輸出時, 相應的指針自動變化。所以,C++的文件定位分為讀位置和寫位置的定位,對應的成員函數是 seekg()和 seekp(),seekg()是設置讀位置,seekp是設置寫位置。它們最通用的形式如下:

              istream &seekg(streamoff offset,seek_dir origin);
              ostream &seekp(streamoff offset,seek_dir origin);

              streamoff定義于 iostream.h 中,定義有偏移量 offset 所能取得的最大值,seek_dir 表示移動的基準位置,是一個有以下值的枚舉:

              ios::beg: 文件開頭
              ios::cur: 文件當前位置
              ios::end: 文件結尾
              這兩個函數一般用于二進制文件,因為文本文件會因為系統對字符的解釋而可能與預想的值不同。

              例:

              file1.seekg(1234,ios::cur);//把文件的讀指針從當前位置向后移1234個字節
              file2.seekp(1234,ios::beg);//把文件的寫指針從文件開頭向后移1234個字節

            posted @ 2012-06-28 22:36 多彩人生 閱讀(365) | 評論 (0)編輯 收藏

            C/C++遍歷文件夾

             finddata_t的使用

              那么到底如何查找文件呢?我們需要一個結構體和幾個大家可能不太熟悉的函數。這些函數和結構體在<io.h>的頭文件中,結構體為 struct _finddata_t ,函數為_findfirst、_findnext和_fineclose.具體如何使用,我會慢慢講來~

              首先講這個結構體吧~struct _finddata_t ,這個結構體是用來存儲文件各種信息的。說實話,這個結構體的具體定義代碼,我沒有找到,不過還好,文檔里面在_find里有比較詳細的成員變量介紹。我基本上就把文檔翻譯過來講吧:

              unsigned atrrib:文件屬性的存儲位 置。它存儲一個unsigned單元,用于表示文件的屬性。文件屬性是用位表示的,主要有以下一些:_A_ARCH(存檔)、_A_HIDDEN(隱 藏)、_A_NORMAL(正常)、_A_RDONLY(只讀)、_A_SUBDIR(文件夾)、_A_SYSTEM(系統)。這些都是 在<io.h>中定義的宏,可以直接使用,而本身的意義其實是一個無符號整型(只不過這個整型應該是2的幾次冪,從而保證只有一位為1,而其 他位為0)。既然是位表示,那么當一個文件有多個屬性時,它往往是通過位或的方式,來得到幾個屬性的綜合。例如只讀+隱藏+系統屬性,應該 為:_A_HIDDEN | _A_RDONLY |_A_SYSTEM .

              time_t time_create:這里的time_t是一個變量類型(長整型?相當于long int?),用來存儲時間的,我們暫時不用理它,只要知道,這個time_create變量是用來存儲文件創建時間的就可以了。

              time_t time_access:文件最后一次被訪問的時間。

              time_t time_write:文件最后一次被修改的時間。

              _fsize_t size:文件的大小。這里的_fsize_t應該可以相當于unsigned整型,表示文件的字節數。

              char name[_MAX_FNAME]:文件的文件名。這里的_MAX_FNAME是一個常量宏,它在<stdlib.h>頭文件中被定義,表示的是文件名的最大長度。

              以此,我們可以推測出,struct_finddata_t ,大概的定義如下:

              struct _finddata_t

              {

              unsigned attrib;

              time_t time_create;

              time_t time_access;

              time_t time_write;

              _fsize_t size;

              char name[_MAX_FNAME];

              };

              前面也說了,這個結構體是用來存儲文件信息的,那么如何把一個硬盤文件的文件信息“存到”這個結構體所表示的內存空間里去呢?這就要靠_findfirst、_findnext和_fineclose三個函數的搭配使用了。

              首先還是對這三個函數一一介紹一番吧……

              long _findfirst( char *filespec, struct _finddata_t *fileinfo );

              返回值:如果查找成功的話,將返回一個long型的唯一的查找用的句柄(就是一個唯一編號)。這個句柄將在_findnext函數中被使用。若失敗,則返回-1.

              參數:

              filespec:標明文件的字符串,可支持通配符。比如:*.c,則表示當前文件夾下的所有后綴為C的文件。

              fileinfo :這里就是用來存放文件信息的結構體的指針。這個結構體必須在調用此函數前聲明,不過不用初始化,只要分配了內存空間就可以了。函數成功后,函數會把找到的文件的信息放入這個結構體中。

              int _findnext( long handle, struct _finddata_t *fileinfo );

              返回值:若成功返回0,否則返回-1.

              參數:

              handle:即由_findfirst函數返回回來的句柄。

              fileinfo:文件信息結構體的指針。找到文件后,函數將該文件信息放入此結構體中。

              int _findclose( long handle );

              返回值:成功返回0,失敗返回-1.

              參數:

              handle :_findfirst函數返回回來的句柄。

              大家看到這里,估計都能猜到個大概了吧?先用_findfirst查找第一個文件,若成功則用返回的句柄調用_findnext函數查找其他的 文件,當查找完畢后用,用_findclose函數結束查找。恩,對,這就是正確思路。下面我們就按照這樣的思路來編寫一個查找C:\WINDOWS文件 夾下的所有exe可執行文件的程序。

              #include <stdio.h>

              #include <io.h>

              const char *to_search="C:\\WINDOWS\\*.exe";        //欲查找的文件,支持通配符

              int main()

              {

              long handle;                                               //用于查找的句柄

              struct _finddata_t fileinfo;                          //文件信息的結構體

              handle=_findfirst(to_search,&fileinfo);         //第一次查找

              if(-1==handle)return -1;

              printf("%s\n",fileinfo.name);                         //打印出找到的文件的文件名

              while(!_findnext(handle,&fileinfo))               //循環查找其他符合的文件,知道找不到其他的為止

              {

              printf("%s\n",fileinfo.name);

              }

              _findclose(handle);                                      //別忘了關閉句柄

              system("pause");

              return 0;

              }

              當然,這個文件的查找是在指定的路徑中進行,如何遍歷硬盤,在整個硬盤中查找文件呢?大家可以在網絡上搜索文件遞歸遍歷等方法,這里不再做進一步介紹。

              細心的朋友可能會注意到我在程序的末尾用了一個system函數。這個與程序本身并沒有影響,和以前介紹給大家的使用getchar()函數的 作用相同,只是為了暫停一下,讓我們能看到命令提示符上輸出的結果而已。不過system函數本身是一個非常強大的函數。大家可以查查MSDN看看~簡單 來說,它是一個C語言與操作系統的相互平臺,可以在程序里通過這個函數,向操作系統傳遞command命令

            posted @ 2012-06-28 21:37 多彩人生 閱讀(746) | 評論 (0)編輯 收藏

            c++ 宏

            c/c++ 宏中"#"和"##"的用法
            2007年05月14日 星期一 上午 10:19
            一、一般用法
            我們使用#把宏參數變為一個字符串,用##把兩個宏參數貼合在一起.
            用法:
            #i nclude<cstdio>
            #i nclude<climits>
            using namespace std;
            #define STR(s)      #s
            #define CONS(a,b)   int(a##e##b)

            int main()
            {
                 printf(STR(vck));            // 輸出字符串"vck"
                 printf("%d\n", CONS(2,3));   // 2e3 輸出:2000
                 return 0;
            }

            二、當宏參數是另一個宏的時候
            需要注意的是凡宏定義里有用'#'或'##'的地方宏參數是不會再展開.

            1, 非'#'和'##'的情況
            #define TOW       (2)
            #define MUL(a,b) (a*b)

            printf("%d*%d=%d\n", TOW, TOW, MUL(TOW,TOW));
            這行的宏會被展開為:
            printf("%d*%d=%d\n", (2), (2), ((2)*(2)));
            MUL里的參數TOW會被展開為(2).

            2, 當有'#'或'##'的時候
            #define A           (2)
            #define STR(s)      #s
            #define CONS(a,b)   int(a##e##b)

            printf("int max: %s\n",   STR(INT_MAX));     // INT_MAX #i nclude<climits>
            這行會被展開為:
            printf("int max: %s\n", "INT_MAX");

            printf("%s\n", CONS(A, A));                // compile error
            這一行則是:
            printf("%s\n", int(AeA));

            INT_MAX和A都不會再被展開, 然而解決這個問題的方法很簡單. 加多一層中間轉換宏.
            加這層宏的用意是把所有宏的參數在這層里全部展開, 那么在轉換宏里的那一個宏(_STR)就能得到正確的宏參數.

            #define A            (2)
            #define _STR(s)      #s
            #define STR(s)       _STR(s)           // 轉換宏
            #define _CONS(a,b)   int(a##e##b)
            #define CONS(a,b)    _CONS(a,b)        // 轉換宏

            printf("int max: %s\n", STR(INT_MAX));           // INT_MAX,int型的最大值,為一個變量 #i nclude<climits>
            輸出為: int max: 0x7fffffff
            STR(INT_MAX) -->   _STR(0x7fffffff) 然后再轉換成字符串;

            printf("%d\n", CONS(A, A));
            輸出為:200
            CONS(A, A)   -->   _CONS((2), (2))   --> int((2)e(2))

            三、'#'和'##'的一些應用特例
            1、合并匿名變量名
            #define   ___ANONYMOUS1(type, var, line)   type   var##line
            #define   __ANONYMOUS0(type, line)   ___ANONYMOUS1(type, _anonymous, line)
            #define   ANONYMOUS(type)   __ANONYMOUS0(type, __LINE__)
            例:ANONYMOUS(static int);   即: static int _anonymous70;   70表示該行行號;
            第一層:ANONYMOUS(static int);   -->   __ANONYMOUS0(static int, __LINE__);
            第二層:                         -->   ___ANONYMOUS1(static int, _anonymous, 70);
            第三層:                         -->   static int   _anonymous70;
            即每次只能解開當前層的宏,所以__LINE__在第二層才能被解開;

            2、填充結構
            #define   FILL(a)    {a, #a}

            enum IDD{OPEN, CLOSE};
            typedef struct MSG{
               IDD id;
               const char * msg;
            }MSG;

            MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};
            相當于:
            MSG _msg[] = {{OPEN, "OPEN"},
                           {CLOSE, "CLOSE"}};

            3、記錄文件名
            #define   _GET_FILE_NAME(f)    #f
            #define   GET_FILE_NAME(f)     _GET_FILE_NAME(f)
            static char   FILE_NAME[] = GET_FILE_NAME(__FILE__);

            4、得到一個數值類型所對應的字符串緩沖大小
            #define   _TYPE_BUF_SIZE(type)   sizeof #type
            #define   TYPE_BUF_SIZE(type)    _TYPE_BUF_SIZE(type)
            char   buf[TYPE_BUF_SIZE(INT_MAX)];
                  -->   char   buf[_TYPE_BUF_SIZE(0x7fffffff)];
                  -->   char   buf[sizeof "0x7fffffff"];
            這里相當于:
            char   buf[11];

            //////////////////////////////////////////////////////////////////////////////////////////////////////////////

            ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

            c和c++調式 利用宏獲得函數名
            僅僅為了獲取函數名,就在函數體中嵌入硬編碼的字符串,這種方法單調乏味還易導致錯誤,不如看一下怎樣使用新的C99特性,在程序運行時獲取函數名吧。
               對象反射庫、調試工具及代碼分析器,經常會需要在運行時訪問函數的名稱,直到不久前,唯一能完成此 項任務并且可移植的方法,是手工在函數體內嵌入一個帶有該函數名的硬編碼字符串,不必說,這種方法非常單調無奇,并且輕易導致錯誤。本文將要演示怎樣使用 新的C99特性,在運行時獲取函數名。
              那么怎樣以編程的方式從當前運行的函數中得到函數名呢?
              答案是:使用__FUNCTION__ 及相關宏。
              引出問題
              通常,在調試中最讓人心煩的階段,是不斷地檢查是否已調用了特定的函數。對此問題的解決方法,一般是添加一個cout或printf()——假如你使用C語言,如下所示:
            void myfunc()
            {
            cout<<"myfunc()"<<endl;
            //其他代碼
            }
            通常在一個典型的工程中,會包含有數千個函數,要在每個函數中都加入一條這樣的輸出語句,無疑難過上“蜀山”啊,因此,需要有一種機制,可以自動地完成這項操作。
              獲取函數名
              作為一個C++程序員,可能經常碰到 __TIME__、__FILE__、__DATE__ ,__LINE__ 這樣的宏,它們會在編譯時,分別轉換為包含編譯時間、處理的轉換單元名稱及當前時間的字符串。
               在最新的ISO C標準中,如大家所知的C99,加入了另一個有用的、類似宏的表達式__func__,其會報告未修飾過的(也就是未裁剪過的)、正在被訪問的函數名。請 注重,__func__不是一個宏,因為預處理器對此函數一無所知;相反,它是作為一個隱式聲明的常量字符數組實現的:
            static const char __func__[] = "function-name";
            在function-name處,為實際的函數名。為激活此特性,某些編譯器需要使用特定的編譯標志,請查看相應的編譯器文檔,以獲取具體的資料。
              有了它,我們可免去大多數通過手工修改,來顯示函數名的苦差事,以上的例子可如下所示進行重寫:
            void myfunc()
            {
            cout<<"__FUNCTION__"<<endl;
            }
            官 方C99標準為此目的定義的__func__標識符,確實值得大家關注,然而,ISO C++卻不完全支持所有的C99擴展,因此,大多數的編譯器提供商都使用 __FUNCTION__ 取而代之,而 __FUNCTION__ 通常是一個定義為 __func__ 的宏,之所以使用這個名字,是因為它已受到了大多數的廣泛支持。
              在Visual Studio 2005中,默認情況下,此特性是激活的,但不能與/EP和/P編譯選項同時使用。請注重在IDE環境中,不能識別__func__ ,而要用__FUNCTION__ 代替。
              Comeau的用戶也應使用 __FUNCTION__ ,而不是 __func__ 。
              C++ BuilderX的用戶則應使用稍稍不同的名字:__FUNC__ 。
              GCC 3.0及更高的版本同時支持 __func__ 和__FUNCTION__ 。
              一旦可自動獲取當前函數名,你可以定義一個如下所示顯示任何函數名的函數:
            void show_name(const char * name)
            {
            cout<<name<<endl;
            }
            void myfunc()
            {
            show_name(__FUNCTION__); //輸出:myfunc
            }
            void foo()
            {
            show_name(__FUNCTION__); //輸出:foo
            }
            因為 __FUNCTION__ 會在函數大括號開始之后就立即初始化,所以,foo()及myfunc()函數可在參數列表中安全地使用它,而不用擔心重載。
              簽名與修飾名
               __FUNCTION__ 特性最初是為C語言設計的,然而,C++程序員也會經常需要有關他們函數的額外信息,在Visual Studio 2005中,還支持另外兩種非標準的擴展特性:__FUNCDNAME__ 與 __FUNCSIG__ ,其分別轉譯為一個函數的修飾名與簽名。函數的修飾名非常有用,例如,在你想要檢查兩個編譯器是否共享同樣的ABI時,就可派得上用場,另外,它還能幫助 你破解那些含義模糊的鏈接錯誤,甚至還可用它從一個DLL中調用另一個用C++鏈接的函數。在下例中,show_name()報告了函數的修飾名:
            void myfunc()
            {
            show_name(__FUNCDNAME__); //輸出:?myfunc@@YAXXZ
            }
            一 個函數的簽名由函數名、參數列表、返回類型、內含的命名空間組成。假如它是一個成員函數,它的類名和const/volatile限定符也將是簽名的 一部分。以下的代碼演示了一個獨立的函數與一個const成員函數簽名間的不同之處,兩個函數的名稱、返回類型、參數完全相同:
            void myfunc()
            {
            show_name(__FUNCSIG__); // void __cdecl myfunc(void)
            }
            struct S
            {
            void myfunc() const
            {
            show_name(__FUNCSIG__); //void __thiscall S::myfunc(void) const
            }
            };



            posted @ 2012-06-28 16:10 多彩人生 閱讀(371) | 評論 (0)編輯 收藏

            wxWidgets系列之自定義事件實現、調用6步曲

            自定義事件實現步驟有如下幾步:

            1、定義自定義事件id

            enum CustomEventId
            {
                ENUM_CUSTOMEVENT_ID_Id1=7000,
                ENUM_CUSTOMEVENT_ID_Id2,
                ENUM_CUSTOMEVENT_ID_Id3
            };

             

            2、申明自定義事件(.h文件中)

            BEGIN_DECLARE_EVENT_TYPES()
                DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME1, ENUM_CUSTOMEVENT_ID_Id1)
                DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME2, ENUM_CUSTOMEVENT_ID_Id2)
                DECLARE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME3, ENUM_CUSTOMEVENT_ID_Id3)
            END_DECLARE_EVENT_TYPES()

             

            3、定義自定義事件(.cpp文件中)

            DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME1)
            DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME2)
            DEFINE_EVENT_TYPE(ENUM_CUSTOMEVENT_NAME3)

             

            4、在BEGIN_EVENT_TABLE與END_EVENT_TABLE()添加事件映射

                EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME1, Frame::OnSetName1)
                EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME2, Frame::OnSetName2)
                EVT_COMMAND(wxID_ANY, ENUM_CUSTOMEVENT_NAME3, Frame::OnSetName3)

             

            5、在Frame中,申明、實現OnSetName1、OnSetName2、OnSetName3

            申明:

                void  OnSetName1(wxCommandEvent& event);

                void  OnSetName2(wxCommandEvent& event);

                void  OnSetName3(wxCommandEvent& event);

            實現:代碼就不在此列舉

            6、自定義事件調用

                 wxCommandEvent eventCustom(ENUM_CUSTOMEVENT_NAME1);
                 wxPostEvent(this->GetParent()->GetEventHandler(), eventCustom); //子窗口

                如果是當前窗口可以寫成

               wxPostEvent(this->GetEventHandler(), eventCustom);

            posted @ 2012-06-25 15:49 多彩人生 閱讀(825) | 評論 (0)編輯 收藏

            僅列出標題
            共25頁: First 11 12 13 14 15 16 17 18 19 Last 

            導航

            統計

            常用鏈接

            留言簿(3)

            隨筆分類

            隨筆檔案

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            欧美精品乱码99久久蜜桃| 国产精品久久久久久吹潮| 精品久久久久久无码中文野结衣| 久久这里只有精品首页| 久久久WWW成人免费精品| 日本五月天婷久久网站| 久久久久人妻精品一区| 99久久精品免费看国产一区二区三区 | 久久久久久亚洲Av无码精品专口| 97r久久精品国产99国产精| 色婷婷狠狠久久综合五月| 精品国产乱码久久久久软件| 亚洲色大成网站www久久九| 久久国产高清字幕中文| 国产精品久久久久久久久久影院 | 狠狠色丁香婷婷综合久久来来去| 亚洲天堂久久久| 大美女久久久久久j久久| 中文字幕无码久久精品青草| 久久精品视频网| 麻豆成人久久精品二区三区免费| 国内精品欧美久久精品| 1000部精品久久久久久久久| 久久综合色区| 久久99精品久久久久久9蜜桃 | 久久免费国产精品一区二区| 99精品久久久久久久婷婷| 久久综合九色欧美综合狠狠| 久久精品成人免费网站| 久久久精品人妻一区二区三区蜜桃| 日韩va亚洲va欧美va久久| 99久久国产热无码精品免费久久久久| 亚洲av成人无码久久精品| 久久婷婷色香五月综合激情| 久久精品中文字幕一区| 狠狠精品久久久无码中文字幕| 72种姿势欧美久久久久大黄蕉| 久久九九精品99国产精品| 少妇内射兰兰久久| 久久精品国产久精国产果冻传媒 | 久久午夜羞羞影院免费观看|