• <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>
            posts - 319, comments - 22, trackbacks - 0, articles - 11
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

                 摘要: 時常在cpp的代碼之中看到這樣的代碼:   view plaincopy to clipboardprint? #ifdef __cplusplus    extern "C" {    #endif    /...  閱讀全文

            posted @ 2011-04-19 23:00 RTY 閱讀(180) | 評論 (0)編輯 收藏

              靜態鏈接庫(Lib)和動態鏈接庫(DLL)的問題困擾了我很長時間,而當中關鍵的問題是兩者有何聯系?又有何區別呢?怎么創建?怎么使用?使用的過程中要注意什么?一直想把這個問題總結一下。

              在windows下一般可以看到后綴為dll和后綴為lib的文件,但這兩種文件可以分為三種庫,分別是動態鏈接庫(Dynamic-Link Libraries),目標庫(Object Libraries)和導入庫(Import Libraries),下面一一解釋這三種庫。

            目標庫(Object Libraries)

            目標庫又叫靜態鏈接庫,是擴展名為.LIB的文件,包括了用戶程序要用到的各種函數。它在用戶程序進行鏈接時,“靜態鏈接”到可執行程序文件當中。例如,在VC++中最常使用到的C運行時目標庫文件就是LIBC.LIB。在鏈接應用程序時常使用所謂“靜態鏈接”的方法,即將各個目標文件(.obj)、運行時函數庫(.lib)以及已編譯的資源文件(.res)鏈接到一起,形成一個可執行文件(.exe)。使用靜態鏈接時,可執行文件需要使用的各種函數和資源都已包含到文件中。這樣做的缺點是對于多個程序都使用的相同函數和資源要重復鏈接到exe文件中,使程序變大、占用內存增加。  

            導入庫(Import Libraries)

            導入庫是一種特殊形式的目標庫文件形式。和目標庫文件一樣,導入庫文件的擴展名也是.LIB,也是在用戶程序被鏈接時,被“靜態鏈接”到可執行文件當中。但是不同的是,導入庫文件中并不包含有程序代碼。相應的,它包含了相關的鏈接信息,幫助應用程序在可執行文件中建立起正確的對應于動態鏈接庫的重定向表。比如KERNEL32.LIB、USER32.LIB和GDI32.LIB就是我們常用到的導入庫,通過它們,我們就可以調用Windows提供的函數了。如果我們在程序中使用到了Rectangle這個函數,GDI32.LIB就可以告訴鏈接器,這個函數在GDI32.DLL動態鏈接庫文件中。這樣,當用戶程序運行時,它就知道“動態鏈接”到GDI32.DLL模塊中以使用這個函數。其實說白了導入庫就是一個索引,一個dll動態鏈接庫的索引表,這是我的理解。

            動態鏈接庫(Dynamic-Link Libraries)

            “動態鏈接”是將一些公用的函數或資源組織成動態鏈接庫文件(.dll),當某個需要使用dll中的函數或資源的程序啟動時(準確的說是初始化時),系統將該dll映射到調用進程的虛擬地址空間、增加該dll的引用計數值,然后當實際使用到該dll時操作系統就將該dll載入內存;如果使用該dll的所有程序都已結束,則系統將該庫從內存中移除。使用同一dll的各個進程在運行時共享dll的代碼,但是對于dll中的數據則各有一份拷貝(當然也有在dll中共享數據的方法)。 動態鏈接庫中可以定義兩種函數:輸出函數和內部函數。輸出函數可以被其他模塊調用,內部函數只能被動態鏈接庫本身調用。動態鏈接庫也可以輸出數據,但這些數據通常只被它自己的函數所使用。

              如我們所知,Windows程序都是一些可執行文件,它們可以創建并顯示一個或多個窗體,使用消息循環來接收用戶的輸入。但是動態鏈接庫并不能直接被執行,它們一般也不會接收消息。它們只是一些包含著函數的獨立文件,這些函數可以被Windows程序或者其它DLL調用以完成某項任務。 
              “動態鏈接”是指Windows程序在運行時才把自己需要存在于某個庫中的函數鏈接進來。“靜態鏈接”是指Windows程序在編譯階段就把各種對象模塊(.OBJ)、運行時庫(.LIB)和資源文件(.RES)鏈接到一起以創建一個可執行文件(.EXE)。 
              DERNAL32.DLL,USER32.DLL,GDI32.DLL,各種驅動程序如KEYBOARD.DRV,SYSTEM.DRV和MOUSE.DRV,顯卡和打印機驅動程序等都是動態鏈接庫。這些庫可以被所有的Windows程序共同使用。 
            有某些動態鏈接庫(如字體文件)稱為“resource-only”。它們只包括數據,而不包括代碼。因此,動態鏈接庫的目的之一就是為許多不同的程序提供函數和資源。在傳統的操作系統里,用戶程序在運行時只能調用操作系統自身的某些函數。而在Windows操作系統下,模塊或程序調用另一個模塊中的函數來執行是一種非常普遍的操作。因此,從某種角度看,對DLL進行編程,其實是在對Windows操作系統作擴展,也可以看作是在對用戶程序作擴展。 
              動態鏈接庫模塊可以有其它的擴展名,但是標準的擴展名是.DLL。只有具有標準擴展句的動態鏈接庫模塊才可以被Windows自動加載。而如果是其它擴展名的動態鏈接庫模塊,程序必須使用LoadLibrary或者LoadLibraryEx函數來顯示加載。 
              我們可以發現,在大型的應用軟件中,會常常使用到動態鏈接庫技術。舉個例子,假如我們要寫一個大型的應用軟件,其中包括了多個程序。我們可以發現很多程序可能都會使用到一些同樣的通用的函數。我們可以把這些通用的函數放到某個目標庫文件中(.LIB),然后在鏈接是把它加到每個程序中進行靜態鏈接。但是這是一種非常浪費的方法,因為每個程序模塊中都會包括這些通用函數的獨立拷貝。另外,如果我們要改變庫文件中的某個函數,就必須把所有使用到這個函數的程序都重新編譯一遍。但是,如果我們使用動態鏈接庫的技術,把所有這些通用函數都放到一個動態鏈接庫文件當中,我們就可以解決以上提到的各種問題。首先,動態鏈接庫在硬盤上只保留一個拷貝,程序只是在運行時才會調用其中使用到的函數,這樣我們就可以節省大量的程序存儲和運行空間。其次,如果要修改某個通用函數時,只要調用接口沒有改變,只是改變它的實現方法,那么我們就不必對每個用到它的程序都進行重新編譯,而只要把動態鏈接庫模塊重新編譯一遍就可以了。 
              動態鏈接庫模塊也可以作為一個單獨的產品來發布。這樣程序開發人員就可以使用第三方的模塊來開發自己的應用程序,提高了程序的復用程序,也節省了大量的時間和精力。

               目標庫和導入庫都是在程序開發過程中才使用到的,而動態鏈接庫是在程序運行時才使用的。在程序運行時,相應的動態鏈接庫文件必須已經保存在硬盤上了。另外,如果要使用動態鏈接庫文件,該文件必須要保存在同.EXE文件同一個目錄下,或者保存在當前目錄、Windows系統目錄、Windows目錄或環境變量中PATH參數指定的目錄下。程序也是按照這個順序來搜尋它需要的動態鏈接庫文件的。

            創建靜態鏈接庫(Lib)

              創建靜態鏈接庫比較簡單,創建win32控制臺程序,選擇靜態庫,這里我沒有選擇上預編譯頭。生成工程以后就像定義一般的函數般,定義放在頭文件,然后實現放在cpp文件里頭,直接build就出來一個靜態的lib了,發布時附上頭文件給使用者就可以。

             

            創建動態鏈接庫(DLL)

             

            用SDK創建一個簡單的dll文件

            在VC++中選擇新建一個Win32 Dynamic-Link Library。需要建立一個c/c++ head file和一個c/c++ source file并加入工程。頭文件中內容為輸出函數的聲明,源文件中內容為DllMain函數和輸出函數的定義。下面是一個最簡單的例子。

             

             

            頭文件代碼如下: 

             

            代碼
            #ifdef TEST_CREATE_DLL_EXPORTS
            #define TEST_CREATE_DLL_API __declspec(dllexport)
            #else
            #define TEST_CREATE_DLL_API __declspec(dllimport)
            #endif

            // This class is exported from the test_create_dll.dll
            class TEST_CREATE_DLL_API Ctest_create_dll {
            public:
            Ctest_create_dll(void);
            // TODO: add your methods here.
            };

            extern TEST_CREATE_DLL_API int ntest_create_dll;

            TEST_CREATE_DLL_API int fntest_create_dll(void);

             

            在創建工程的時候????TEST_CREATE_DLL_EXPORTS就已經在預定義處定義過,生成導出dll。

            頭文件預處理中的__declspec是微軟增加的“C擴展類存儲屬性”(C Extended Storage-Class Attributes),它指明一個給出的實例被存儲為一種微軟特定的類存儲屬性,可以為thread,naked,dllimport或dllexport. [MSDN原文:The extended attribute syntax for specifying storage-class information uses the __declspec keyword, which specifies that an instance of a given type is to be stored with a Microsoft-specific storage-class attribute (thread, naked, dllimport, or dllexport).] 輸出函數必須指明為CALLBACK。 DllMain是dll的入口點函數。也可以不寫它。DllMain必須返回TRUE,否則系統將終止程序并彈出一個“啟動程序時出錯”對話框。 編譯鏈接后,得到動態鏈接庫文件dlldemo.dll和輸入庫文件dlldemo.lib。

            _declspec(dllexport) 

            聲明一個導出函數,是說這個函數要從本DLL導出。我要給別人用。一般用于dll中 。
            省掉在DEF文件中手工定義導出哪些函數的一個方法。當然,如果你的DLL里全是C++的類的話,你無法在DEF里指定導出的函數,只能用__declspec(dllexport)導出類。

             

            __declspec(dllimport)

            聲明一個導入函數,是說這個函數是從別的DLL導入。我要用。一般用于使用某個dll的exe中 。
            不使用 __declspec(dllimport) 也能正確編譯代碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因為它可以確定函數是否存在于 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨 DLL 邊界的函數調用中。但是,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量。

            相信寫WIN32程序的人,做過DLL,都會很清楚__declspec(dllexport)的作用,它就是為了省掉在DEF文件中手工定義導出哪些函數的一個方法。當然,如果你的DLL里全是C++的類的話,你無法在DEF里指定導出的函數,只能用__declspec(dllexport)導出類。但是,MSDN文檔里面,對于__declspec(dllimport)的說明讓人感覺有點奇怪,先來看看MSDN里面是怎么說的:

            不使用 __declspec(dllimport) 也能正確編譯代碼,但使用 __declspec(dllimport) 使編譯器可以生成更好的代碼。編譯器之所以能夠生成更好的代碼,是因為它可以確定函數是否存在于 DLL 中,這使得編譯器可以生成跳過間接尋址級別的代碼,而這些代碼通常會出現在跨 DLL 邊界的函數調用中。但是,必須使用 __declspec(dllimport) 才能導入 DLL 中使用的變量。

            extern "C"   

            指示編譯器用C語言方法給函數命名。

            在制作DLL導出函數時由于C++存在函數重載,因此__declspec(dllexport)    function(int,int)    在DLL會被decorate,例如被decorate成為function_int_int,而且不同的編譯器decorate的方法不同,造成了在用GetProcAddress取得function地址時的不便,使用extern "C"時,上述的decorate不會發生,因為C沒有函數重載,但如此一來被extern"C"修飾的函數,就不具備重載能力,可以說extern 和extern "C"不是一回事。

            C++編譯器在生成DLL時,會對導出的函數進行名字改編,并且不同的編譯器使用的改變規則不一樣,因此改編后的名字會不一樣。這樣,如果利用不同的編譯器分別生成DLL和訪問該DLL的客戶端代碼程序的話,后者在訪問該DLL的導出函數時會出現問題。為了實現通用性,需要加上限定符:extern “C”。

            但是利用限定符extern “C”可以解決C++和C之間相互調用時函數命名的問題,但是這種方法有一個缺陷,就是不能用于導出一個類的成員函數,只能用于導出全局函數。LoadLibrary導入的函數名,對于非改編的函數,可以寫函數名;對于改編的函數,就必須吧@和號碼都寫上,一樣可以加載成功,可以試試看。

            解決警告  inconsistent dll linkage

             inconsistent dll linkage警告是寫dll時常遇到的一個問題,解決此警告的方法如下:

              一般PREDLL_API工程依賴于是否定義了MYDLL_EXPORTS來決定宏展開為__declspec(dllexport)還是__declspec(dllimport)。展開為__declspec(dllexport)是DLL編譯時的需要,通知編譯器該函數是需要導出供外部調用的。展開為__declspec(dllimport)是給調用者用的,通知編譯器,該函數是個外部導入函數。

            對于工程設置里面的預定義宏,是最早被編譯器看到的。所以當編譯器編譯DLL工程中的MYDLL.cpp時,因為看到前面有工程設置有定義MYDLL_EXPORTS,所以就把PREDLL_API展開為__declspec(dllexport)了。

            這樣做的目的是為了讓DLL和調用者共用同一個h文件,在DLL項目中,定義MYDLL_EXPORTS,PREDLL_API就是導出;在調用該DLL的項目中,不定義MYDLL_EXPORTS,PREDLL_API就是導入。

            使用靜態鏈接庫(Lib)

             

              使用靜態鏈接庫需要庫的開發者提供庫的頭文件以及lib文件,一般來說lib文件都比較大(相對導入庫來說),靜態鏈接庫是將全部指令都包含入調用程序生成的EXE文件中,并不存在“導出某個函數提供給用戶使用”的情況,就是要么全要,要么都不要。

            使用動態鏈接庫(DLL)

             

            方法一: load-time dynamic linking (隱式調用)
              在要調用dll的應用程序鏈接時,將dll的輸入庫文件(import library,.lib文件)包含進去。具體的做法是在源文件開頭加一句#include ,然后就可以在源文件中調用dlldemo.dll中的輸出文件了。

            #pragma comment(lib, "***.lib") //通知編譯器DLL的.lib文件所在路徑及文件名,也可以不采用該語句,在屬性欄——輸入——附加依賴項處添加對應的lib就可以編譯鏈接應用程序了。

            extern "C" __declspec(dllimport) foo(); //聲明導入函數

            方法二: run-time dynamic linking (顯式調用)
              不必在鏈接時包含輸入庫文件,而是在源程序中使用LoadLibrary或LoadLibraryEx動態的載入dll。
              主要步驟為(以demodll.dll為例): 

            1) typedef函數原型和定義函數指針。
              typedef void (CALLBACK* DllFooType)(void) ;
              DllFooType pfnDllFoo = NULL ;
            2) 使用LoadLibrary載入dll,并保存dll實例句柄
              HINSTANCE dllHandle = NULL ;
              ... 
              dllHandle = LoadLibrary(TEXT("dlldemo.dll"));
            3) 使用GetProcAddress得到dll中函數的指針
              pfnDllFoo = (DllFooType)GetProcAddress(dllHandle,TEXT("DllFoo")) ;
              注意從GetProcAddress返回的指針必須轉型為特定類型的函數指針。
            4)檢驗函數指針,如果不為空則可調用該函數 
              if(pfnDllFoo!=NULL)
              DllFoo() ;
            5)使用FreeLibrary卸載dll
              FreeLibrary(dllHandle) ;

            動態鏈接庫(DLL)的優點

             

              →節約內存;
              →使應用程序“變瘦”;
              →可單獨修改動態鏈接庫而不必與應用程序重新鏈接;
              →可方便實現多語言聯合編程(比如用VC++寫個dll,然后在VB中調用);
              →可將資源打包;
              →可在應用程序間共享內存
              →......

             

            杭州京都醫院http://www.fjzzled.com/京都醫院

            posted @ 2011-04-19 22:58 RTY 閱讀(1297) | 評論 (0)編輯 收藏

            1、內存分配方面:

                 堆:一般由程序員分配釋放, 若程序員不釋放,程序結束時可能由OS回收 。注意它與數據結構中的堆是兩回事,分配方式是類似于鏈表。可能用到的關鍵字如下:new、malloc、delete、free等等。

                 棧:由編譯器(Compiler)自動分配釋放,存放函數的參數值,局部變量的值等。其操作方式類似于數據結構中的棧。

             

            2、申請方式方面:

                 堆:需要程序員自己申請,并指明大小。在c中malloc函數如p1 = (char *)malloc(10);在C++中用new運算符,但是注意p1、p2本身是在棧中的。因為他們還是可以認為是局部變量。

                 棧:由系統自動分配。 例如,聲明在函數中一個局部變量 int b;系統自動在棧中為b開辟空間

             

            3、系統響應方面:

                堆:操作系統有一個記錄空閑內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大于所申請空間的堆結點,然后將該結點從空閑結點鏈表中刪除,并將該結點的空間分配給程序,另外,對于大多數系統,會在這塊內存空間中的首地址處記錄本次分配的大小,這樣代碼中的delete語句才能正確的釋放本內存空間。另外由于找到的堆結點的大小不一定正好等于申請的大小,系統會自動的將多余的那部分重新放入空閑鏈表中。

                棧:只要棧的剩余空間大于所申請空間,系統將為程序提供內存,否則將報異常提示棧溢出。

             

            4、大小限制方面:

                堆:是向高地址擴展的數據結構,是不連續的內存區域。這是由于系統是用鏈表來存儲的空閑內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限于計算機系統中有效的虛擬內存。由此可見,堆獲得的空間比較靈活,也比較大。

                棧:在Windows下, 棧是向低地址擴展的數據結構,是一塊連續的內存的區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,在WINDOWS下,棧的大小是固定的(是一個編譯時就確定的常數),如果申請的空間超過棧的剩余空間時,將提示overflow。因此,能從棧獲得的空間較小。

             

            5、效率方面:

                堆:是由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便,另外,在WINDOWS下,最好的方式是用VirtualAlloc分配內存,他不是在堆,也不是在棧是直接在進程的地址空間中保留一快內存,雖然用起來最不方便。但是速度快,也最靈活。

                棧:由系統自動分配,速度較快。但程序員是無法控制的。

             

            6、存放內容方面:

                 堆:一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。

                  棧:在函數調用時第一個進棧的是主函數中后的下一條指令(函數調用語句的下一條可執行語句)的地址然后是函數的各個參數,在大多數的C編譯器中,參數是由右往左入棧,然后是函數中的局部變量。 注意: 靜態變量是不入棧的。當本次函數調用結束后,局部變量先出棧,然后是參數,最后棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。

             

            7、存取效率方面:

                堆:char *s1 = "Hellow Word";是在編譯時就確定的;

                棧:char s1[] = "HellowWord"; 是在運行時賦值的;用數組比用指針速度要快一些,因為指針在底層匯編中需要用edx寄存器中轉一下,而數組在棧上直接讀取。

            posted @ 2011-04-19 22:55 RTY 閱讀(229) | 評論 (0)編輯 收藏

            1.初識Visual Leak Detector
                  靈活自由是C/C++語言的一大特色,而這也為C/C++程序員出了一個難題。當程序越來越復雜時,內存的管理也會變得越加復雜,稍有不慎就會出現內存問題。內存泄漏是最常見的內存問題之一。內存泄漏如果不是很嚴重,在短時間內對程序不會有太大的影響,這也使得內存泄漏問題有很強的隱蔽性,不容易被發現。然而不管內存泄漏多么輕微,當程序長時間運行時,其破壞力是驚人的,從性能下降到內存耗盡,甚至會影響到其他程序的正常運行。另外內存問題的一個共同特點是,內存問題本身并不會有很明顯的現象,當有異常現象出現時已時過境遷,其現場已非出現問題時的現場了,這給調試內存問題帶來了很大的難度。            

            Visual Leak Detector是一款用于Visual C++的免費的內存泄露檢測工具。可以在http://www.codeproject.com/tools/visualleakdetector.asp下載到。相比較其它的內存泄露檢測工具,它在檢測到內存泄漏的同時,還具有如下特點:

            1、 可以得到內存泄漏點的調用堆棧,如果可以的話,還可以得到其所在文件及行號;

            2、 可以得到泄露內存的完整數據;

            3、 可以設置內存泄露報告的級別;

            4、 它是一個已經打包的lib,使用時無須編譯它的源代碼。而對于使用者自己的代碼,也只需要做很小的改動;

            5、 他的源代碼使用GNU許可發布,并有詳盡的文檔及注釋。對于想深入了解堆內存管理的讀者,是一個不錯的選擇。            

            可見,從使用角度來講,Visual Leak Detector簡單易用,對于使用者自己的代碼,唯一的修改是#include Visual Leak Detector的頭文件后正常運行自己的程序,就可以發現內存問題。從研究的角度來講,如果深入Visual Leak Detector源代碼,可以學習到堆內存分配與釋放的原理、內存泄漏檢測的原理及內存操作的常用技巧等。      本文首先將介紹Visual Leak Detector的使用方法與步驟,然后再和讀者一起初步的研究Visual Leak Detector的源代碼,去了解Visual Leak Detector的工作原理。

             

            2.使用Visual Leak Detector(1.0)
                  下面讓我們來介紹如何使用這個小巧的工具。      

            首先從網站上下載zip包,解壓之后得到vld.h, vldapi.h, vld.lib, vldmt.lib, vldmtdll.lib, dbghelp.dll等文件。將.h文件拷貝到Visual C++的默認include目錄下,將.lib文件拷貝到Visual C++的默認lib目錄下,便安裝完成了。因為版本問題,如果使用windows 2000或者以前的版本,需要將dbghelp.dll拷貝到你的程序的運行目錄下,或其他可以引用到的目錄。      

            接下來需要將其加入到自己的代碼中。方法很簡單,只要在包含入口函數的.cpp文件中包含vld.h就可以。如果這個cpp文件包含了stdafx.h,則將包含vld.h的語句放在stdafx.h的包含語句之后,否則放在最前面。如下是一個示例程序:

            #include<vld.h>

            void main()

            {

              …

            }      

            接下來讓我們來演示如何使用Visual Leak Detector檢測內存泄漏。下面是一個簡單的程序,用new分配了一個int大小的堆內存,并沒有釋放。其申請的內存地址用printf輸出到屏幕上。

            #include<vld.h>

            #include<stdlib.h>

            #include<stdio.h> 

            void f()

            {   

              int*p =newint(0x12345678);   

              printf("p=%08x, ", p);

            void main()

            {   

              f();

            }

            編譯運行后,在標準輸出窗口得到:

            p=003a89c0 

            在Visual C++的Output窗口得到: 

            WARNING: Visual Leak Detector detected memory leaks!

            ---------- Block 57 at 0x003A89C0: 4 bytes ---------- --57號塊0x003A89C0地址泄漏了4個字節 

            Call Stack:                                             --下面是調用堆棧   

            d:\test\testvldconsole\testvldconsole\main.cpp (7): f --表示在main.cpp第7行的f()函數   

            d:\test\testvldconsole\testvldconsole\main.cpp (14): main–雙擊以引導至對應代碼處   

            f:\rtm\vctools\crt_bld\self_x86\crt\src\crtexe.c (586): __tmainCRTStartup   

            f:\rtm\vctools\crt_bld\self_x86\crt\src\crtexe.c (403): mainCRTStartup   

            0x7C816D4F (File and line number not available): RegisterWaitForInputIdle 

            Data:                                 --這是泄漏內存的內容,0x12345678   78 56 34 12                                                 

            xV4..... ........ 

            Visual Leak Detector detected 1 memory leak.   

            第二行表示57號塊有4字節的內存泄漏,地址為0x003A89C0,根據程序控制臺的輸出,可以知道,該地址為指針p。程序的第7行,f()函數里,在該地址處分配了4字節的堆內存空間,并賦值為0x12345678,這樣在報告中,我們看到了這4字節同樣的內容。

            可以看出,對于每一個內存泄漏,這個報告列出了它的泄漏點、長度、分配該內存時的調用堆棧、和泄露內存的內容(分別以16進制和文本格式列出)。雙擊該堆棧報告的某一行,會自動在代碼編輯器中跳到其所指文件的對應行。這些信息對于我們查找內存泄露將有很大的幫助。

            這是一個很方便易用的工具,安裝后每次使用時,僅僅需要將它頭文件包含進來重新build就可以。而且,該工具僅在build Debug版的時候會連接到你的程序中,如果build Release版,該工具不會對你的程序產生任何性能等方面影響。所以盡可以將其頭文件一直包含在你的源代碼中。

             

            3.Visual Leak Detector工作原理
              下面讓我們來看一下該工具的工作原理。      

              在這之前,我們先來看一下Visual C++內置的內存泄漏檢測工具是如何工作的。Visual C++內置的工具CRT Debug Heap工作原來很簡單。在使用Debug版的malloc分配內存時,malloc會在內存塊的頭中記錄分配該內存的文件名及行號。當程序退出時CRT會在main()函數返回之后做一些清理工作,這個時候來檢查調試堆內存,如果仍然有內存沒有被釋放,則一定是存在內存泄漏。從這些沒有被釋放的內存塊的頭中,就可以獲得文件名及行號。        這種靜態的方法可以檢測出內存泄漏及其泄漏點的文件名和行號,但是并不知道泄漏究竟是如何發生的,并不知道該內存分配語句是如何被執行到的。要想了解這些,就必須要對程序的內存分配過程進行動態跟蹤。Visual Leak Detector就是這樣做的。它在每次內存分配時將其上下文記錄下來,當程序退出時,對于檢測到的內存泄漏,查找其記錄下來的上下文信息,并將其轉換成報告輸出。      

            3.1初始化
              Visual Leak Detector要記錄每一次的內存分配,而它是如何監視內存分配的呢?Windows提供了分配鉤子(allocation hooks)來監視調試堆內存的分配。它是一個用戶定義的回調函數,在每次從調試堆分配內存之前被調用。在初始化時,Visual Leak Detector使用_CrtSetAllocHook注冊這個鉤子函數,這樣就可以監視從此之后所有的堆內存分配了。      

              如何保證在Visual Leak Detector初始化之前沒有堆內存分配呢?全局變量是在程序啟動時就初始化的,如果將Visual Leak Detector作為一個全局變量,就可以隨程序一起啟動。但是C/C++并沒有約定全局變量之間的初始化順序,如果其它全局變量的構造函數中有堆內存分配,則可能無法檢測到。Visual Leak Detector使用了C/C++提供的#pragma init_seg來在某種程度上減少其它全局變量在其之前初始化的概率。根據#pragma init_seg的定義,全局變量的初始化分三個階段:首先是compiler段,一般c語言的運行時庫在這個時候初始化;然后是lib段,一般用于第三方的類庫的初始化等;最后是user段,大部分的初始化都在這個階段進行。Visual Leak Detector將其初始化設置在compiler段,從而使得它在絕大多數全局變量和幾乎所有的用戶定義的全局變量之前初始化。 

             

            4.記錄內存分配
              一個分配鉤子函數需要具有如下的形式:intYourAllocHook(intallocType,void*userData, size_t size,intblockType,longrequestNumber,constunsignedchar*filename,intlineNumber);      就像前面說的,它在Visual Leak Detector初始化時被注冊,每次從調試堆分配內存之前被調用。這個函數需要處理的事情是記錄下此時的調用堆棧和此次堆內存分配的唯一標識——requestNumber。      

              得到當前的堆棧的二進制表示并不是一件很復雜的事情,但是因為不同體系結構、不同編譯器、不同的函數調用約定所產生的堆棧內容略有不同,要解釋堆棧并得到整個函數調用過程略顯復雜。不過windows提供一個StackWalk64函數,可以獲得堆棧的內容。StackWalk64的聲明如下:BOOLStackWalk64( DWORDMachineType, HANDLEhProcess, HANDLEhThread, LPSTACKFRAME64StackFrame, PVOIDContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64ReadMemoryRoutine, PFUNCTION_TABLE_ACCESS_ROUTINE64FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64GetModuleBaseRoutine, PTRANSLATE_ADDRESS_ROUTINE64TranslateAddress);STACKFRAME64結構表示了堆棧中的一個frame。給出初始的STACKFRAME64,反復調用該函數,便可以得到內存分配點的調用堆棧了。   // Walk the stack.   while(count < _VLD_maxtraceframes) {       count++;       if(!pStackWalk64(architecture, m_process, m_thread, &frame, &context,                         NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) {           // Couldn't trace back through any more frames.           break;       }

            posted @ 2011-04-19 22:53 RTY 閱讀(379) | 評論 (0)編輯 收藏

            QList<T> 的釋放分兩種情況:

            1.T的類型為非指針,這時候直接調用clear()方法就可以釋放了,看如下測試代碼

            #include <QtCore/QCoreApplication>#include <QList>#include <QString>
            int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);    typedef struct _test    {        int id;        QString name;        QString sex;    }Por_test;    QList<Por_test>  slist;    for (int i=0;i<100000;i++)    {        Por_test s;        s.id = 1;        s.name = QString("hello World!");        s.sex = QString("男");        slist.append(s);    }    slist.clear();    return a.exec();}

            將上面代碼中的slist.clear(); 注釋掉,內存顯示為如下(任務管理器里的截圖)

            111

            如不去掉的話,內存顯示如下圖

            22222

            2.T的類型為指針的情況,這時候直接調用clear()方法將不能釋放,先看代碼

            #include <QtCore/QCoreApplication>#include <QList>#include <QString>int main(int argc, char *argv[]){    QCoreApplication a(argc, argv);    typedef struct _test    {        int id;        QString name;        QString sex;    }Por_test;    QList<Por_test *>  slist;    for (int i=0;i<100000;i++)    {        Por_test *s = new Por_test();        s->id = 1;        s->name = QString("hello World!");        s->sex = QString("男?");        slist.append(s);    }//    qDeleteAll(slist);    slist.clear();    return a.exec();}

             

            上面代碼運行后的內存情況如下圖

            3333

            說明當T的類型為指針時,調用clear()方法并不能釋放其內存

            此時void qDeleteAll ( const Container & c )方法將派上用場了,將上面代碼中的注釋去掉以后,

            再次運行程序,此時的內存情況如下圖

            4444

            通過對比靚圖,可以看出,內存已經釋放,我們再來看下qt助手中qDeleteAll 方法的說明

            void qDeleteAll ( ForwardIterator begin, ForwardIterator end )

            Deletes all the items in the range [beginend) using the C++ delete operator. The item type must be a pointer type (for example, QWidget *).

            Example:

             QList<Employee *> list; list.append(new Employee("Blackpool", "Stephen")); list.append(new Employee("Twist", "Oliver")); qDeleteAll(list.begin(), list.end()); list.clear();

            Notice that qDeleteAll() doesn't remove the items from the container; it merely calls delete on them. In the example above, we call clear() on the container to remove the items.

            This function can also be used to delete items stored in associative containers, such as QMap and QHash. Only the objects stored in each container will be deleted by this function; objects used as keys will not be deleted.

            See also forward iterators.

            void qDeleteAll ( const Container & c )

            This is an overloaded member function, provided for convenience.

            This is the same as qDeleteAll(c.begin(), c.end()).

             

            上面qDeleteAll 方法的說明,已經很清楚了,如果T為指針類型時,釋放內存須在clear方法前加上qDeleteAll 方法。

            posted @ 2011-04-19 22:49 RTY 閱讀(3848) | 評論 (0)編輯 收藏

            app - 建立一個應用程序的makefile。這是默認值,所以如果模板沒有被指定,這個將被使用。

            lib - 建立一個庫的makefile。

            vcapp - 建立一個應用程序的Visual Studio項目文件。

            vclib - 建立一個庫的Visual Studio項目文件。

            subdirs - 這是一個特殊的模板,它可以創建一個能夠進入特定目錄并且為一個項目文件生成makefile并且為它調用make的makefile。

            “app”模板
            “app”模板告訴qmake為建立一個應用程序生成一個makefile。當使用這個模板時,下面這些qmake系統變量是被承認的。你應該在你的.pro文件中使用它們來為你的應用程序指定特定信息。

            HEADERS - 應用程序中的所有頭文件的列表。

            SOURCES - 應用程序中的所有源文件的列表。

            FORMS - 應用程序中的所有.ui文件(由Qt設計器生成)的列表。

            LEXSOURCES - 應用程序中的所有lex源文件的列表。

            YACCSOURCES - 應用程序中的所有yacc源文件的列表。

            TARGET - 可執行應用程序的名稱。默認值為項目文件的名稱。(如果需要擴展名,會被自動加上。)

            DESTDIR - 放置可執行程序目標的目錄。

            DEFINES - 應用程序所需的額外的預處理程序定義的列表。

            INCLUDEPATH - 應用程序所需的額外的包含路徑的列表。

            DEPENDPATH - 應用程序所依賴的搜索路徑。

            VPATH - 尋找補充文件的搜索路徑。

            DEF_FILE - 只有Windows需要:應用程序所要連接的.def文件。

            RC_FILE - 只有Windows需要:應用程序的資源文件。

            RES_FILE - 只有Windows需要:應用程序所要連接的資源文件。

            CONFIG變量
            配置變量指定了編譯器所要使用的選項和所需要被連接的庫。配置變量中可以添加任何東西,但只有下面這些選項可以被qmake識別。

            下面這些選項控制著使用哪些編譯器標志:

            release - 應用程序將以release模式連編。如果“debug”被指定,它將被忽略。

            debug - 應用程序將以debug模式連編。

            warn_on - 編譯器會輸出盡可能多的警告信息。如果“warn_off”被指定,它將被忽略。

            warn_off - 編譯器會輸出盡可能少的警告信息。

            下面這些選項定義了所要連編的庫/應用程序的類型:

            qt - 應用程序是一個Qt應用程序,并且Qt庫將會被連接。

            thread - 應用程序是一個多線程應用程序。

            x11 - 應用程序是一個X11應用程序或庫。

            windows - 只用于“app”模板:應用程序是一個Windows下的窗口應用程序。

            console - 只用于“app”模板:應用程序是一個Windows下的控制臺應用程序。

            dll - 只用于“lib”模板:庫是一個共享庫(dll)。

            staticlib - 只用于“lib”模板:庫是一個靜態庫。

            plugin - 只用于“lib”模板:庫是一個插件,這將會使dll選項生效。

            例如,如果你的應用程序使用Qt庫,并且你想把它連編為一個可調試的多線程的應用程序,你的項目文件應該會有下面這行:

                CONFIG += qt thread debug注意,你必須使用“+=”,不要使用“=”,否則qmake就不能正確使用連編Qt的設置了,比如沒法獲得所編譯的Qt庫的類型了。

             

            qmake高級概念
            操作符

            “=”操作符      分配一個值給一個變量
            “+=”操作符     向一個變量的值的列表中添加一個值
            “-=”操作符      從一個變量的值的列表中移去一個值
            “*=”操作符      僅僅在一個值不存在于一個變量的值的列表中的時候,把它添加進去
            “~=”操作符      替換任何與指定的值的正則表達式匹配的任何值 DEFINES ~= s/QT_[DT].+/QT
            作用域
            win32:thread {
                    DEFINES += QT_THREAD_SUPPORT    } else:debug {        DEFINES += QT_NOTHREAD_DEBUG    } else {        warning("Unknown configuration")    }    }變量
            到目前為止我們遇到的變量都是系統變量,比如DEFINES、SOURCES和HEADERS。你也可以為你自己創建自己的變量,這樣你就可以在作用域中使用它們了。創建自己的變量很容易,只要命名它并且分配一些東西給它。比如:

                MY_VARIABLE = value你也可以通過在其它任何一個變量的變量名前加$$來把這個變量的值分配給當前的變量。例如:

               MY_DEFINES = $$DEFINESMY_DEFINES = $${DEFINES}
            第二種方法允許你把一個變量和其它變量連接起來,而不用使用空格。qmake將允許一個變量包含任何東西(包括$(VALUE),可以直接在makefile中直接放置,并且允許它適當地擴張,通常是一個環境變量)。無論如何,如果你需要立即設置一個環境變量,然后你就可以使用$$()方法。比如:

                MY_DEFINES = $$(ENV_DEFINES)這將會設置MY_DEFINES為環境變量ENV_DEFINES傳遞給.pro文件地值。另外你可以在替換的變量里調用內置函數。這些函數(不會和下一節中列舉的測試函數混淆)列出如下:

            join( variablename, glue, before, after )
            這將會在variablename的各個值中間加入glue。如果這個變量的值為非空,那么就會在值的前面加一個前綴before和一個后綴after。只有variablename是必須的字段,其它默認情況下為空串。如果你需要在glue、before或者after中使用空格的話,你必須提供它們。

            member( variablename, position )
            這將會放置variablename的列表中的position位置的值。如果variablename不夠長,這將會返回一個空串。variablename是唯一必須的字段,如果沒有指定位置,則默認為列表中的第一個值。

            find( variablename, substr )
            這將會放置variablename中所有匹配substr的值。substr也可以是正則表達式,而因此將被匹配。

                MY_VAR = one two three four    MY_VAR2 = $$join(MY_VAR, " -L", -L) -Lfive    MY_VAR3 = $$member(MY_VAR, 2) $$find(MY_VAR, t.*)MY_VAR2將會包含“-Lone -Ltwo -Lthree -Lfour -Lfive”,并且MYVAR3將會包含“three two three”。

            system( program_and_args )
            這將會返回程序執行在標準輸出/標準錯誤輸出的內容,并且正像平時所期待地分析它。比如你可以使用這個來詢問有關平臺的信息。

                UNAME = $$system(uname -s)    contains( UNAME, [lL]inux ):message( This looks like Linux ($$UNAME) to me )測試函數
            qmake提供了可以簡單執行,但強大測試的內置函數。這些測試也可以用在作用域中(就像上面一樣),在一些情況下,忽略它的測試值,它自己使用測試函數是很有用的。

            contains( variablename, value )
            如果value存在于一個被叫做variablename的變量的值的列表中,那么這個作用域中的設置將會被處理。例如:

                contains( CONFIG, thread ) {        DEFINES += QT_THREAD_SUPPORT    }如果thread存在于CONFIG變量的值的列表中時,那么QT_THREAD_SUPPORT將會被加入到DEFINES變量的值的列表中。

            count( variablename, number )
            如果number與一個被叫做variablename的變量的值的數量一致,那么這個作用域中的設置將會被處理。例如:

                count( DEFINES, 5 ) {        CONFIG += debug    }error( string )
            這個函數輸出所給定的字符串,然后會使qmake退出。例如:

                error( "An error has occured" )文本“An error has occured”將會被顯示在控制臺上并且qmake將會退出。

            exists( filename )
            如果指定文件存在,那么這個作用域中的設置將會被處理。例如:

                exists( /local/qt/qmake/main.cpp ) {        SOURCES += main.cpp    }如果/local/qt/qmake/main.cpp存在,那么main.cpp將會被添加到源文件列表中。

            注意可以不用考慮平臺使用“/”作為目錄的分隔符。

            include( filename )
            項目文件在這一點時包含這個文件名的內容,所以指定文件中的任何設置都將會被處理。例如:

                 include( myotherapp.pro )myotherapp.pro項目文件中的任何設置現在都會被處理。

            isEmpty( variablename )
            這和使用count( variablename, 0 )是一樣的。如果叫做variablename的變量沒有任何元素,那么這個作用域中的設置將會被處理。例如:

                isEmpty( CONFIG ) {        CONFIG += qt warn_on debug    }message( string )
            這個函數只是簡單地在控制臺上輸出消息。

                message( "This is a message" )文本“This is a message”被輸出到控制臺上并且對于項目文件的處理將會繼續進行。

            system( command )
            特定指令被執行并且如果它返回一個1的退出值,那么這個作用域中的設置將會被處理。例如:

                system( ls /bin ) {        SOURCES += bin/main.cpp        HEADERS += bin/main.h    }所以如果命令ls /bin返回1,那么bin/main.cpp將被添加到源文件列表中并且bin/main.h將被添加到頭文件列表中。

            infile( filename, var, val )
            如果filename文件(當它被qmake自己解析時)包含一個值為val的變量var,那么這個函數將會返回成功。你也可以不傳遞第三個參數(val),這時函數將只測試文件中是否分配有這樣一個變量var。

            以下為我的一個項目舉例
            # 項目目標:為一個庫文件

            TEMPLATE = lib# 編譯項目文件所需頭文件的路徑INCLUDEPATH += ../common .# 目標文件路徑DESTDIR=../lib# 條件依賴:Unix平臺上 定義本想目的 UI目錄, MOC目錄, 目的目錄unix {  UI_DIR = ../.ui  MOC_DIR = ../.moc  OBJECTS_DIR = ../.obj}# 本項目配置:CONFIG         += qt warn_on release thread# Input  頭文件 ,源文件HEADERS += COMControllerThread.h \           DecodeSMS.h \           monitor_common.h \           monitor_interface.h \           MonitorThread.h \          UserEvent.h \           MyCOM.h \           MySMS.h \           MyTagHandle.h \          SMSParseThread.h \           tag_dict.hSOURCES += COMControllerThread.cpp \          DecodeSMS.cpp \           monitor_common.cpp \           monitor_interface.cpp \          MonitorThread.cpp \           MyCOM.cpp \           MySMS.cpp \           MyTagHandle.cpp \           SMSParseThread.cpp \           tag_dict.cpp注:qmake -project 可以生成pro文件(可以根據項目需要,編輯改文件)
            qmake 可以生成Makefile文件
            make 編譯
            使用qmake -project時,會把本目錄及其子目錄內所有.cpp .h文件加入到項目輸入文件中,使用是注意移去其他無用的文件。
            qmake生成的Makefile文件,可以根據需要做相應修改

            posted @ 2011-04-19 22:46 RTY 閱讀(611) | 評論 (0)編輯 收藏

            \a  (parameter marker) 參數標記
            \abstract (The \abstract and \endabstract commands delimit a document's abstract section.)
            \badcode (The \badcode and \endcode commands delimit a snippet of code that doesn't compile or is wrong for some other reason.)
            \bold (The \bold command renders its argument in bold font.)
            \brief  (The \brief command introduces a one-sentence description of a class, namespace, header file, property or variable.)
            \c (The \c command is used for rendering variable names, user-defined class names, and C++ keywords (e.g. int and for) in the code font.()
            \caption  (The \caption command provides a caption for an image.)
            \chapter
            \class
            \code
            \codeline,
            \compat
            \contentspage
            \default (new)
            \div (new)
            \dots
            \else
            \endif
            \enum
            \example
            \expire
            \externalpage
            \fn
            \footnote
            \generatelist
            \group
            \header
            \headerfile
            \i
            \if
            \image
            \include
            \indexpage
            \ingroup
            \inherits (new)
            \inmodule
            \inlineimage
            \internal
            \keyword
            \l
            \legalese
            \list
            \macro
            \mainclass
            \meta
            \module
            \namespace
            \nextpage
            \newcode
            \nonreentrant
            \o
            \obsolete
            \oldcode
            \omit
            \omitvalue
            \overload
            \page
            \part
            \preliminary
            \previouspage
            \printline
            \printto
            \printuntil
            \property
            \qmlattachedproperty (new)
            \qmlattachedsignal (new)
            \qmlbasictype (new)
            \qmlclass (new)
            \qmlmethod (new)
            \qmlproperty (new)
            \qmlsignal (new)
            \quotation
            \quotefile
            \quotefromfile
            \raw (avoid)
            \reentrant
            \reimp
            \relates
            \row
            \sa
            \section1
            \section2
            \section3
            \section4
            \service
            \since
            \skipline
            \skipto
            \skipuntil
            \snippet,
            \span (new)
            \startpage
            \sub
            \subtitle
            \sup
            \table
            \tableofcontents
            \target
            \threadsafe
            \title
            \tt
            \typedef
            \underline
            \variable
            \value
            \warning

            posted @ 2011-04-19 21:18 RTY 閱讀(329) | 評論 (0)編輯 收藏

                 摘要:     CSS3 Buttons – 10+ Awesome Ready-To-Use Solutions (+All Related Tutorials You Need) 14APR   For years, it was im...  閱讀全文

            posted @ 2011-04-18 21:30 RTY 閱讀(546) | 評論 (0)編輯 收藏

            原文:http://www.cnblogs.com/codingmylife/archive/2010/05/08/1730832.html

                  找到qt安裝目錄下的mkspecs文件夾,在里面找到你使用的對應版本編譯器,打開qmake.conf。稍等:

            /MD:動態鏈接多線程庫(msvcrt.lib)。使用該選項時,需要用/NODEFAULTLIB選項來忽略掉libc.lib、 libcmt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib庫,否則會有鏈接錯誤;

            /MDd:動態鏈接多線程調試庫(msvcrtd.lib)。使用該選項時,需要用/NODEFAULTLIB選項來忽略掉libc.lib、 libcmt.lib、msvcrt.lib、libcd.lib、libcmtd.lib庫,否則會有鏈接錯誤;

            /MT:靜態鏈接多線程庫(libcmt.lib)。使用該選項時,需要用/NODEFAULTLIB選項來忽略掉libc.lib、 msvcrt.lib、libcd.lib、libcmtd.lib、msvcrtd.lib庫,否則會有鏈接錯誤;

            /MTd:靜態鏈接多線程調試庫(libcmtd.lib)。使用該選項時,需要用/NODEFAULTLIB選項來忽略掉libc.lib、 libcmt.lib、msvcrt.lib、libcd.lib、msvcrtd.lib庫,否則會有鏈接錯誤。

                  左邊的是使用的鏈接方式,在qmake.conf中可以找到:

            QMAKE_CFLAGS_RELEASE    = -O2 -MD
            QMAKE_CFLAGS_DEBUG      = -Zi -MDd

            其中說明release下使用/MD參數,debug下使用/MDd參數,該如何忽略呢?

            以我使用/MDd參數時的debug為例,添加部分為紅色:

            QMAKE_LFLAGS_DEBUG      = /DEBUG /NODEFAULTLIB:libc.lib /NODEFAULTLIB:libcmt.lib

            /NODEFAULTLIB:msvcrt.lib /NODEFAULTLIB:libcd.lib /NODEFAULTLIB:libcmtd.lib

            posted @ 2011-04-15 23:05 RTY 閱讀(2722) | 評論 (0)編輯 收藏

            • 指定臨時文件生成目錄,使項目文件夾更干凈

                  QT默認情況下把所有的編譯中間文件都生成到debug和release文件夾里。可以在.pro文件中加入:

            MOC_DIR = tmp/moc

            RCC_DIR = tmp/rcc

            UI_DIR = tmp/ui

            OBJECTS_DIR = tmp/obj

            這樣,編譯時生成的臨時文件就按不同類型分類放到項目下的tmp文件夾中了。

            posted @ 2011-04-15 23:03 RTY 閱讀(2699) | 評論 (0)編輯 收藏

            僅列出標題
            共31頁: First 23 24 25 26 27 28 29 30 31 
            亚洲午夜福利精品久久| 久久国产一区二区| 欧洲国产伦久久久久久久| 综合网日日天干夜夜久久| 国产成人精品久久一区二区三区| 久久www免费人成精品香蕉| 一本色道久久88精品综合| 久久99精品久久只有精品| 日韩欧美亚洲综合久久影院Ds| 偷窥少妇久久久久久久久| 精品无码久久久久久国产| 怡红院日本一道日本久久 | 精品久久久久久| 精品乱码久久久久久夜夜嗨| 性欧美丰满熟妇XXXX性久久久| 久久这里只有精品视频99| 99久久国产免费福利| 国产精品99久久久久久人| 91精品国产色综久久| 精品久久久中文字幕人妻| 久久天天躁夜夜躁狠狠躁2022| 久久狠狠高潮亚洲精品| 精品久久久久久综合日本| 久久国产欧美日韩精品| 一级做a爰片久久毛片免费陪| 国产美女久久精品香蕉69| 久久久久亚洲AV片无码下载蜜桃| 久久精品无码一区二区三区日韩| 久久涩综合| 久久久国产精品| 国产欧美久久一区二区| 国产精品欧美久久久天天影视 | 91精品国产综合久久久久久| 欧美一区二区久久精品| 亚洲精品99久久久久中文字幕| 国产福利电影一区二区三区久久久久成人精品综合 | 久久久精品国产亚洲成人满18免费网站| 久久精品国产精品亚洲毛片| 无码人妻精品一区二区三区久久久 | 久久毛片一区二区| 日韩欧美亚洲综合久久|