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

            chaosuper85

            C++博客 首頁 新隨筆 聯系 聚合 管理
              118 Posts :: 0 Stories :: 3 Comments :: 0 Trackbacks

            #

            開發輔助工具大收集

            除了我們日常開發使用的Visual C++、Delphi、JBuilder等等大家伙,
            還有很多小巧好用的開發輔助工具,善用它們可以極大的提高我們的效率。

            日常工作中我主要使用Visual C++開發程序,工作幾年,收集了一些小工具,
            下面逐個介紹給大家。也希望大家把自己的工具拿出來和大家分享。

            如果大家需要,請跟貼,需要的朋友多的話,我上傳到FTP上(都是沒有限制的最新版本喲)

            Visual C++插件

            [1] Visual Assist

            http://www.wholetomato.com/download/index.shtml

            這是我現在使用最為頻繁的工具,有了它,現在在Visual C++中寫程序簡直成了一種享受,
            Visual Assist的智能提示功能實在是太強大了,估計大家都應該裝了吧!
            唯一不太爽的是Visual Assist對C++ STL和Templates的解析還是有待改進。

            [2] WndTab

            http://www.wndtabs.com

            Visual C++ 6.0的編輯窗口沒有分頁顯示,
            想在打開的多個文件中切換非常麻煩,
            WndTab為VC的編輯窗口加上了Tab,
            現在點擊每個文件的Tab就可以方便的切換到該文件進行編輯了,強烈推薦。

            [3] BoundsCheck

            CompuWare的調試工具,可以集成到Visual C++中。
            BoundsCheck可以幫助我們發現程序中隱藏的bug,比如Memory Leak等。
            缺省安裝后,BoundsCheck的設置是每當發現調試狀態下運行的程序中的bug就馬上中斷執行,返回Visual C++窗口報告bug,但是很多BoundsCheck發現的bug都是一些程序隱患,但不影響當前程序運行,所以有些討厭??梢栽贐oundsCheck的工具欄中將立即報告錯誤按鈕釋放,以后我們就可以不被BoundsCheck打擾,而是每次調試后得到一份BoundsCheck的bug匯總報告!

            其他工具

            [4] 界面庫Xtreme Toolkit

            http://www.codejock.com

            和Xtreme Toolkit類似的還有BCG Controls,但是我覺得Xtreme Toolkit更好用一些,它們都提供了一整套功能強大、非常漂亮的控件,幫助我們輕松創建出很Cool的程序界面,從而把主要精力放到程序功能上。

            [5] IconXP

            http://www.aha-soft.com

            制作程序的各種圖標,如果利用Visual C++或者Delphi等自帶的資源編輯器,只能編輯256色的圖標,非常麻煩而且基本無法編輯出XP風格的圖標來。利用IconXP可以輕松創作出很Cool的圖標來,而且IconXP能夠從各種文件中提取出圖標文件。

            寫了這么多,累了,明天繼續……
            [6] OllyDbg

            http://home.t-online.de/home/Ollydbg/

            這是一個很Cool的靜態反匯編工具,并且能夠在反匯編代碼的基礎上對應用程序進行調試。
            個人認為OllyDbg比很多crack網站上推薦的WDASM好用,因為OllyDbg加入了很多對反匯編代碼的進一步分析功能,并加上相應的注釋,非常方便。
            比如應用程序在某處調用了Windows API函數,該處后面就會出現注釋告訴你這里調用了哪個Windows API函數,更酷的是連給該Windows API傳遞參數的地方也會加上注釋說明。
            另外由于很多應用程序都是使用Visual C++編寫,而Visual C++生成的匯編代碼有一定的格式(如果沒有選擇某些優化功能的時候),所以OllyDbg甚至會將一些匯編代碼對應的C語言代碼以注釋的方式說明。

            OllyDbg本身的調試功能也很強大,多用幾次就會得心應手。

            總而言之,OllyDbg絕對是在沒有源代碼的情況下分析應用程序的必備工具。

            CodeProject上有兩篇文章FreeCell & Hearts, behind the scenes和Minesweeper, Behind the scenes,作者就是以OllyDbg為工具探索到了Windows附帶的掃雷游戲、空當接龍游戲的底層數據結構,從而寫出了直接讀取這些游戲內存的程序,我稍加修改就做了一個自動掃雷的程序,呵呵。

            以下程序在http://www.sysinternals.com有提供

            [7] DebugView

            看過《深入淺出MFC》嗎,候捷先生在書的最后提到了一種追蹤TRACE(實際上是Windows函數OutputDebugString)的工具。有了該工具,你就可以在應用程序運行時通過它觀察追蹤應用程序內部的運行情況,只要你在程序中加了足夠多的TRACE宏,并且以Debug版本編譯。
            特別是對于程序邏輯復雜(Debug幾次就暈了),或者涉及到圖形界面刷新或顯示的程序(如果用一臺電腦調試,在Visual C++環境和被調試程序之間切換,你很難看到正確的結果),或者非常耗費系統資源的程序(在用Visual C++調試運行,就更費勁了),巧妙的使用這類工具可以高效的解決問題。
            說實話,Paul DiLascia等大師固然提供了這些工具,但是這些大師只是為了展示某些技術,所以他們提供的工具都只有基本功能。而DebugView是同類工具中最為優秀的一個,適用范圍廣,能夠定制各種過濾條件,讓你只看到關心的TRACE輸出信息,而且可以定制高亮顯示的內容等等,非常方便。
            DebugView是完全免費的!

            [8]
            Disk Monitor
            File Monitor
            Register Monitor
            Port Monitor


            這系列Monitor工具分別對系統中的磁盤、文件、注冊表、端口的變化更改進行實時監控并記錄下來,對于我們追蹤程序對系統進行了那些更改特別有用。

            SysInternals上面還有很多工具,都是免費的,有些還提供源代碼。

            上面是我經常使用的開發輔助工具,有些可能一時沒有想到,待以后慢慢在這里補全。
            因我主要使用Visual C++進行開發,所以介紹的工具也都主要是和Visual C++相關的,希望有朋友能夠將其他主要開發工具的好的配套輔助工具也來個介紹。

            另外,如果有朋友需要上面介紹的工具,請跟貼,我試情況上傳到FTP上供大家下載。

            posted @ 2009-08-19 22:29 chaosuper 閱讀(275) | 評論 (0)編輯 收藏

            我們很難寫出所有可能被實例化的類型都合適的模板。某些情況下,

                通用模板定義對于某個類型可能是完全錯誤的,所以我們需要能夠實現處

                理某些特殊情況,特化的概念變是如此。compare函數和Queue類是這

                個問題的很好例子。因為與C風格字符串一起使用時,他們都不能正確工作

            。

                template <typename T>
                int compare(const T &v1,const T &v2)
                {
                  if(v1 < v2) return -1;
                  if(v2 < v1) return 1;
                  return 0;
                }

                    如果用兩個const char* 實參調用這個模板定義,函數將比較指針的
                值。也就是比較兩個指針在內存中的相對位置,卻并沒有說明與指針所指
                數組的內容有關的任何事情。
                    為了能夠將compare函數用于字符串,必須提供一個知道怎樣比較C風
                格字符串的特殊定義。這些就被稱作是特化的,它對模板的用戶而言是透
                明的。


                1. 函數模板的特化
                特化形式:
                - 關鍵字template后面接一對空的尖括號<>;
                - 再接模板名和一對尖括號<>,尖括號中指定這個特化定義的模板參數:
                - 函數形參表
                - 函數體

                template<>
                int compare<const char*> (const char* const &v1,
                     const char* const &v2)
                {
                  return strcmp(v1,v2);
                }
                    特化的聲明必須與 對應的模板相匹配。類型形參固定為const char*。
                因此,函數形參是const char* 的const引用。當調用compare函數的時候,
                傳給它兩個字符指針,編譯器將調用特化版本。而不調用上面的泛型版本。
                  const  char *cp1 = "world", *cp2 = "hi";
                  int i1, i2;
                  compare(cp1, cp2); //調用特化函數模板
                  compare(i1, i2);   //調用泛型函數模板

                注意:
                * 函數模板特化時template<>不能省略,如果缺少結果是聲明該函數的重載

            。
                * 必須包含函數形參列表。如果可以從形參列表推斷模板實參,則不必顯示

                   定模板實參。
                * 如果程序由多個文件構成,模板特化的聲明必須在使用該特化的每個文件
                   中出現。


                2.類模板的特化
                  當使用C風格字符串時,Queue類具有 compare函數相似的問題。問題就處
                在push函數中,該函數復制給定的值以創建Queue中的新元素。默認情況下

            ,
                復制C風格字符串只會復制指針,不會復制字符。而顯然復制指針將出現一


                列的嚴重問題。為了解決復制C風格字符串的問題,需要為const char*定義
                整個類的特化版本:
                  template<> class Queue<const char*>{
                  public:
                    void push(const char*);
                    void pop() {real_queue.pop();}
                    bool empty() const {return real_queue.front();}
                    //返回類型與模板參數類型不同
                    std::string front() {return real_queue.front();}
                    const std::string &front() const {return real_queue.front();

                  private :
                    Queue<std::string> real_queue;
                };
                給Queue一個新的數據元素,string對象的Queue。
                在類的外部定義一個成員:
                  void Queue<const char*>::push (const char* val)
                  {
                    return real_queue.push(val);
                  }
                這個函數通過調用read_queue的push函數把val指向的數組復制到未命名的
                string 對象中。當需要出隊列的時候調用相應real_queue.pop()函數即返
                回了這個string,從而解決了不用復制指針的問題。

               3.特化成員而不特化類
                在上例的實現中,我們可以換一種方法,即不需要特化類,而只需要特化類
                的成員函數push、pop。

                根據函數模板特化的要求:
                template <>
                void Queue<const char*>::push(const char *const &val)
                {
                  char * new_item = new char[strlen(val)+1];
                  strncpy(new_item, val, strlen(val)+1);
                  QueueItem<const char*> *pt =
                 new QueueItem<const char*>(new_item);
                  if(empty())
                    head = tail = pt;   //隊列中沒有元素
                  eles{
                    tail->next = pt; //添加新元素到列尾
                    tail = pt;
                  }
                }

                template<>
                void Queue<const char*>::pop()
                {
                  QueueItem<const char*> *p = head;
                  delete head->item;  //刪除隊首元素
                  head = head->next;  //指向當前隊首元素
                  delete p;           //刪除零時指針
                }


                4.類模板的部分特化
                如果類模板有一個以上的模板形參,我們很有可能只要特化某些模板形參
                而不是全部形參。這時我們就需要使用類的部分特化。

                //定義模板類
                template <class T1, class T2>
                class some_template{
                  // ...
                };

                //定義模板類的部分特化:T2類型固定,部分特化T1類型
                template<class T1>
                class some_template<T1, int>{
                  // ...
                };

                //使用類模板的部分特化
                some_template<int, string> foo; //使用模板類
                some_template<string,int> bar;  //使用模板類的部分特化

                通過使用模板特化能解決一些在通?;蛘咄ㄓ们闆r下無法解決的特殊問題。

                在掌握了基本的語法規范和實現方法后便可以加以應用。

             

            posted @ 2009-08-11 18:58 chaosuper 閱讀(542) | 評論 (0)編輯 收藏

            Q:什么是C風格轉換?什么是static_cast, dynamic_cast 以及

            reinterpret_cast?區別是什么?為什么要注意?

            A:轉換的含義是通過改變一個變量的類型為別的類型從而改變該變量的表示方式

            。為了類型轉換一個簡單對象為另一個對象你會使用傳統的類型轉換操作符。比

            如,為了轉換一個類型為doubole的浮點數的指針到整型:
            代碼:
            int i;
            double d;

            i = (int) d;
            或者:

            i = int (d);

            對于具有標準定義轉換的簡單類型而言工作的很好。然而,這樣的轉換符也能不

            分皂白的應用于類(class)和類的指針。ANSI-C++標準定義了四個新的轉換符:

            'reinterpret_cast', 'static_cast', 'dynamic_cast' 和 'const_cast',目的

            在于控制類(class)之間的類型轉換。
            代碼:
            reinterpret_cast<new_type>(expression)
            dynamic_cast<new_type>(expression)
            static_cast<new_type>(expression)
            const_cast<new_type>(expression)


            1 reinterpret_cast

            'reinterpret_cast'轉換一個指針為其它類型的指針。它也允許從一個指針轉換

            為整數類型。反之亦然。(譯注:是指針具體的地址值作為整數值?)
            這個操作符能夠在非相關的類型之間轉換。操作結果只是簡單的從一個指針到別

            的指針的值的二進制拷貝。在類型之間指向的內容不做任何類型的檢查和轉換。

            如果情況是從一個指針到整型的拷貝,內容的解釋是系統相關的,所以任何的實

            現都不是方便的。一個轉換到足夠大的整型能夠包含它的指針是能夠轉換回有效

            的指針的。

            代碼:
            class A {};
            class B {};

            A * a = new A;
            B * b = reinterpret_cast<B *>(a);
            'reinterpret_cast'就像傳統的類型轉換一樣對待所有指針的類型轉換。

            2 static_cast

            'static_cast'允許執行任意的隱式轉換和相反轉換動作。(即使它是不允許隱式

            的)

            應用到類的指針上,意思是說它允許子類類型的指針轉換為父類類型的指針(這

            是一個有效的隱式轉換),同時,也能夠執行相反動作:轉換父類為它的子類。

            在這最后例子里,被轉換的父類沒有被檢查是否與目的類型相一致。
            代碼:
            class Base {};
            class Derived : public Base {};

            Base *a    = new Base;
            Derived *b = static_cast<Derived *>(a);
            'static_cast'除了操作類型指針,也能用于執行類型定義的顯式的轉換,以及基

            礎類型之間的標準轉換:

            代碼:
            double d = 3.14159265;
            int    i = static_cast<int>(d);

            3 dynamic_cast

            'dynamic_cast'只用于對象的指針和引用。當用于多態類型時,它允許任意的隱

            式類型轉換以及相反過程。不過,與static_cast不同,在后一種情況里(注:即

            隱式轉換的相反過程),dynamic_cast會檢查操作是否有效。也就是說,它會檢

            查轉換是否會返回一個被請求的有效的完整對象。
            檢測在運行時進行。如果被轉換的指針不是一個被請求的有效完整的對象指針,

            返回值為NULL.
            代碼:
            class Base { virtual dummy() {} };
            class Derived : public Base {};

            Base* b1 = new Derived;
            Base* b2 = new Base;

            Derived* d1 = dynamic_cast<Derived *>(b1);          // succeeds
            Derived* d2 = dynamic_cast<Derived *>(b2);          // fails: returns

            'NULL'

            如果一個引用類型執行了類型轉換并且這個轉換是不可能的,一個bad_cast的異

            常類型被拋出:
            代碼:
            class Base { virtual dummy() {} };
            class Derived : public Base { };

            Base* b1 = new Derived;
            Base* b2 = new Base;

            Derived d1 = dynamic_cast<Derived &*>(b1);          // succeeds
            Derived d2 = dynamic_cast<Derived &*>(b2);          // fails: exception

            thrown

            4 const_cast

            這個轉換類型操縱傳遞對象的const屬性,或者是設置或者是移除:
            代碼:
            class C {};

            const C *a = new C;

            C *b = const_cast<C *>(a);
            其它三種操作符是不能修改一個對象的常量性的。
            注意:'const_cast'也能改變一個類型的volatile qualifier。

            --------------------------------------------------------------------

            C++的4種類型轉換

                一、C 風格(C-style)強制轉型如下:

                (T) expression // cast expression to be of type T
                函數風格(Function-style)強制轉型使用這樣的語法:
                T(expression) // cast expression to be of type T
                這兩種形式之間沒有本質上的不同,它純粹就是一個把括號放在哪的問題。

            我把這兩種形式稱為舊風格(old-style)的強制轉型。

               二、 C++的四種強制轉型形式:

              C++ 同時提供了四種新的強制轉型形式(通常稱為新風格的或 C++ 風格的強

            制轉型):
              const_cast(expression)
              dynamic_cast(expression)
              reinterpret_cast(expression)
              static_cast(expression)

              每一種適用于特定的目的:

              ·dynamic_cast 主要用于執行“安全的向下轉型(safe downcasting)”,

            也就是說,要確定一個對象是否是一個繼承體系中的一個特定類型。它是唯一不

            能用舊風格語法執行的強制轉型,也是唯一可能有重大運行時代價的強制轉型。
               
                ·static_cast 可以被用于強制隱型轉換(例如,non-const 對象轉型為

            const 對象,int 轉型為 double,等等),它還可以用于很多這樣的轉換的反向

            轉換(例如,void* 指針轉型為有類型指針,基類指針轉型為派生類指針),但

            是它不能將一個 const 對象轉型為 non-const 對象(只有 const_cast 能做到

            ),它最接近于C-style的轉換。
               
              ·const_cast 一般用于強制消除對象的常量性。它是唯一能做到這一點的

            C++ 風格的強制轉型。

              ·reinterpret_cast 是特意用于底層的強制轉型,導致實現依賴

            (implementation-dependent)(就是說,不可移植)的結果,例如,將一個指

            針轉型為一個整數。這樣的強制轉型在底層代碼以外應該極為罕見。
              
              舊風格的強制轉型依然合法,但是新的形式更可取。首先,在代碼中它們更

            容易識別(無論是人還是像 grep 這樣的工具都是如此),這樣就簡化了在代碼

            中尋找類型系統被破壞的地方的過程。第二,更精確地指定每一個強制轉型的目

            的,使得編譯器診斷使用錯誤成為可能。例如,如果你試圖使用一個 const_cast

            以外的新風格強制轉型來消除常量性,你的代碼將無法編譯。

            == 
            ==  dynamic_cast .vs. static_cast
            ==

            class B { ... };
            class D : public B { ... };

            void f(B* pb)
            {
               D* pd1 = dynamic_cast<D*>(pb);
               D* pd2 = static_cast<D*>(pb);
            }

            If pb really points to an object of type D, then pd1 and pd2 will get

            the same value. They will also get the same value if pb == 0.

            If pb points to an object of type B and not to the complete D class,

            then dynamic_cast will know enough to return zero. However, static_cast

            relies on the programmer’s assertion that pb points to an object of

            type D and simply returns a pointer to that supposed D object.

                即dynamic_cast可用于繼承體系中的向下轉型,即將基類指針轉換為派生類

            指針,比static_cast更嚴格更安全。dynamic_cast在執行效率上比static_cast

            要差一些,但static_cast在更寬上范圍內可以完成映射,這種不加限制的映射伴隨

            著不安全性.static_cast覆蓋的變換類型除類層次的靜態導航以外,還包括無映射

            變換,窄化變換(這種變換會導致對象切片,丟失信息),用VOID*的強制變換,隱式類

            型變換等...


            ==
            ==  static_cast .vs. reinterpret_cast
            ==

                reinterpret_cast是為了映射到一個完全不同類型的意思,這個關鍵詞在我們

            需要把類型映射回原有類型時用到它.我們映射到的類型僅僅是為了故弄玄虛和其

            他目的,這是所有映射中最危險的.(這句話是C++編程思想中的原話)

                static_cast 和 reinterpret_cast 操作符修改了操作數類型. 它們不是互

            逆的; static_cast 在編譯時使用類型信息執行轉換, 在轉換執行必要的檢測(諸

            如指針越界計算, 類型檢查). 其操作數相對是安全的. 另一方面,

            reinterpret_cast 僅僅是重新解釋了給出的對象的比特模型而沒有進行二進制轉

            換, 例子如下:

                int n=9; double d=static_cast < double > (n);

                上面的例子中, 我們將一個變量從 int 轉換到 double. 這些類型的二進制

            表達式是不同的. 要將整數 9 轉換到 雙精度整數 9, static_cast 需要正確地

            為雙精度整數 d 補足比特位. 其結果為 9.0. 而reinterpret_cast 的行為卻不

            同:

                int n=9;
                double d=reinterpret_cast<double & > (n);

                這次, 結果有所不同. 在進行計算以后, d 包含無用值. 這是因為

            reinterpret_cast 僅僅是復制 n 的比特位到 d, 沒有進行必要的分析.

            posted @ 2009-08-07 19:13 chaosuper 閱讀(2540) | 評論 (0)編輯 收藏

            Linux環境下的軟件安裝,并不是一件容易的事情;如果通過源代碼編譯后在安裝,當然事情就更為復雜一些;現在安裝各種軟件的教程都非常普遍;但萬變不離其中,對基礎知識的扎實掌握,安裝各種軟件的問題就迎刃而解了。Configure腳本配置工具就是基礎之一,它是autoconf的工具的基本應用。
                  
                與一些技巧相比,Configure顯得基礎一些,當然使用和學習起來就顯得枯燥乏味一些,當然要成為高手,對基礎的熟悉不能超越哦。
                  
                為此我轉載了一篇關于Configure選項配置的詳細介紹。供大家參考

                'configure'腳本有大量的命令行選項.對不同的軟件包來說,這些選項可能會有變化,但是許多基本的選項是不會改變的.帶上'--help'選項執行'configure'腳本可以看到可用的所有選項.盡管許多選項是很少用到的,但是當你為了特殊的需求而configure一個包時,知道他們的存在是很有益處的.下面對每一個選項進行簡略的介紹:

                --cache-file=FILE
                  'configure'會在你的系統上測試存在的特性(或者bug!).為了加速隨后進行的配置,測試的結果會存儲在一個cache file里.當configure一個每個子樹里都有'configure'腳本的復雜的源碼樹時,一個很好的cache file的存在會有很大幫助.

                --help
                  輸出幫助信息.即使是有經驗的用戶也偶爾需要使用使用'--help'選項,因為一個復雜的項目會包含附加的選項.例如,GCC包里的'configure'腳本就包含了允許你控制是否生成和在GCC中使用GNU匯編器的選項.

                --no-create
                  'configure'中的一個主要函數會制作輸出文件.此選項阻止'configure'生成這個文件.你可以認為這是一種演習(dry run),盡管緩存(cache)仍然被改寫了.

                --quiet
                --silent
                  當'configure'進行他的測試時,會輸出簡要的信息來告訴用戶正在作什么.這樣作是因為'configure'可能會比較慢,沒有這種輸出的話用戶將會被扔在一旁疑惑正在發生什么.使用這兩個選項中的任何一個都會把你扔到一旁.(譯注:這兩句話比較有意思,原文是這樣的:If there was no such output, the user would be left wondering what is happening. By using this option, you too can be left wondering!)

                --version
                  打印用來產生'configure'腳本的Autoconf的版本號.

                --prefix=PEWFIX
                  '--prefix'是最常用的選項.制作出的'Makefile'會查看隨此選項傳遞的參數,當一個包在安裝時可以徹底的重新安置他的結構獨立部分. 舉一個例子,當安裝一個包,例如說Emacs,下面的命令將會使Emacs Lisp file被安裝到"/opt/gnu/share":
                $ ./configure --prefix=/opt/gnu

                --exec-prefix=EPREFIX
                  與'--prefix'選項類似,但是他是用來設置結構倚賴的文件的安裝位置.編譯好的'emacs'二進制文件就是這樣一個問件.如果沒有設置這個選項的話,默認使用的選項值將被設為和'--prefix'選項值一樣.

                --bindir=DIR
                  指定二進制文件的安裝位置.這里的二進制文件定義為可以被用戶直接執行的程序.

                --sbindir=DIR
                  指定超級二進制文件的安裝位置.這是一些通常只能由超級用戶執行的程序.

                --libexecdir=DIR
                  指定可執行支持文件的安裝位置.與二進制文件相反,這些文件從來不直接由用戶執行,但是可以被上面提到的二進制文件所執行.

                --datadir=DIR
                  指定通用數據文件的安裝位置.

                --sysconfdir=DIR
                  指定在單個機器上使用的只讀數據的安裝位置.

                --sharedstatedir=DIR
                  指定可以在多個機器上共享的可寫數據的安裝位置.

                --localstatedir=DIR
                  指定只能單機使用的可寫數據的安裝位置.

                --libdir=DIR
                  指定庫文件的安裝位置.

                --includedir=DIR
                  指定C頭文件的安裝位置.其他語言如C++的頭文件也可以使用此選項.

                --oldincludedir=DIR
                  指定為除GCC外編譯器安裝的C頭文件的安裝位置.

                --infodir=DIR
                  指定Info格式文檔的安裝位置.Info是被GNU工程所使用的文檔格式.

                --mandir=DIR
                  指定手冊頁的安裝位置.

                --srcdir=DIR
                  這個選項對安裝沒有作用.他會告訴'configure'源碼的位置.一般來說不用指定此選項,因為'configure'腳本一般和源碼文件在同一個目錄下.

                --program-prefix=PREFIX
                  指定將被加到所安裝程序的名字上的前綴.例如,使用'--program-prefix=g'來configure一個名為'tar'的程序將會使安裝的程序被命名為'gtar'.當和其他的安裝選項一起使用時,這個選項只有當他被`Makefile.in'文件使用時才會工作.

                --program-suffix=SUFFIX
                  指定將被加到所安裝程序的名字上的后綴.

                --program-transform-name=PROGRAM
                  這里的PROGRAM是一個sed腳本.當一個程序被安裝時,他的名字將經過`sed -e PROGRAM'來產生安裝的名字.

                --build=BUILD
                  指定軟件包安裝的系統平臺.如果沒有指定,默認值將是'--host'選項的值.

                --host=HOST
                  指定軟件運行的系統平臺.如果沒有指定,將會運行`config.guess'來檢測.

                --target=GARGET
                  指定軟件面向(target to)的系統平臺.這主要在程序語言工具如編譯器和匯編器上下文中起作用.如果沒有指定,默認將使用'--host'選項的值.

                --disable-FEATURE
                  一些軟件包可以選擇這個選項來提供為大型選項的編譯時配置,例如使用Kerberos認證系統或者一個實驗性的編譯器最優配置.如果默認是提供這些特性,可以使用'--disable-FEATURE'來禁用它,這里'FEATURE'是特性的名字.例如:

               $ ./configure --disable-gui

                -enable-FEATURE[=ARG]
                  相反的,一些軟件包可能提供了一些默認被禁止的特性,可以使用'--enable-FEATURE'來起用它.這里'FEATURE'是特性的名字.一個特性可能會接受一個可選的參數.例如:
                $ ./configure --enable-buffers=128
                `--enable-FEATURE=no'與上面提到的'--disable-FEATURE'是同義的.

                --with-PACKAGE[=ARG]
                  在自由軟件社區里,有使用已有軟件包和庫的優秀傳統.當用'configure'來配置一個源碼樹時,可以提供其他已經安裝的軟件包的信息.例如,倚賴于Tcl和Tk的BLT器件工具包.要配置BLT,可能需要給'configure'提供一些關于我們把Tcl和Tk裝的何處的信息:
                $ ./configure --with-tcl=/usr/local --with-tk=/usr/local
                '--with-PACKAGE=no'與下面將提到的'--without-PACKAGE'是同義的.

                --without-PACKAGE
                  有時候你可能不想讓你的軟件包與系統已有的軟件包交互.例如,你可能不想讓你的新編譯器使用GNU ld.通過使用這個選項可以做到這一點:
                $ ./configure --without-gnu-ld

                --x-includes=DIR
                  這個選項是'--with-PACKAGE'選項的一個特例.在Autoconf最初被開發出來時,流行使用'configure'來作為Imake的一個變通方法來制作運行于X的軟件.'--x-includes'選項提供了向'configure'腳本指明包含X11頭文件的目錄的方法.

                --x-libraries=DIR
                  類似的,'--x-libraries'選項提供了向'configure'腳本指明包含X11庫的目錄的方法.

                  在源碼樹中運行'configure'是不必要的同時也是不好的.一個由'configure'產生的良好的'Makefile'可以構筑源碼屬于另一棵樹的軟件包.在一個獨立于源碼的樹中構筑派生的文件的好處是很明顯的:派生的文件,如目標文件,會凌亂的散布于源碼樹.這也使在另一個不同的系統或用不同的配置選項構筑同樣的目標文件非常困難.建議使用三棵樹:一棵源碼樹(source tree),一棵構筑樹(build tree),一棵安裝樹(install tree).這里有一個很接近的例子,是使用這種方法來構筑GNU malloc包:

                $ gtar zxf mmalloc-1.0.tar.gz

                $ mkdir build && cd build

                $ ../mmalloc-1.0/configure

                creating cache ./config.cache

                checking for gcc... gcc
                checking whether the C compiler (gcc ) works... yes

                checking whether the C compiler (gcc ) is a cross-compiler... no

                checking whether we are using GNU C... yes

                checking whether gcc accepts -g... yes

                checking for a BSD compatible install... /usr/bin/install -c

                checking host system type... i586-pc-linux-gnu

                checking build system type... i586-pc-linux-gnu

                checking for ar... ar
                checking for ranlib... ranlib

                checking how to run the C preprocessor... gcc -E

                checking for unistd.h... yes

                checking for getpagesize... yes

                checking for working mmap... yes

                checking for limits.h... yes

                checking for stddef.h... yes

                updating cache ../config.cache
                creating ./config.status

                這樣這棵構筑樹就被配置了,下面可以繼續構筑和安裝這個包到默認的位置'/usr/local':

                $ make all && make install

            一個軟件包通過編譯源代碼安裝后,如何完全的卸載??

                如果原先的source還在的話,很多source的Makefile都有寫uninstall規則,直接在Souce里make uninstall就可行,不過碰到無良作者沒寫的,那一句一句看Makefile里install部分他都干了些什么,然后挨個刪除。
                如果source沒了.....那就一邊郁悶吧
                到目前為止, 我裝的都可以make uninstall.......
                (因為總是不小心裝錯地方, 結果就make uninstall&&make clean,然后重新configure......)
                linux下軟件的基本安裝和卸載

                Linux軟件的安裝和卸載一直是困擾許多新用戶的難題。在Windows中,我們可以使用軟件自帶的安裝卸載程序或在控制面板中的“添加/刪除程序”來實現。與其相類似,在Linux下有一個功能強大的軟件安裝卸載工具,名為RPM。它可以用來建立、安裝、查詢、更新、卸載軟件。該工具是在命令行下使用的。在Shell的提示符后輸入rpm,就可獲得該命令的幫助信息。

                軟件的安裝

                Linux下軟件的安裝主要有兩種不同的形式。第一種安裝文件名為xxx.tar.gz;另一種安裝文件名為xxx.i386.rpm。以第一種方式發行的軟件多為以源碼形式發送的;第二種方式則是直接以二進制形式發送的。

                對于第一種,安裝方法如下:

                1 .首先,將安裝文件拷貝至你的目錄中。例如,如果你是以root身份登錄上的,就將軟件拷貝至/root中。

                #cp xxx.tar.gz /root

                2 .由于該文件是被壓縮并打包的,應對其解壓縮。命令為:

                #tar xvzf filename.tar.gz 如果是filename.tar.bz2格式的,應該是tar jxvf filename.tar.bz2來解壓

                3. 執行該命令后,安裝文件按路徑,解壓縮在當前目錄下。用ls命令可以看到解壓縮后的文件。通常在解壓縮后產生的文件中,有“Install”的文件。該文件為純文本文件,詳細講述了該軟件包的安裝方法。

                4.執行解壓縮后產生的一個名為configure的可執行腳本程序。它是用于檢查系統是否有編譯時所需的庫,以及庫的版本是否滿足編譯的需要等安裝所需要的系統信息。為隨后的編譯工作做準備。命令為: #./configure

                如果您想把軟件安裝到指定目錄,應該用#./configure --prefix=/您自己指定的目錄,比如我想把一個mlterm安裝到/opt/mlterm目錄中,應該如下輸入

                #./configure --prefix=/opt/mlterm

                5.檢查通過后,將生成用于編譯的MakeFile文件。此時,可以開始進行編譯了。編譯的過程視軟件的規模和計算機性能的不同,所耗費的時間也不同。命令為: #make。

                6.成功編譯后,鍵入如下的命令開始安裝:

                #make install

                7.安裝完畢,應清除編譯過程中產生的臨時文件和配置過程中產生的文件。鍵入如下命令:

                #make clean

                #make distclean

                至此,軟件的安裝結束。

                對于第二種,其安裝方法要簡單得多。

                同第一種方式一樣,將安裝文件拷貝至你的目錄中。然后使用rpm來安裝該文件。命令如下:

                #rpm -i filename.i386.rpm

                rpm將自動將安裝文件解包,并將軟件安裝到缺省的目錄下。并將軟件的安裝信息注冊到rpm的數據庫中。參數i的作用是使rpm進入安裝模式。

                軟件的卸載

                1.軟件的卸載主要是使用rpm來進行的。卸載軟件首先要知道軟件包在系統中注冊的名稱。鍵入命令:

                #rpm -q -a

                即可查詢到當前系統中安裝的所有的軟件包。

                2. 確定了要卸載的軟件的名稱,就可以開始實際卸載該軟件了。鍵入命令:

                #rpm -e [package name]

                即可卸載軟件。參數e的作用是使rpm進入卸載模式。對名為[package name]的軟件包進行卸載。由于系統中各個軟件包之間相互有依賴關系。如果因存在依賴關系而不能卸載,rpm將給予提示并停止卸載。你可以使用如下的命令來忽略依賴關系,直接開始卸載:

                #rpm -e [package name] -nodeps

                忽略依賴關系的卸載可能會導致系統中其它的一些軟件無法使用

                如果想知道rpm包安裝到哪里了呢?

                應該用 #rpm -ql [package name]

                3.如何卸載用源碼包安裝的軟件?

                最好是看README和INSTALL ;一般的情況下都有說,但大多軟件沒有提供源碼包的卸載方法;我們可以找到軟件的安裝點刪除。主要看你把它安裝在哪了。

                比如:

                如果安裝軟件時,指定個目錄。這個問題也不會難;

                比如用源碼包安裝gaim 的

                #./configure --prefix=/opt/gaim

                #make

                #make install

                如果安裝mlterm

                #./configure --prefix=/opt/mlterm

                #make

                #make install

                把源碼包安裝的軟件,都指定安裝在 /opt目錄中,這樣不就知道了??

                如果刪除,就刪除相應的軟件目錄;

                有些軟件要在解壓安裝目錄中執行 make uninstall ,這樣就卸載掉了

            posted @ 2009-08-06 15:26 chaosuper 閱讀(125) | 評論 (0)編輯 收藏

               用VC來寫程序,有時總是出這樣那樣的問題,沒辦法只能自己上網查資料來解決,在這里把自己常見的問題和一些技巧貼出來分享給大家,希望對大家有用,也省去大家再去搜索的煩惱……

                1.如何在Release狀態下進行調試

                Project->Setting=>ProjectSetting對話框,選擇Release狀態。C/C++標簽中的Category選General,Optimizations選Disable(Debug),Debut info選Program Database.在Link標簽中選中Generate debug info復選框。

                注:只是一個介乎Debug和Release的中間狀態,所有的ASSERT、VERIFY都不起作用,函數調用方式已經是真正的調用,而不查表,但是這種狀態下QuickWatch、調用隊列跟蹤功能仍然有效,和Debug版一樣。

                2. Release和Debug有什么不同

                Release版稱為發行版,Debug版稱為調試版。

                Debug中可以單步執行、跟蹤等功能,但生成的可執行文件比較大,代碼運行速度較慢。Release版運行速度較快,可執行文件較小,但在其編譯條件下無法執行調試功能。

                Release的exe文件鏈接的是標準的MFC DLL(Use MFC in a shared or static dll)。這些DLL在安裝Windows的時候,已經配置,所以這些程序能夠在沒有安裝Visual C++ 6.0的機器上運行。而Debug版本的exe鏈接了調試版本的MFC DLL文件,在沒有安裝Visual C++6.0的機器上不能運行,因為缺相應的DLL,除非選擇use static dll when link.

                3. ASSERT和VERIFY有什么區別

                ASSERT里面的內容在Release版本中不編譯,VERIFY里面的內容仍然翻譯,但不再判斷真假。所以后者更安全一點。例如ASSERT(file.Open(strFileName))。一旦到了Release版本中,這一行就忽略了,file根本就不Open()了,而且沒有任何出錯的信息。如果用VERIFY()就不會有這個問題。

                4.Workspace和Project之間是什么樣的關系

                每個Workspace可以包括幾個project,但只有一個處于Active狀態,各個project之間可以有依賴關系,在project的Setting……中可以設定,比如那個Active狀態的project可以依賴于其他的提供其函數調用的靜態庫。

                5. 如何在非MFC程序中使用ClassWizard

                在工程目錄下新建一個空的。RC文件,然后加入到工程中就可以了。

                6.如何設置斷點

                按F9在當前光標處增加一個斷點和取消一個斷點。另外,在編輯狀態下,按Ctrl+B組合鍵,彈出斷點設置對話框。然后單擊「Condition…」按鈕彈出設置斷點條件的對話框進行設置。

                7.在編輯狀態下發現成員變量或函數不能顯示提示是如何打開顯示功能

                這似乎是目前這個Visual C++ 6.0版本的一個bug,可按如下步驟使其正常,如再出現,可如法炮制:

                (1)關閉Project

                (2)刪除“工程名。ncb”文件

                (3)重新打開工程

                8.如何將一個通過ClassWizard生成的類徹底刪除

                首先在工作區的FileView中選中該類的。h和。cpp文件,按delete刪除,然后在文件管理器中將這兩個文件刪除,再運行ClassWizard,這時出現是否移走該類的提示,選擇remove就可以了。

                9. 如何將在workspace中消失的類找出來

                打開該類對應的頭文件,然后將其類名隨便改一下,這個時候工作區就會出現新的類,再將這個類改回原來的名字就可以了。

                10. 如何清除所有的斷點

                菜單「Edit」->「Breakpoints…」,打開“Breakpoints”對話框,單擊「Remove All」按鈕即可??旖萱I是“Ctrl + Shift + F8”。

                11. 如何再ClassWizard中選擇未列出的信息

                打開“ClassWizard”對話框,然后切換到“Class Info”頁面。改變“Message filter”,如選擇“Window”,“Message”頁面就會出現Window的信息。

                12. 如何檢測程序中的括號是否匹配

                把光標移動到需要檢測的括號前面,按快捷鍵“Ctrl + ]”。如果括號匹配正確,光標就跳到匹配的括號處,否則光標不移動,并且機箱喇叭還會發出一聲警告。

                13. 如何查看一個宏(或變量、函數)的定義

                把光標移動到要查看的一個宏上,就比如說最常見的DECLARE_MAP_MESSAGE上按一下F12(或右鍵菜單中的相關菜單),如果沒有建立瀏覽文件,就會出現提示對話框,按「確定」按鈕,然后就會跳到該宏(或變量、函數)定義的地方。

                14. 如何添加Lib文件到當前工程

                單擊菜單「Project」->「Settings…」彈出“Project Setting”對話框,切換到“Link”標簽頁,在“Object/library modules”處輸入Lib文件名稱,不同的Lib之間用空格格開。

                15. 如何快速刪除項目下的Debug文件夾中臨時文件

                在工作區的FileView視圖中選中對應的項目,單擊右鍵彈出菜單,選擇「Clean(selection only)」菜單即可。

                16. 如何快速生成一個現有工程除了工程名外完全相同的新工程

                在新建工程的“New”對話框中選擇“Custom Appwizard”項,輸入新工程的名字,單擊「OK」按鈕。出現“Custom AppWizard”項,輸入新工程的名字,單擊「OK」按鈕。出現“Custom AppWizard-Step 1 of 2”對話框,選擇“An existing Project”項,單擊「Next」按鈕。出現“Custom AppWizard-Step 2 of 2”對話框,選擇現有工程的工程文件名,最后單擊「Finish」按鈕。編譯后就生成一個與現有工程相同但可以重新取名的工程AppWizard.現在就可以項用MFC AppWizard一樣用這個定制的向導。如果不想用了,可以在Visual C++ 6.0安裝目錄下Common\MSDev98\Template目錄中刪除該Wizard對應的。awx和。pdb文件。

                17. 如何解決Visual C++ 6.0不正確連接的問題

                情景:明明改動了一個文件,卻要把整個項目全部重新編譯鏈接一次。剛剛鏈接好,一運行,又提示重新編譯鏈接一次。這是因為出現了未來文件(修改時間和創建時間比系統時間晚)的緣故??梢赃@樣處理:找到工程文件夾下的debug目錄,將創建和修改時間都比系統時間的文件全部刪除,然后再從新“Rebuild All”一次。

                18. 引起LNK2001的常見錯誤都有哪些

                遇到的LNK2001錯誤主要為:unresolved external symbol “symbol”,如果鏈接程序不能在所有的庫和目標文件內找到所引用的函數、變量或標簽,將產生此錯誤信息。

                一般來說,發生錯誤的原因有兩個:一是所引用的函數、變量不存在,拼寫不正確或者使用錯誤;其次可能使用了不同版本的鏈接庫。以下是可能產生LNK2001錯誤的原因:

                <1>由于編碼錯誤導致的LNK2001錯誤

                (1)不相匹配的程序代碼或模塊定義(。DEF)文件導致LNK2001.例如,如果在C++源文件了內聲明了一變量“var1”,卻試圖在另一個文件內以變量“var1”訪問改變量。

                (2)如果使用的內聯函數是在。cpp文件內定義的,而不是在頭文件內定義將導致LNK2001錯誤。

                (3)調用函數時如果所用的參數類型和頭函數聲明時的類型不符將會產生LNK2001錯誤。

                (4)試圖從基類的構造函數或析構函數中調用虛擬函數時將會導致LNK2001錯誤。

                (5)要注意函數和變量的可公用性,只有全局變量、函數是可公用的。靜態函數和靜態變量具有相同的使用范圍限制。當試圖從文件外部方位任何沒有在該文件內聲明的靜態變量時將導致編譯錯誤或LNK2001錯誤。

                <2>由于編譯和聯機的設置而造成的LNK2001錯誤

                (1)如果編譯時使用的是/NOD(/NODERAULTLIB)選項,程序所需要的運行庫和MFC時將得到又編譯器寫入目標文件模塊,但除非在文件中明確包含這些庫名,否則這些庫不會被鏈接進工程文件。這種情況下使用/NOD將導致LNK2001錯誤

                (2)如果沒有為wWinMainCRTStartup設定程序入口,在使用Unicode和MFC時將出現“unresolved external on _WinMain@16”的LNK2001錯誤信息。

                (3)使用/MD選項編譯時,既然所有的運行庫都被保留在動態鏈接庫之內,源文件中對“func”的引用,在目標文件里即對“__imp__func”的引用。如果試圖使用靜態庫LIBC.LIB或LIBCMT.LIB進行鏈接,將在__imp__func上發生LNK2001錯誤。如果不使用/MD選項編譯,在使用MSVCxx.LIB鏈接時也會發生LNK2001錯誤。

                (4)使用/ML選項編譯時,如用LIBCMT.LIB鏈接會在_errno上發生LNK2001錯誤。

                (5)當編譯調試版的應用程序時,如果采用發行版模態庫進行鏈接也會產生LNK2001錯誤;同樣,使用調試版模態庫鏈接發行版應用程序時也會產生相同的錯誤。

                (6)不同版本的庫和編譯器的混合使用也能產生問題,因為新版的庫里可能包含早先的版本沒有的符號和說明。

                (7)在不同的模塊中使用內聯和非內聯的編譯選項能夠導致LNK2001錯誤。如果創建C++庫時打開了函數內聯(/Ob1或/Ob2),但是在描述該函數的相應頭文件里卻關閉了函數內聯(沒有inline關鍵字),只是將得到錯誤信息。為避免該問題的發生,應該在相應的頭文件中用inline關鍵字標志為內聯函數。

                (8)不正確的/SUBSYSTEM或ENTRY設置也能導致LNK2001錯誤。

                19. 如何調試一個沒有源碼的exe文件調用的dll

                在Visual C++ 6.0中,進入“Project Setting”對話框然后選擇Debug標簽頁。通常Visual Studio默認“executable for debug session”為可執行文件名,但可以將他改成任何你想要的程序。甚至可以指定不同的工作目錄以及傳遞參數到你的程序。這個技術常用來調試Dlls、名字空間擴展、COM對象和其他從某些EXE以及從第三方的EXE中調用的plug-in程序。

                20.   Visual C++ 6.0工程中的項目文件都表示什么

                ·opt:工程關于開發環境的參數文件。如工具條位置等信息。

                ·aps(AppStudio File)資源輔助文件,二進制格式,一般不用去管它。

                ·clw:ClassWizard信息文件,實際上是INI文件格式,有興趣可以研究一下。有時候ClassWizard出了問題,手工修改CLW文件可以解決。如果此文件不存在的話,每次用ClassWizard的時候回提示是否重建。

                ·dsp(DevelopStudio Project):項目文件,文本格式,不過不熟悉的不要手工修改。

                ·dsw(DevelopStudio Workspace):是工作區文件,其他特點和。dsp差不多。

                ·plg:是編譯信息文件,編譯時的error和warning信息文件(實際上是一個html文件),一般用處不大。在單擊菜單「Tool」->「Option」彈出的對話框里面有個選項可以控制這個文件的生成。

                ·hpj(Help Project):是生成幫助文件的工程,用microsoft Help Compiler可以處理。

                ·mdp(Microsoft DevStudio Project):是舊版本的項目文件,如果要打開此文件的話,會提示你是否轉換成新的。dsp格式。

                ·bsc:是用于瀏覽項目信息的,如果用Source Brower的話就必須有這個文件。如果不用這個功能的話,可以在Project Options里面去掉Generate Browse Info File,這樣可以加快編譯速度。

                ·map是執行文件的映象信息記錄文件,除非對系統底層,這個文件一般用不著。

                ·pch(Pre-Compiled File):是與編譯文件,可以加快編譯速度,但是文件非常大。

                ·pdb(Program Database):記錄了程序有關的一些數據和調試信息,在調試的時候可能有用。

                ·exp:只有在編譯DLL的時候才會生成,記錄了DLL文件的一些信息,一般也沒有用。

                ·ncb:無編譯瀏覽文件(no compile browser)。當自動完成功能出問題時可以刪除此文件。編譯工程后會自動生成。

            posted @ 2009-08-06 15:24 chaosuper 閱讀(137) | 評論 (0)編輯 收藏

                1. 學會寫簡單的makefile

                2. 編一應用程序,可以用makefile跑起來

                3. 學會寫驅動的makefile

                4. 寫一簡單char驅動,makefile編譯通過,可以insmod, lsmod, rmmod. 在驅動的init函數里打印hello world, insmod后應該能夠通過dmesg看到輸出。

                5. 寫一完整驅動, 加上read, write, ioctl, polling等各種函數的驅動實現。 在ioctl里完成從用戶空間向內核空間傳遞結構體的實現。

                6. 寫一block驅動, 加上read,write,ioctl,poll等各種函數實現。

                7. 簡單學習下內存管理, 這個是最難的,明白各種memory alloc的函數實現細節。這是Linux開發的基本功。

                8. 學習鎖機制的應用,這個不是最難的但是最容易犯錯的,涉及到很多同步和并發的問題。

                9. 看內核中實際應用的驅動代碼。 你會發現最基本的你已經知道了, 大的框架都是一樣的, 無非是read, write, ioctl等函數的實現, 但里面包含了很多很多細小的實現細節是之前不知道的。 這時候就要考慮到很多別的問題而不僅僅是基本功能的實現。

                推薦您看2.6.20中integrated的一個驅動 kvm, 記得是在driver/lguest下,很好玩的, 就是Linux下的虛擬機驅動, 代碼不長,但功能強大。有能力的可以自己寫一操作系統按照要求做成磁盤鏡像加載到虛擬機中, 然后客戶機可以有自己的4G虛擬地址空間。

                10. 看完驅動歡迎您進入Linux kernel學習中來。

                最簡單的方法,跟著ldd(Linux devive driver)做一遍。

            posted @ 2009-08-06 06:16 chaosuper 閱讀(177) | 評論 (0)編輯 收藏

            Ubuntu 在安裝時,如同大部分 Linux 發行版一樣,都會同時安裝 GNU 版本的 Java。這個 Java 的實用程度太低,尤其對于開發人員來說,是沒有太多用處的。在 Ubuntu 下,安裝 SUN Java 是一件很容易的事情。第一步:
                sudo apt-get install sun-java5-jdk
            安裝完畢之后,選擇默認 java:
                sudo update-alternatives --config java
            然后配置環境變量:
                sudo vim /etc/environment
            在其中添加如下兩行:
                CLASSPATH=/usr/lib/jvm/java-1.5.0-sun/lib
                JAVA_HOME=/usr/lib/jvm/java-1.5.0-sun
            保存退出。

            之后安裝配置 Eclipse。安裝很簡單:
                sudo apt-get install eclipse
            然已經這時新安裝的 java 已經成為系統默認的 jvm,但是 Eclipse 并不會用 update-alternative 設置的 jvm 來啟動自身,而使用的是以前的 GNU Java。GNU Java 是 1.4.2 的實現,而且在性能上遠不如 SUN 的實現。為了讓 Eclipse 利用 SUN Java 啟動,我們還需要繼續配置。首先將 SUN Java 完完全全的設置為系統的默認 JDK:
                sudo update-java-alternatives -s java-1.5.0-sun
            然后編輯 JVM 配置文件:
                sudo vim /etc/jvm
            將文件中的
                /usr/lib/jvm/java-1.5.0-sun
            這一行移動到配置塊的頂部。由于 Eclipse 會忽略 Ubuntu 的通用 Java 設置(貌似一個 bug),我們需要繼續編輯 Eclipse 的 java_home 文件:
                sudo vim /etc/eclipse/java_home
            如同上面一樣,將
                /usr/lib/jvm/java-1.5.0-sun
            這一行移動到文件的頂部。

            所有的安裝配置完成之后,Ubuntu 的 Java 開發平臺就基本完備了。
            posted @ 2009-08-06 05:29 chaosuper 閱讀(69) | 評論 (0)編輯 收藏

                C++虛函數探索筆記(3)——延伸思考:虛函數應用的一些其他情形

                關注問題:

                虛函數的作用

                虛函數的實現原理

                虛函數表在對象布局里的位置

                虛函數的類的sizeof

                純虛函數的作用

                多級繼承時的虛函數表內容

                虛函數如何執行父類代碼

                多繼承時的虛函數表定位,以及對象布局

                虛析構函數的作用

                虛函數在QT中的應用

                虛函數與inline修飾符,static修飾符

                虛析構函數

                大家都知道,在C++里需要自己嚴格管理好資源的分配和回收。通常情況下,

            在一個對象被析構的時候,是要由其釋放其申請到的各種資源的。最常見的,當

            然就是內存資源啦。

                當只有一個類的時候,我們可以不用考慮太多,只要在析構函數里檢查并釋

            放所有申請到的資源即可。但是在這個類繼承了一個抽象接口基類時,就有點點

            不一樣了。讓我們看看類的析構過程:

                在大多數的類的使用時,通常都是直接刪除該類的實例對象,然后該類的析

            構函數就會被調用,從而使得這個類在析構函數里執行的資源釋放代碼被執行到

            。

                如果這個類繼承了其他類,那么編譯器還會在這個類的析構函數里自動添加

            對父類的析構函數的調用,從而將父類里申請的資源也進行釋放。如果偶多個父

            類,也會依次調用各個析構函數。

                倘若繼承的是一個抽象接口類,并且在程序運行期,可能通過一個基類指針

            將此對象釋放掉,那么致命而又隱藏的內存泄露BUG就出現啦……因為試圖刪除的

            是基對象,刪除時調用的是基類的析構函數,而基類的析構函數當然是不會去調

            用子類的析構函數的羅!

                讓我們看看下面的代碼,使用vs2008編譯并運行的時候,將會在程序運行結

            束時報告內存泄漏情況(如果要在linux下編譯測試,需要去掉第一行的include

            ,以及return前的_CrtDumpMemoryLeaks()函數,然后使用linux下檢查內存泄

            露的工具進行測試)。
             //Source filename: Win32Con.cpp
            #include
            class parent
            {
            public:
             parent() { }
             /*virtual */ ~parent() { }
            };

            class child:public parent
            {
            public:
             child()
             {
              p=new char[1000];
             }
             ~child()
             {
              delete[] p;
             }
             char *p;
            };

            void free_child(parent *pp)
            {
             delete pp;
            }

            int main()
            {
             child *obj=new child();
             free_child(obj);
             _CrtDumpMemoryLeaks();
             return 0;
            }

             


                在這段代碼里我們創建的是一個child類型的對象,然后使用free_child

            (parent*)函數來試圖釋放這個對象,這個時候,只會調用到parent::

            ~parent()這個析構函數,而不會調用到child::~child()!

                如何解決這個問題呢?

                很簡單的,只要在parent::~parent()前增加 virtual關鍵字,將其變成

            一個虛函數。這樣,無論是以這個對象的父類指針進行刪除的時候,就會從虛函

            數表里定位到子類child的析構函數,這樣就能夠從子類開始一級一級的向上調用

            析構函數,從而正確的將這個對象在各個繼承層次上申請的所有資源都釋放掉。

                正因為這個原因,在很多C++編程原則的文章或者書里都會提到這樣的原則:

                如果一個類要被設計為可被繼承的基類,那么其析構函數應該被聲明為虛函

            數。

               虛函數在QT中的應用

                在QT里虛函數的應用非常的廣泛,事實上,在大多數的C++類庫里都不可避免

            的要使用到虛函數。這里簡單的列舉QT里使用虛函數的情況:

                QT的事件機制

                是使用了虛函數的,你因此才可以自定義事件處理函數。比如最核心的

            QObject類的定義里(在qobject.h里),我們可以看到如下的虛函數定義:

                virtual bool event(QEvent *);

                virtual bool eventFilter(QObject *, QEvent *);

                然后,在QWidget類繼承QObject類后重新實現了上面的兩個虛函數,完成很

            多窗口控件類的缺省事件處理。

                當你要編寫自定義的QT控件的時候,對event虛函數的重新實現就更是重要啦

            。

                QT的信號和槽

                QT的槽函數可以被聲明為虛函數,所以雖然QT在實現信號和槽機制的時候可

            能出于效率或者運行代價的原因未采用虛函數機制,但是我們依然可以在必要的

            時候使用虛函數來完成一些特定功能。比如為一些自定義控件類抽象出來一個抽

            象接口基類,在做信號和槽的連接的時候是對基類指針進行操作,而在基類里的

            槽定義為虛函數,那么虛函數在此依然可以實現信號與槽的多態。

                然而虛函數在調用的時候,一定要經歷查表的步驟,是存在一定的運行開銷

            的,對于一些非常頻繁的槽調用還是應該考慮到使用虛函數產生的代價的。

                其他

                在虛函數上,static和inline這兩個關鍵詞與virtual顯得很不友好。

                從語義上即可看出,static和virtual完全就是沖突的,所以如果你試圖為一

            個虛函數增加一個static限定詞,那么你的C++編譯器就會很負責任的報告一個嚴

            重錯誤給你。

                而inline的含義和虛函數其實也是非常沖突的,但是inline在語法上只是給

            編譯器一個建議,而不是強制的語義限定,所以C++編譯器應該會忽略掉inline關

            鍵詞,繼續正常的編譯。

             

            posted @ 2009-08-05 17:47 chaosuper 閱讀(302) | 評論 (0)編輯 收藏

                多繼承與虛函數重復

                既然說到了多繼承,那么還有一個問題可能會需要解決,那就是如果兩個父類里都有相同的虛函數定義,在子對象的布局里會是怎么樣個情況?是否依然可以將這個虛函數指向到正確的實現代碼上呢?

                修改前面一個源代碼,在parent2的接口里增加下面的虛函數定義:

                virtual int fun1(){cout<<"parent2::fun1()"<<endl;return 0;};

                上面的fun1的定義與parent1類里的完全重復相同(類型,參數列表),增加上面的代碼后立即開始編譯,程序正常編譯通過。運行之,得到下面的結果:

                child1::fun1()

                child1::fun2()

                這個程序居然正確的完成了執行,編譯器在其中做了些怎樣的工作,是怎么樣避免掉隊fun1函數的沖突問題呢?

                讓我們來看看這個時候的child1的對象布局:
             class child1    size(8):
                    +---
                    | +--- (base class parent1)
             0      | | {vfptr}
                    | +---
                    | +--- (base class parent2)
             4      | | {vfptr}
                    | +---
                    +---

            child1::$vftable@parent1@:
                    | &child1_meta
                    |  0
             0      | &child1::fun1

            child1::$vftable@parent2@:
                    | -4
             0      | &child1::fun2
             1      | &thunk: this-=4; goto child1::fun1

            child1::fun1 this adjustor: 0
            child1::fun2 this adjustor: 4

             


                恩~~~還是兩個vfptr在child1的對象布局里(不一樣就怪啦,呵呵),但是第二個vfptr所指的虛函數表的內容有所變化哦!

                注意看紅色字體部分,虛函數表里并沒有直接填寫child::fun1的代碼,而是多了一個 &thunk: this-=4;然后才goto child1::fun1!注意到一個關鍵名詞thunk了吧?沒錯,vc在這里使用了名為thunk的技術,避免了虛函數fun1在兩個基類里重復出現導致的沖突問題?。ǔ藅hunk,還有其他方法可以解決此類問題的)。

                現在,我們知道為什么相同的虛函數不會在子類里出現沖突的情況了。

                但是,倘若我們在基類里就是由兩個沖突的普通函數,而不是虛函數,是個怎樣的情況呢?

                多繼承產生的沖突與虛繼承,虛基類

                我們在parent1和parent2里添加一個相同的函數void fun3(),然后再進行編譯,通過了!查看類對象布局,跟上面的完全一致。但是在main函數里調用chobj.fun3()的時候,編譯器卻不再能正確編譯了,并且會提示“error C2385: 對”fun3“的訪問不明確”的錯誤信息,沒錯,編譯器不知道你要訪問哪個fun3了。

                如何解決這樣的多繼承帶來的問題呢,其實有一個簡單的做法。就是在方法前限定引用的具體是哪個類的函數,比如:chobj.parent1::fun3();  ,這樣的寫法就寫明了是要調用chobj的父類parent1里的fun3()函數!

                我們再看看另外一種情況,從parent1和parent2里抹去剛才添加的fun3函數,將之放到一個共同的基類里:
                 class commonbase

                {

                public:

                void fun3(){cout<<"commonbase::fun3()"<<endl;}

                };
             


                而parent1和parent2都修改為從此類繼承??梢钥吹剑谶@個情況下,依然需要使用chobj.parent1::fun3();  的方式才可以正確調用到fun3,難道,在這種情況下,就不能自然的使用chobj.fun3()這樣的方式了嗎?

                虛繼承可以解決這個問題——我們在parent1和parent2繼承common類的地方添加上一個關鍵詞virtual,如下:
                 class parent1:virtual public commonbase

                {

                public:

                virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};

                };
             


                給parent2也同樣的處理,然后再次編譯,這次chobj.fun3()可以編譯通過了?。。?/p>

                編譯器這次又在私下里做了哪些工作了呢????
             class child1    size(16):
                    +---
                    | +--- (base class parent1)
             0      | | {vfptr}
             4      | | {vbptr}
                    | +---
                    | +--- (base class parent2)
             8      | | {vfptr}
            12      | | {vbptr}
                    | +---
                    +---
                    +--- (virtual base commonbase)
                    +---

            child1::$vftable@parent1@:
                    | &child1_meta
                    |  0
             0      | &child1::fun1

            child1::$vftable@parent2@:
                    | -8
             0      | &child1::fun2
             1      | &thunk: this-=8; goto child1::fun1

            child1::$vbtable@parent1@:
             0      | -4
             1      | 12 (child1d(parent1+4)commonbase)

            child1::$vbtable@parent2@:
             0      | -4
             1      | 4 (child1d(parent2+4)commonbase)

            child1::fun1 this adjustor: 0
            child1::fun2 this adjustor: 8

            vbi:       class  offset o.vbptr  o.vbte fVtorDisp
                  commonbase      16       4       4 0

             


                這次變化可大了去了!!!

                首先,可以看到兩個類parent1和parent2的對象布局里,都多了一個vbptr的指針。而在child1的對象布局里,還有一個virtual base commonbase的虛擬基類。再看看兩個vbptr的內容:

                12 (child1d(parent1+4)commonbase) 這個很好理解,從parent1的vbptr開始,偏移12個字節,指向的是virtual base commonbase!

                再看看4 (child1d(parent2+4)commonbase) ,從parent2的vbptr開始,便宜4個字節,也指向了virtual base commonbase!

                這下明白了。虛基類在child1里只有一個共同的對象布局了,所以就可以直接用chobj.fun3()啦,當然,在commonbase里的其他成員變量此時也可以同樣的方式訪問了!

                雖然解決方案有了,但是在一個系統的設計里,如果有一個基類出現多繼承沖突的情況,大部分情況下都說明這樣的設計是有問題的,應該盡量避免這樣的設計,并且盡量用純虛函數,來提取一些抽象的接口類,把共同的方法接口都抽取出來,通常就能避免多繼承的問題。

             

            posted @ 2009-08-05 17:46 chaosuper 閱讀(144) | 評論 (0)編輯 收藏

              C++虛函數探索筆記(2)——虛函數與多繼承

                關注問題:

                虛函數的作用

                虛函數的實現原理

                虛函數表在對象布局里的位置

                虛函數的類的sizeof

                純虛函數的作用

                多級繼承時的虛函數表內容

                虛函數如何執行父類代碼

                多繼承時的虛函數表定位,以及對象布局

                虛析構函數的作用

                虛函數在QT的信號與槽中的應用

                虛函數與inline修飾符,static修飾符

                前面我們嘗試了一個簡單的例子,接下來嘗試一個多級繼承的例子,以及一個多繼承的例子。主要涉及到以下問題:多級繼承時虛函數表的內容是如何填寫的,如何在多級繼承的情況下調用某一級父類里的虛函數,以及在多繼承(多個父類)的情況下的對象布局。

                多級繼承

                在這里,多級繼承指的是有3層或者多層繼承關系的情形。讓我們看看下面的代碼:
             //Source filename: Win32Con.cpp

             #include <iostream>
             using namespace std;
            class parent1
             {
            public:
                virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
                virtual int fun2()=0;
            };
            class child1:public parent1
             {
            public:

                virtual int fun1()
                {
                    cout<<"child1::fun1()"<<endl;
                    parent1::fun1();
                    return 0;
                }
                virtual int fun2()
                {
                    cout<<"child1::fun2()"<<endl;
                    return 0;
                }
            };

            class grandson:public child1
             {
            public:
                virtual int fun2()
                {
                    cout<<"grandson::fun2()"<<endl;
                    //parent1::fun2();
                    parent1::fun1();
                    child1::fun2();
                    return 0;
                }
            };

            void test_func1(parent1 *pp)
            {
                pp->fun1();
                pp->fun2();
            }

            int main(int argc, char* argv[])
            {
                grandson sunzi;
                test_func1(&sunzi);
                return 0;

             


                這段代碼展示了三個class,分別是parent1,child1,grandson.

                類parent1定義了兩個虛函數,其中fun2是一個純虛函數,這個類是一個不可實例化的抽象基類。

                類child1繼承了parent1,并且對兩個虛函數fun1和fun2都編寫了實現的代碼,這個類可以被實例化。

                類grandson繼承了child1,但是只對虛函數fun2編寫了實現的代碼。

                此外,我們還改寫了test_func1函數,它的參數為parent1類型的指針,我們可以將parent1的子孫類作為這個函數的參數傳入。在這個函數里,我們將依次調用parent1類的兩個虛函數。

                可以先通過閱讀代碼預測一下程序的輸出內容。

                程序的輸出內容將是:

                child1::fun1()

                parent1::fun1()

                grandson::fun2()

                parent1::fun1()

                child1::fun2()

                先看第一行輸出child1::fun1(),為什么會輸出它呢?我們定義的具體對象sunzi是grandson類型的,test_func1的參數類型是parent1類型。在調用這個虛函數的時候,是完成了一次怎樣的調用過程呢?

                讓我們再次使用cl命令輸出這幾個類的對象布局:
             class parent1   size(4):
                    +---
             0      | {vfptr}
                    +---

            parent1::$vftable@:
                    | &parent1_meta
                    |  0
             0      | &parent1::fun1
             1      | &parent1::fun2

            parent1::fun1 this adjustor: 0
            parent1::fun2 this adjustor: 0

            class child1    size(4):
                    +---
                    | +--- (base class parent1)
             0      | | {vfptr}
                    | +---
                    +---

            child1::$vftable@:
                    | &child1_meta
                    |  0
             0      | &child1::fun1
             1      | &child1::fun2

            child1::fun1 this adjustor: 0
            child1::fun2 this adjustor: 0

            class grandson  size(4): //grandson的對象布局
                    +---
                    | +--- (base class child1)
                    | | +--- (base class parent1)
             0      | | | {vfptr}
                    | | +---
                    | +---
                    +---

            grandson::$vftable@:  //grandson虛函數表的內容
                    | &grandson_meta
                    |  0
             0      | &child1::fun1
             1      | &grandson::fun2

            grandson::fun2 this adjustor: 0

             


                因為我們實例化的是一個grandson對象,讓我們看看它的對象布局。正如前面的例子一樣,里面只有一個vfptr指針,但是不一樣的卻是這個指針所指的虛函數表的內容:

                第一個虛函數,填寫的是child1類的fun1的地址;第二個虛函數填寫的才是grandson類的fun2的地址。

                很顯然我們可以得出這樣一個結論:在一個子對象的虛函數表里,每一個虛函數的實際運行的函數地址,將填寫為在繼承體系里最后實現該虛函數的函數地址。

                所以當我們在test_func1里調用了傳入的parent1指針的fun1函數的時候,我們實際執行的是填寫在虛函數表里的child1::fun1(),而調用fun2函數的時候,是從虛函數表里得到了grandson::fun2函數的地址并調用之。在“程序輸出結果”表里的第一行和第三行結果證實了上述結論。

                再看一下程序代碼部分的child1::fun1()的實現代碼,在第18行,我們有parent1::fun1();這樣的語句,這行代碼輸出了運行結果里的第二行,而在grandson::fun2()的實現代碼第35行的parent1::fun1();以及第36行的child1::fun2();則輸出了運行結果里的第四行和第五行的內容。這三行代碼展示了如何調用父類以及更高的祖先類里的虛函數?!聦嵣希@與調用父類的普通函數沒有任何區別。

                在程序代碼的第34行,有一行被注釋了的內容//parent1::fun2();,之所以會注釋掉,是因為這樣的代碼是無法通過編譯的,因為在parent1類里,fun2是一個“純虛函數”也就是說這個函數沒有代碼實體,在編譯的時候,鏈接器將無法找到fun2的目標代碼從而報錯。

                其實有了對虛函數的正確的認識,上面的多級繼承是很自然就能明白的。然而在多繼承的情況下,情況就有所不同了……

                多繼承下虛函數的使用

                假如一個類,它由多個父類繼承而來,而在不同的父類的繼承體系里,都存在虛函數的時候,這個類的對象布局又會是怎樣的?它又是怎樣定位虛函數的呢?

                讓我們看看下面的代碼:
             //Source filename: Win32Con.cpp
             #include <iostream>
             using namespace std;
            class parent1
             {
            public:
                virtual int fun1(){cout<<"parent1::fun1()"<<endl;return 0;};
            };
            class parent2
             {
            public:
                virtual int fun2(){cout<<"parent2::fun2()"<<endl;return 0;};
            };
            class child1:public parent1,public parent2
             {
            public:
                virtual int fun1()
                {
                    cout<<"child1::fun1()"<<endl;
                    return 0;
                }
                virtual int fun2()
                {
                    cout<<"child1::fun2()"<<endl;
                    return 0;
                }
            };
            void test_func1(parent1 *pp)
            {
                pp->fun1();
            }
            void test_func2(parent2 *pp)
            {
                pp->fun2();
            }
            int main(int argc, char* argv[])
            {
                child1 chobj;
                test_func1(&chobj);
                test_func2(&chobj);
                return 0;
            }
             


                這一次,我們有兩個父類,parent1和parent2,在parent1里定義了虛函數fun1,而在parent2里定義了虛函數fun2,然后我們有一個子類child1,在里面重新實現了fun1和 fun2兩個虛函數。然后我們編寫了test_func1函數來調用parent1類型對象的fun1函數,編寫了test_func2函數調用parent2對象的fun2函數。在main函數里我們實例化了一個child1類型的對象chobj,然后分別傳給test_func1和test_func2去執行。

                這段代碼的運行結果非常簡單就能看出來:

                child1::fun1()

                child1::fun2()

                但是,讓我們看看對象布局吧:
             class child1    size(8):
                    +---
                    | +--- (base class parent1)
             0      | | {vfptr}
                    | +---
                    | +--- (base class parent2)
             4      | | {vfptr}
                    | +---
                    +---

            child1::$vftable@parent1@:
                    | &child1_meta
                    |  0
             0      | &child1::fun1

            child1::$vftable@parent2@:
                    | -4
             0      | &child1::fun2

            child1::fun1 this adjustor: 0
            child1::fun2 this adjustor: 4

             


                注意到沒?在child1的對象布局里,出現了兩個vfptr指針!

                這兩個虛函數表指針分別繼承于parent1和parent2類,分別指向了不同的兩個虛函數表。

                問題來了,當我們使用test_func1調用parent1類的fun1函數的時候,調用個過程還比較好理解,可以從傳入的地址參數取得繼承自parent1的vfptr,從而執行正確的fun1函數代碼,但是當我們調用test_func2函數的時候,為什么程序可以自動取得來自parent2的vfptr呢,從而得出正確的fun2函數的地址呢?

                其實,這個工作是編譯器自動根據實例的類型完成的,在編譯階段就已經確定了在調用test_func2的時候,傳入的this指針需要增加一定的偏移(在這里則是第一個vfptr所占用的大小,也就是4字節)。

                我們可以看看main函數里這部分代碼的反匯編代碼:
               child1 chobj;
            00F5162E 8D 4D F4         lea         ecx,[chobj]
            00F51631 E8 F5 FB FF FF   call        child1::child1 (0F5122Bh)
                test_func1(&chobj);
            00F51636 8D 45 F4         lea         eax,[chobj]
            00F51639 50               push        eax
            00F5163A E8 6F FB FF FF   call        test_func1 (0F511AEh)
            00F5163F 83 C4 04         add         esp,4
                test_func2(&chobj);
            00F51642 8D 45 F4         lea         eax,[chobj]
            00F51645 85 C0            test        eax,eax
            00F51647 74 0E            je          main+47h (0F51657h)
            00F51649 8D 4D F4         lea         ecx,[chobj]
            00F5164C 83 C1 04         add         ecx,4
            00F5164F 89 8D 2C FF FF FF mov         dword ptr [ebp-0D4h],ecx
            00F51655 EB 0A            jmp         main+51h (0F51661h)
            00F51657 C7 85 2C FF FF FF 00 00 00 00 mov         dword ptr [ebp-0D4h],0
            00F51661 8B 95 2C FF FF FF mov         edx,dword ptr [ebp-0D4h]
            00F51667 52               push        edx
            00F51668 E8 F6 FA FF FF   call        test_func2 (0F51163h)
            00F5166D 83 C4 04         add         esp,4
                return 0;


                從第4行至第5行,執行的是test_func1函數,this指針指向 chobj (第2行lea ecx,[chobj]),但是調用test_func2函數的時候,this指針被增加了4(第14行)!于是,在test_func2執行的時候,就可以從&chobj+4的地方獲得vfptr指針,從而根據parent2的對象布局得到了fun2的地址并執行了。

                為了證實這點,我們可以將代碼做如下的修改:
                 1:  int main(int argc, char* argv[])

                2:  {

                3:      child1 chobj;

                4:      test_func1(&chobj);

                5:      test_func2((parent2 *)(void *)&chobj);

                6:      return 0;

                7:  }

                8:
             


                請注意紅色部分的變化,在講chobj傳入給test_func2之前,先用(void *)強制轉換為無類型指針,再轉換為parent2 指針,這樣的轉換,顯然是可行的,因為chobj本身就是parent2的子類,然而,程序的執行效果卻是:

                child1::fun1()

                child1::fun1()

                執行test_func2函數,調用的是parent2::fun2,但是居然執行的是child1::fun1()函數!!!

                這中間發生了些什么呢?我們再看看反匯編的代碼:
               child1 chobj;
            013D162E 8D 4D F4         lea         ecx,[chobj]
            013D1631 E8 F5 FB FF FF   call        child1::child1 (13D122Bh)
                test_func1(&chobj);
            013D1636 8D 45 F4         lea         eax,[chobj]
            013D1639 50               push        eax
            013D163A E8 6F FB FF FF   call        test_func1 (13D11AEh)
            013D163F 83 C4 04         add         esp,4
                test_func2((parent2*)(void *)&chobj);
            013D1642 8D 45 F4         lea         eax,[chobj]
            013D1645 50               push        eax
            013D1646 E8 18 FB FF FF   call        test_func2 (13D1163h)
            013D164B 83 C4 04         add         esp,4
                return 0;
             


                從調用test_func2的反匯編代碼可以看到,這一次ecx寄存器的值沒有做改變!所以在執行test_func2的時候,將取得parent1對象布局里的vfptr,而這個vfptr所指的虛函數表里的第一項就是fun1,并且被填寫為child1::fun1的地址了。所以才出現了child::fun1的輸出內容!顯然這里有一個隱藏的致命問題,加入parent1和parent2的第一個虛函數的參數列表不一致,這樣的調用顯然就會導致堆棧被破壞掉,程序99%會立即崩潰。之前的程序沒有崩潰并且成功輸出內容,不過是因為parent1::fun1()和parent2::fun2()的參數列表一致的關系而已。

                所以,千萬不要在使用一個多繼承對象的時候,將其類型信息丟棄,編譯器還需要依靠正確的類型信息,在使用虛函數的時候來得到正確的匯編代碼!

             

             

            posted @ 2009-08-05 17:45 chaosuper 閱讀(180) | 評論 (0)編輯 收藏

            僅列出標題
            共12頁: First 3 4 5 6 7 8 9 10 11 Last 
            精品久久久久一区二区三区 | 精品综合久久久久久88小说| 久久99精品久久久久久 | 香蕉aa三级久久毛片| 亚洲人成网站999久久久综合| 亚洲中文字幕久久精品无码APP| 久久亚洲精品国产精品| 精品久久久久久无码国产| 亚洲精品午夜国产va久久| 久久久女人与动物群交毛片| 久久精品一区二区影院| 久久久久亚洲av综合波多野结衣| 久久96国产精品久久久| 亚洲国产日韩综合久久精品| 97久久精品无码一区二区天美| 久久精品国产精品亚洲人人| 国内精品久久久久影院一蜜桃| 久久精品国产亚洲5555| 久久Av无码精品人妻系列| 久久一区二区三区99| 亚洲伊人久久综合影院| 国产美女久久久| 无码日韩人妻精品久久蜜桃| 香蕉久久永久视频| 精品久久久久久国产三级| 久久久精品免费国产四虎| 国产精品99久久久精品无码| 一本一道久久精品综合| 欧洲精品久久久av无码电影| 人人妻久久人人澡人人爽人人精品| www.久久99| 精品久久久久久国产潘金莲| 99蜜桃臀久久久欧美精品网站| 久久综合九色欧美综合狠狠| 久久精品无码一区二区三区日韩| 精品久久久久久国产潘金莲| AV色综合久久天堂AV色综合在| 亚洲AV日韩精品久久久久| 亚洲午夜久久久久妓女影院| 波多野结衣AV无码久久一区| 中文字幕久久波多野结衣av|