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

            road420

            導航

            <2025年5月>
            27282930123
            45678910
            11121314151617
            18192021222324
            25262728293031
            1234567

            統計

            常用鏈接

            留言簿(2)

            隨筆檔案

            文章檔案

            搜索

            最新評論

            閱讀排行榜

            評論排行榜

            #

            #define

            今天整理了一些#define的用法,與大家共享!
            1.簡單的define定義

            #define MAXTIME 1000 

            一個簡單的MAXTIME就定義好了,它代表1000,如果在程序里面寫

            if(i<MAXTIME){.........}

            編譯器在處理這個代碼之前會對MAXTIME進行處理替換為1000。

            這樣的定義看起來類似于普通的常量定義CONST,但也有著不同,因為define的定義更像是簡單的文本替換,而不是作為一個量來使用,這個問題在下面反映的尤為突出。

            2.define的“函數定義”

            define可以像函數那樣接受一些參數,如下

            #define max(x,y) (x)>(y)?(x):(y);

            這個定義就將返回兩個數中較大的那個,看到了嗎?因為這個“函數”沒有類型檢查,就好像一個函數模板似的,當然,它絕對沒有模板那么安全就是了。可以作為一個簡單的模板來使用而已。

            但是這樣做的話存在隱患,例子如下:
            #define Add(a,b) a+b;
            在一般使用的時候是沒有問題的,但是如果遇到如:c * Add(a,b) * d的時候就會出現問題,代數式的本意是a+b然后去和c,d相乘,但是因為使用了define(它只是一個簡單的替換),所以式子實際上變成了
            c*a + b*d

            另外舉一個例子:
            #define pin (int*);
            pin a,b;
            本意是a和b都是int型指針,但是實際上變成int* a,b;
            a是int型指針,而b是int型變量。
            這是應該使用typedef來代替define,這樣a和b就都是int型指針了。

            所以我們在定義的時候,養成一個良好的習慣,建議所有的層次都要加括號。

            3.宏的單行定義
            #define A(x) T_##x
            #define B(x) #@x

            #define C(x) #x
            我們假設:x=1,則有:
            A(1)------〉T_1
            B(1)------〉'1'
            C(1)------〉"1"

            (這里參考了 hustli的文章)

            3.define的多行定義

            define可以替代多行的代碼,例如MFC中的宏定義(非常的經典,雖然讓人看了惡心)

            #define MACRO(arg1, arg2) do { \
            /* declarations */ \
            stmt1; \
            stmt2; \
            /* ... */ \
            } while(0) /* (no trailing ; ) */

            #define DECLARE_RTTI(thisClass, superClass)\
              virtual const char* GetClassName() const\
              {return #thisClass;}\
              static int isTypeOf(const char* type)\
              {\
               if(!strcmp(#thisClass, type)\
                return 1;\
               return superClass::isTypeOf(type);\
               return 0;\
              }\
              virtual int isA(const char* type)\
              {\
               return thisClass::isTypeOf(type);\
              }\
              static thisClass* SafeDownCast(DitkObject* o)\
              {\
               if(o&&o->isA(#thisClass))\
                return static_cast<thisClass*>(o);\
               return NULL;\
              }

            關鍵是要在每一個換行的時候加上一個"\" 

            摘抄自http://www.blog.edu.cn/user1/16293/archives/2005/115370.shtml 修補了幾個bug

            4.在大規模的開發過程中,特別是跨平臺和系統的軟件里,define最重要的功能是條件編譯。

            就是:
            #ifdef WINDOWS
            ......
            ......
            #endif
            #ifdef LINUX
            ......
            ......
            #endif

            可以在編譯的時候通過#define設置編譯環境

            5.如何定義宏、取消宏

            //定義宏
            #define [MacroName] [MacroValue]
            //取消宏
            #undef [MacroName]
            普通宏
            #define PI (3.1415926)

            帶參數的宏
            #define max(a,b) ((a)>(b)? (a),(b))
            關鍵是十分容易產生錯誤,包括機器和人理解上的差異等等。

            6.條件編譯
            #ifdef XXX…(#else) …#endif
            例如 #ifdef DV22_AUX_INPUT
            #define AUX_MODE 3 
            #else
            #define AUY_MODE 3
            #endif
            #ifndef XXX … (#else) … #endif

            7.頭文件(.h)可以被頭文件或C文件包含;
            重復包含(重復定義)
            由于頭文件包含可以嵌套,那么C文件就有可能包含多次同一個頭文件,就可能出現重復定義的問題的。
            通過條件編譯開關來避免重復包含(重復定義)
            例如
            #ifndef __headerfileXXX__
            #define __headerfileXXX__

            文件內容

            #endif

            posted @ 2008-07-09 08:48 深邃者 閱讀(262) | 評論 (0)編輯 收藏

            回調函數

            簡介

              對于很多初學者來說,往往覺得回調函數很神秘,很想知道回調函數的工作原理。本文將要解釋什么是回調函數、它們有什么好處、為什么要使用它們等等問題,在開始之前,假設你已經熟知了函數指針。

              什么是回調函數?

              簡而言之,回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(地址)作為參數傳遞給另一個函數,當這個指針被用為調用它所指向的函數時,我們就說這是回調函數。

              為什么要使用回調函數?

              因為可以把調用者與被調用者分開。調用者不關心誰是被調用者,所有它需知道的,只是存在一個具有某種特定原型、某些限制條件(如返回值為int)的被調用函數。

              如果想知道回調函數在實際中有什么作用,先假設有這樣一種情況,我們要編寫一個庫,它提供了某些排序算法的實現,如冒泡排序、快速排序、shell排序、shake排序等等,但為使庫更加通用,不想在函數中嵌入排序邏輯,而讓使用者來實現相應的邏輯;或者,想讓庫可用于多種數據類型(int、float、string),此時,該怎么辦呢?可以使用函數指針,并進行回調。

              回調可用于通知機制,例如,有時要在程序中設置一個計時器,每到一定時間,程序會得到相應的通知,但通知機制的實現者對我們的程序一無所知。而此時,就需有一個特定原型的函數指針,用這個指針來進行回調,來通知我們的程序事件已經發生。實際上,SetTimer() API使用了一個回調函數來通知計時器,而且,萬一沒有提供回調函數,它還會把一個消息發往程序的消息隊列。

              另一個使用回調機制的API函數是EnumWindow(),它枚舉屏幕上所有的頂層窗口,為每個窗口調用一個程序提供的函數,并傳遞窗口的處理程序。如果被調用者返回一個值,就繼續進行迭代,否則,退出。EnumWindow()并不關心被調用者在何處,也不關心被調用者用它傳遞的處理程序做了什么,它只關心返回值,因為基于返回值,它將繼續執行或退出。

              不管怎么說,回調函數是繼續自C語言的,因而,在C++中,應只在與C代碼建立接口,或與已有的回調接口打交道時,才使用回調函數。除了上述情況,在C++中應使用虛擬方法或函數符(functor),而不是回調函數。

              一個簡單的回調函數實現

              下面創建了一個sort.dll的動態鏈接庫,它導出了一個名為CompareFunction的類型--typedef int (__stdcall *CompareFunction)(const byte*, const byte*),它就是回調函數的類型。另外,它也導出了兩個方法:Bubblesort()和Quicksort(),這兩個方法原型相同,但實現了不同的排序算法。

            void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc);

            void DLLDIR __stdcall Quicksort(byte* array,int size,int elem_size,CompareFunction cmpFunc);

              這兩個函數接受以下參數:

              ·byte * array:指向元素數組的指針(任意類型)。

              ·int size:數組中元素的個數。

              ·int elem_size:數組中一個元素的大小,以字節為單位。

              ·CompareFunction cmpFunc:帶有上述原型的指向回調函數的指針。

              這兩個函數的會對數組進行某種排序,但每次都需決定兩個元素哪個排在前面,而函數中有一個回調函數,其地址是作為一個參數傳遞進來的。對編寫者來說,不必介意函數在何處實現,或它怎樣被實現的,所需在意的只是兩個用于比較的元素的地址,并返回以下的某個值(庫的編寫者和使用者都必須遵守這個約定):

              ·-1:如果第一個元素較小,那它在已排序好的數組中,應該排在第二個元素前面。

              ·0:如果兩個元素相等,那么它們的相對位置并不重要,在已排序好的數組中,誰在前面都無所謂。

              ·1:如果第一個元素較大,那在已排序好的數組中,它應該排第二個元素后面。

              基于以上約定,函數Bubblesort()的實現如下,Quicksort()就稍微復雜一點:

            void DLLDIR __stdcall Bubblesort(byte* array,int size,int elem_size,CompareFunction cmpFunc)
            {
             for(int i=0; i < size; i++)
             {
              for(int j=0; j < size-1; j++)
              {
               //回調比較函數
               if(1 == (*cmpFunc)(array+j*elem_size,array+(j+1)*elem_size))
               {
                //兩個相比較的元素相交換
                byte* temp = new byte[elem_size];
                memcpy(temp, array+j*elem_size, elem_size);
                memcpy(array+j*elem_size,array+(j+1)*elem_size,elem_size);
                memcpy(array+(j+1)*elem_size, temp, elem_size);
                delete [] temp;
               }
              }
             }
            }

              注意:因為實現中使用了memcpy(),所以函數在使用的數據類型方面,會有所局限。

              對使用者來說,必須有一個回調函數,其地址要傳遞給Bubblesort()函數。下面有二個簡單的示例,一個比較兩個整數,而另一個比較兩個字符串:

            int __stdcall CompareInts(const byte* velem1, const byte* velem2)
            {
             int elem1 = *(int*)velem1;
             int elem2 = *(int*)velem2;

             if(elem1 < elem2)
              return -1;
             if(elem1 > elem2)
              return 1;

             return 0;
            }

            int __stdcall CompareStrings(const byte* velem1, const byte* velem2)
            {
             const char* elem1 = (char*)velem1;
             const char* elem2 = (char*)velem2;
             return strcmp(elem1, elem2);
            }

              下面另有一個程序,用于測試以上所有的代碼,它傳遞了一個有5個元素的數組給Bubblesort()和Quicksort(),同時還傳遞了一個指向回調函數的指針。

            int main(int argc, char* argv[])
            {
             int i;
             int array[] = {5432, 4321, 3210, 2109, 1098};

             cout << "Before sorting ints with Bubblesort\n";
             for(i=0; i < 5; i++)
              cout << array[i] << '\n';

             Bubblesort((byte*)array, 5, sizeof(array[0]), &CompareInts);

             cout << "After the sorting\n";
             for(i=0; i < 5; i++)
              cout << array[i] << '\n';

             const char str[5][10] = {"estella","danielle","crissy","bo","angie"};

             cout << "Before sorting strings with Quicksort\n";
             for(i=0; i < 5; i++)
              cout << str[i] << '\n';

             Quicksort((byte*)str, 5, 10, &CompareStrings);

             cout << "After the sorting\n";
             for(i=0; i < 5; i++)
              cout << str[i] << '\n';

             return 0;
            }

              如果想進行降序排序(大元素在先),就只需修改回調函數的代碼,或使用另一個回調函數,這樣編程起來靈活性就比較大了。

            調用約定

              上面的代碼中,可在函數原型中找到__stdcall,因為它以雙下劃線打頭,所以它是一個特定于編譯器的擴展,說到底也就是微軟的實現。任何支持開發基于Win32的程序都必須支持這個擴展或其等價物。以__stdcall標識的函數使用了標準調用約定,為什么叫標準約定呢,因為所有的Win32 API(除了個別接受可變參數的除外)都使用它。標準調用約定的函數在它們返回到調用者之前,都會從堆棧中移除掉參數,這也是Pascal的標準約定。但在C/C++中,調用約定是調用者負責清理堆棧,而不是被調用函數;為強制函數使用C/C++調用約定,可使用__cdecl。另外,可變參數函數也使用C/C++調用約定。

              Windows操作系統采用了標準調用約定(Pascal約定),因為其可減小代碼的體積。這點對早期的Windows來說非常重要,因為那時它運行在只有640KB內存的電腦上。

              如果你不喜歡__stdcall,還可以使用CALLBACK宏,它定義在windef.h中:

            #define CALLBACK __stdcallor

            #define CALLBACK PASCAL //而PASCAL在此被#defined成__stdcall

              作為回調函數的C++方法

              因為平時很可能會使用到C++編寫代碼,也許會想到把回調函數寫成類中的一個方法,但先來看看以下的代碼:

            class CCallbackTester
            {
             public:
             int CALLBACK CompareInts(const byte* velem1, const byte* velem2);
            };

            Bubblesort((byte*)array, 5, sizeof(array[0]),
            &CCallbackTester::CompareInts);

              如果使用微軟的編譯器,將會得到下面這個編譯錯誤:

            error C2664: 'Bubblesort' : cannot convert parameter 4 from 'int (__stdcall CCallbackTester::*)(const unsigned char *,const unsigned char *)' to 'int (__stdcall *)(const unsigned char *,const unsigned char *)' There is no context in which this conversion is possible

              這是因為非靜態成員函數有一個額外的參數:this指針,這將迫使你在成員函數前面加上static。當然,還有幾種方法可以解決這個問題,但限于篇幅,就不再論述了。

            posted @ 2008-07-05 10:11 深邃者 閱讀(350) | 評論 (1)編輯 收藏

            內存分區

            五大內存分區
                在C++中,內存分成5個區,他們分別是堆、棧、自由存儲區、全局/靜態存儲區和常量存儲區。
                棧,就是那些由編譯器在需要的時候分配,在不需要的時候自動清楚的變量的存儲區。里面的變量通常是局部變量、函數參數等。
                堆,就是那些由new分配的內存塊,他們的釋放編譯器不去管,由我們的應用程序去控制,一般一個new就要對應一個delete。如果程序員沒有釋放掉,那么在程序結束后,操作系統會自動回收。
                自由存儲區,就是那些由malloc等分配的內存塊,他和堆是十分相似的,不過它是用free來結束自己的生命的。
                全局/靜態存儲區,全局變量和靜態變量被分配到同一塊內存中,在以前的C語言中,全局變量又分為初始化的和未初始化的,在C++里面沒有這個區分了,他們共同占用同一塊內存區。
                常量存儲區,這是一塊比較特殊的存儲區,他們里面存放的是常量,不允許修改(當然,你要通過非正當手段也可以修改,而且方法很多)
            明確區分堆與棧
                在bbs上,堆與棧的區分問題,似乎是一個永恒的話題,由此可見,初學者對此往往是混淆不清的,所以我決定拿他第一個開刀。
                首先,我們舉一個例子:
                void f() { int* p=new int[5]; }
                這條短短的一句話就包含了堆與棧,看到new,我們首先就應該想到,我們分配了一塊堆內存,那么指針p呢?他分配的是一塊棧內存,所以這句話的意思就是:在棧內存中存放了一個指向一塊堆內存的指針p。在程序會先確定在堆中分配內存的大小,然后調用operator new分配內存,然后返回這塊內存的首地址,放入棧中,他在VC6下的匯編代碼如下:
                00401028   push        14h
                0040102A   call        operator new (00401060)
                0040102F   add         esp,4
                00401032   mov         dword ptr [ebp-8],eax
                00401035   mov         eax,dword ptr [ebp-8]
                00401038   mov         dword ptr [ebp-4],eax
                這里,我們為了簡單并沒有釋放內存,那么該怎么去釋放呢?是delete p么?澳,錯了,應該是delete []p,這是為了告訴編譯器:我刪除的是一個數組,VC6就會根據相應的Cookie信息去進行釋放內存的工作。
                好了,我們回到我們的主題:堆和棧究竟有什么區別?
                主要的區別由以下幾點:
                1、管理方式不同;
                2、空間大小不同;
                3、能否產生碎片不同;
                4、生長方向不同;
                5、分配方式不同;
                6、分配效率不同;
                管理方式:對于棧來講,是由編譯器自動管理,無需我們手工控制;對于堆來說,釋放工作由程序員控制,容易產生memory leak。
                空間大小:一般來講在32位系統下,堆內存可以達到4G的空間,從這個角度來看堆內存幾乎是沒有什么限制的。但是對于棧來講,一般都是有一定的空間大小的,例如,在VC6下面,默認的棧空間大小是1M(好像是,記不清楚了)。當然,我們可以修改:   
                打開工程,依次操作菜單如下:Project->Setting->Link,在Category 中選中Output,然后在Reserve中設定堆棧的最大值和commit。
            注意:reserve最小值為4Byte;commit是保留在虛擬內存的頁文件里面,它設置的較大會使棧開辟較大的值,可能增加內存的開銷和啟動時間。
                碎片問題:對于堆來講,頻繁的new/delete勢必會造成內存空間的不連續,從而造成大量的碎片,使程序效率降低。對于棧來講,則不會存在這個問題,因為棧是先進后出的隊列,他們是如此的一一對應,以至于永遠都不可能有一個內存塊從棧中間彈出,在他彈出之前,在他上面的后進的棧內容已經被彈出,詳細的可以參考數據結構,這里我們就不再一一討論了。
                生長方向:對于堆來講,生長方向是向上的,也就是向著內存地址增加的方向;對于棧來講,它的生長方向是向下的,是向著內存地址減小的方向增長。
                分配方式:堆都是動態分配的,沒有靜態分配的堆。棧有2種分配方式:靜態分配和動態分配。靜態分配是編譯器完成的,比如局部變量的分配。動態分配由alloca函數進行分配,但是棧的動態分配和堆是不同的,他的動態分配是由編譯器進行釋放,無需我們手工實現。
                分配效率:棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。堆則是C/C++函數庫提供的,它的機制是很復雜的,例如為了分配一塊內存,庫函數會按照一定的算法(具體的算法可以參考數據結構/操作系統)在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間(可能是由于內存碎片太多),就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然后進行返回。顯然,堆的效率比棧要低得多。
                從這里我們可以看到,堆和棧相比,由于大量new/delete的使用,容易造成大量的內存碎片;由于沒有專門的系統支持,效率很低;由于可能引發用戶態和核心態的切換,內存的申請,代價變得更加昂貴。所以棧在程序中是應用最廣泛的,就算是函數的調用也利用棧去完成,函數調用過程中的參數,返回地址,EBP和局部變量都采用棧的方式存放。所以,我們推薦大家盡量用棧,而不是用堆。
                雖然棧有如此眾多的好處,但是由于和堆相比不是那么靈活,有時候分配大量的內存空間,還是用堆好一些。
                無論是堆還是棧,都要防止越界現象的發生(除非你是故意使其越界),因為越界的結果要么是程序崩潰,要么是摧毀程序的堆、棧結構,產生以想不到的結果,就算是在你的程序運行過程中,沒有發生上面的問題,你還是要小心,說不定什么時候就崩掉,那時候debug可是相當困難的:)
                對了,還有一件事,如果有人把堆棧合起來說,那它的意思是棧,可不是堆,呵呵,清楚了?
            static用來控制變量的存儲方式和可見性
                   函數內部定義的變量,在程序執行到它的定義處時,編譯器為它在棧上分配空間,函數在棧上分配的空間在此函數執行結束時會釋放掉,這樣就產生了一個問題: 如果想將函數中此變量的值保存至下一次調用時,如何實現? 最容易想到的方法是定義一個全局的變量,但定義為一個全局變量有許多缺點,最明顯的缺點是破壞了此變量的訪問范圍(使得在此函數中定義的變量,不僅僅受此函數控制)。

                   需要一個數據對象為整個類而非某個對象服務,同時又力求不破壞類的封裝性,即要求此成員隱藏在類的內部,對外不可見。

                   static的內部機制:
                   靜態數據成員要在程序一開始運行時就必須存在。因為函數在程序運行中被調用,所以靜態數據成員不能在任何函數內分配空間和初始化。
                   這樣,它的空間分配有三個可能的地方,一是作為類的外部接口的頭文件,那里有類聲明;二是類定義的內部實現,那里有類的成員函數定義;三是應用程序的main()函數前的全局數據聲明和定義處。
                  靜態數據成員要實際地分配空間,故不能在類的聲明中定義(只能聲明數據成員)。類聲明只聲明一個類的“尺寸和規格”,并不進行實際的內存分配,所以在類聲明中寫成定義是錯誤的。它也不能在頭文件中類聲明的外部定義,因為那會造成在多個使用該類的源文件中,對其重復定義。
                  static被引入以告知編譯器,將變量存儲在程序的靜態存儲區而非棧上空間,靜態
            數據成員按定義出現的先后順序依次初始化,注意靜態成員嵌套時,要保證所嵌套的成員已經初始化了。消除時的順序是初始化的反順序。

                   static的優勢:
                   可以節省內存,因為它是所有對象所公有的,因此,對多個對象來說,靜態數據成員只存儲一處,供所有對象共用。靜態數據成員的值對每個對象都是一樣,但它的值是可以更新的。只要對靜態數據成員的值更新一次,保證所有對象存取更新后的相同的值,這樣可以提高時間效率。

                    引用靜態數據成員時,采用如下格式:
                     <類名>::<靜態成員名>
                如果靜態數據成員的訪問權限允許的話(即public的成員),可在程序中,按上述格式
            來引用靜態數據成員。

                   PS:
                  (1)類的靜態成員函數是屬于整個類而非類的對象,所以它沒有this指針,這就導致
            了它僅能訪問類的靜態數據和靜態成員函數。
                  (2)不能將靜態成員函數定義為虛函數。
                  (3)由于靜態成員聲明于類中,操作于其外,所以對其取地址操作,就多少有些特殊
            ,變量地址是指向其數據類型的指針 ,函數地址類型是一個“nonmember函數指針”。

                  (4)由于靜態成員函數沒有this指針,所以就差不多等同于nonmember函數,結果就
            產生了一個意想不到的好處:成為一個callback函數,使得我們得以將C++和C-based X W
            indow系統結合,同時也成功的應用于線程函數身上。
                  (5)static并沒有增加程序的時空開銷,相反她還縮短了子類對父類靜態成員的訪問
            時間,節省了子類的內存空間。
                  (6)靜態數據成員在<定義或說明>時前面加關鍵字static。
                  (7)靜態數據成員是靜態存儲的,所以必須對它進行初始化。
                  (8)靜態成員初始化與一般數據成員初始化不同:
                  初始化在類體外進行,而前面不加static,以免與一般靜態變量或對象相混淆;
                  初始化時不加該成員的訪問權限控制符private,public等;
                       初始化時使用作用域運算符來標明它所屬類;
                       所以我們得出靜態數據成員初始化的格式:
                     <數據類型><類名>::<靜態數據成員名>=<值>
                  (9)為了防止父類的影響,可以在子類定義一個與父類相同的靜態變量,以屏蔽父類的影響。這里有一點需要注意:我們說靜態成員為父類和子類共享,但我們有重復定義了靜態成員,這會不會引起錯誤呢?不會,我們的編譯器采用了一種絕妙的手法:name-mangling 用以生成唯一的標志。

            posted @ 2008-07-05 10:05 深邃者 閱讀(169) | 評論 (0)編輯 收藏

            WM_DESTROY 和 WM_NCDESTROY

            WM_DESTROY 和 WM_NCDESTROY 消息之間有什么區別?

            原文鏈接 What is the difference between WM_DESTROY and WM_NCDESTROY?

            在窗口銷毀時有兩個緊密關聯的 windows 消息, 就是 WM_DESTROY 和 WM_NCDESTROY. 它們有何區別?

            區別就是 WM_DESTROY 消息是在窗口銷毀動作序列中的開始被發送的, 而 WM_NCDESTROY 消息是在結尾. 這在你的窗口擁有子窗口時是個重大區別. 如果你有一個帶子窗口的父窗口, 那么消息的發送序列 (在沒有怪誕行為影響的前提下) 就像這樣:

            hwnd = parent, uMsg = WM_DESTROY
            hwnd = child, uMsg = WM_DESTROY
            hwnd = child, uMsg = WM_NCDESTROY
            hwnd = parent, uMsg = WM_NCDESTROY

            注意, 父窗口是在子窗口被銷毀之前收到 WM_DESTROY 消息, 在子窗口被銷毀之后收到 WM_NCDESTROY 消息.

            兩個銷毀消息, 一個在開頭, 一個在結尾, 這意味著, 對于你自己的模塊, 你可以通過處理相應的消息來執行清理操作.
            例如, 如果有些東西必須在開頭清理, 那么你可以使用 WM_DESTROY 消息.

            WM_NCDESTROY 消息是你窗口將會收到的最后一個消息 (在沒有怪誕行為影響的前提下), 因此, 這里是做 "最終清理" 的最佳場所.
            這就是為什么我們的 new scratch 程序會一直等到 WM_NCDESTROY 銷毀它的實例變量, 才會返回.

            與這兩個銷毀消息配對的, 是 WM_CREATE 和 WM_NCCREATE 這兩個類似的消息. 與 WM_NCDESTROY 是你窗口收到的最后一條消息類似,
            WM_NCCREATE 消息是第一條消息, 這是一個創建你自己的實例變量的好地方. 需要注意的是, 如果你導致 WM_NCCREATE 消息返回失敗,
            那么所有你將收到的消息就只有 WM_NCDESTROY 了; 不會有 WM_DESTROY 消息了, 因為你根本就沒有收到相應的 WM_CREATE 消息.

            那么什么是我一直在暗示的 "怪誕行為" 呢? 下一次 (When the normal window destruction messages are thrown for a loop) 我們再說

            posted @ 2008-01-23 18:21 深邃者 閱讀(365) | 評論 (0)編輯 收藏

            懸掛指針

            懸掛指針與boost::weak_ptr

               與內存泄露相比,C++最令人頭痛的問題是內存越界,而內存越界很多情況下是由于懸掛指針引起的。  
              假設一個指針變量:
              Object * ptr;
              使用ptr時,我們除了要判斷ptr是否為0以外,還要懷疑它指向的對象是否有效,是不是已經在別的地方被銷毀了。我們希望當它指向的對象被銷毀時,ptr被自動置為0。
              顯然,C++沒有這種機制,但是,可以借助于boost::weak_ptr做到這一點。

            inline void null_deleter(void const *
            {
            }

            class X
            {
            private:

                shared_ptr
            <X> this_;
                
            int i_;

            public:

                
            explicit X(int i): this_(this, &null_deleter), i_(i)
                {
                }

                X(X 
            const & rhs): this_(this, &null_deleter), i_(rhs.i_)
                {
                }

                X 
            & operator=(X const & rhs)
                {
                    i_ 
            = rhs.i_;
                }

                weak_ptr
            <X> weak_this() const { return this_; }
            };


             

            定義變量:
            weak_ptr<X> ptr = x.weak_this();  // x為一個X 對象 

            則當 x 被銷毀時,ptr 被自動置為無效。使用方法如下:

            if ( shard_ptr<X>  safePtr  = ptr.lock() )  safePtr->do_something();

            這種辦法用于單線程中,因為 x  對象可能是基于棧分配的。如果需要在多線程中訪問X對象,那么最好的辦法還是使用shared_ptr 來管理對象的生命期。這樣的話,對于safePtr, 可以保證在 safePtr 的生命期內,它所指向的對象不會被其它線程刪除。

            posted @ 2007-11-09 19:28 深邃者 閱讀(552) | 評論 (0)編輯 收藏

            CString

            1. CString實現的機制.

            CString是通過“引用”來管理串的,“引用”這個詞我相信大家并不陌生,象Window內核對象、COM對象等都是通過引用來實現的。而CString也是通過這樣的機制來管理分配的內存塊。實際上CString對象只有一個指針成員變量,所以任何CString實例的長度只有4字節.

            即: int len = sizeof(CString);//len等于4

            這個指針指向一個相關的引用內存塊,如圖: CString str("abcd");

            ‘A’

            ‘B’

            ‘C’

            ‘D’

            0

            0x04040404 head部,為引用內存塊相關信息

            str 0x40404040

            正因為如此,一個這樣的內存塊可被多個CString所引用,例如下列代碼:

            CString str("abcd");

            CString a = str;

            CString b(str);

            CString c;

            c = b;

            上面代碼的結果是:上面四個對象(str,a,b,c)中的成員變量指針有相同的值,都為0x40404040.而這塊內存塊怎么知道有多少個CString引用它呢?同樣,它也會記錄一些信息。如被引用數,串長度,分配內存長度。

            這塊引用內存塊的結構定義如下:

            struct CStringData

            {

            long nRefs; //表示有多少個CString 引用它. 4

            int nDataLength; //串實際長度. 4

            int nAllocLength; //總共分配的內存長度(不計這頭部的12字節). 4

            };

            由于有了這些信息,CString就能正確地分配、管理、釋放引用內存塊。

            如果你想在調試程序的時候獲得這些信息。可以在Watch窗口鍵入下列表達式:

            (CStringData*)((CStringData*)(this->m_pchData)-1)或

            (CStringData*)((CStringData*)(str.m_pchData)-1)//str為指CString實例

            正因為采用了這樣的好機制,使得CString在大量拷貝時,不僅效率高,而且分配內存少。

            2.LPCTSTR 與 GetBuffer(int nMinBufLength)

            這兩個函數提供了與標準C的兼容轉換。在實際中使用頻率很高,但卻是最容易出錯的地方。這兩個函數實際上返回的都是指針,但它們有何區別呢?以及調用它們后,幕后是做了怎樣的處理過程呢?

            (1) LPCTSTR 它的執行過程其實很簡單,只是返回引用內存塊的串地址。 它是作為操作符重載提供的,所以在代碼中有時可以隱式轉換,而有時卻需強制轉制。如:

            CString str;

            const char* p = (LPCTSTR)str;

            //假設有這樣的一個函數,Test(const char* p); 你就可以這樣調用

            Test(str);//這里會隱式轉換為LPCTSTR

            (2) GetBuffer(int nMinBufLength) 它類似,也會返回一個指針,不過它有點差別,返回的是LPTSTR

            (3) 這兩者到底有何不同呢?我想告訴大家,其本質上完全不一樣,一般說LPCTSTR轉換后只應該當常量使用,或者做函數的入參;而GetBuffer(...)取出指針后,可以通過這個指針來修改里面的內容,或者做函數的出參。為什么呢?也許經常有這樣的代碼:

            CString str("abcd");

            char* p = (char*)(const char*)str;

            p[2] = 'z';

            其實,也許有這樣的代碼后,你的程序并沒有錯,而且程序也運行得挺好。但它卻是非常危險的。再看

            CString str("abcd");

            CString test = str;

            ....

            char* p = (char*)(const char*)str;

            p[2] = 'z';

            strcpy(p, "akfjaksjfakfakfakj");//這下完蛋了

            你知道此時,test中的值是多少嗎?答案是"abzd"。它也跟著改變了,這不是你所期望發生的。但為什么會這樣呢?你稍微想想就會明白,前面說過,因為CString是指向引用塊的,str與test指向同一塊地方,當你p[2]='z'后,當然test也會隨著改變。所以用它做LPCTSTR做轉換后,你只能去讀這塊數據,千萬別去改變它的內容。

            假如我想直接通過指針去修改數據的話,那怎樣辦呢?就是用GetBuffer(...).看下述代碼:

            CString str("abcd");

            CString test = str;

            ....

            char* p = str.GetBuffer(20);

            p[2] = 'z'; // 執行到此,現在test中值卻仍是"abcd"

            strcpy(p, "akfjaksjfakfakfakj"); // 執行到此,現在test中值還是"abcd"

            為什么會這樣?其實GetBuffer(20)調用時,它實際上另外建立了一塊新內塊存,并分配20字節長度的buffer,而原來的內存塊引用計數也相應減1. 所以執行代碼后str與test是指向了兩塊不同的地方,所以相安無事。

            (4) 不過這里還有一點注意事項:就是str.GetBuffer(20)后,str的分配長度為20,即指針p它所指向的buffer只有20字節長,給它賦值時,切不可超過,否則災難離你不遠了;如果指定長度小于原來串長度,如GetBuffer(1),實際上它會分配4個字節長度(即原來串長度);另外,當調用GetBuffer(...)后并改變其內容,一定要記得調用ReleaseBuffer(),這個函數會根據串內容來更新引用內存塊的頭部信息。

            (5) 最后還有一注意事項,看下述代碼:

            char* p = NULL;

            const char* q = NULL;

            {

            CString str = "abcd";

            q = (LPCTSTR)str;

            p = str.GetBuffer(20);

            AfxMessageBox(q);// 合法的

            strcpy(p, "this is test");//合法的,

            }

            AfxMessageBox(q);// 非法的,可能完蛋

            strcpy(p, "this is test");//非法的,可能完蛋

            這里要說的就是,當返回這些指針后, 如果CString對象生命結束,這些指針也相應無效。

            3.拷貝 & 賦值 & "引用內存塊" 什么時候釋放?

            下面演示一段代碼執行過程

            void Test()

            {

            CString str("abcd");

            //str指向一引用內存塊(引用內存塊的引用計數為1,長度為4,分配長度為4)

            CString a;

            //a指向一初始數據狀態,

            a = str;

            //a與str指向同一引用內存塊(引用內存塊的引用計數為2,長度為4,分配長度為4)

            CString b(a);

            //a、b與str指向同一引用內存塊(引用內存塊的引用計數為3,長度為4,分配長度為4)

            {

            LPCTSTR temp = (LPCTSTR)a;

            //temp指向引用內存塊的串首地址。(引用內存塊的引用計數為3,長度為4,分配長度為4)

            CString d = a;

            //a、b、d與str指向同一引用內存塊(引用內存塊的引用計數為4, 長度為4,分配長度為4)

            b = "testa";

            //這條語句實際是調用CString::operator=(CString&)函數。 b指向一新分配的引用內存塊。(新分配的引用內存塊的 引用計數為1, 長度為5, 分配長度為5)

            //同時原引用內存塊引用計數減1. a、d與str仍指向原 引用內存塊(引用內存塊的引用計數為3,長度為4,分配長度為4)

            }

            //由于d生命結束,調用析構函數,導至引用計數減1(引用內存塊的引用計數為2,長度為4,分配長度為4)

            LPTSTR temp = a.GetBuffer(10);

            //此語句也會導致重新分配新內存塊。temp指向新分配引用內存塊的串首地址(新 分配的引用內存塊的引用計數為1,長度為0,分配長度為10)

            //同時原引用內存塊引用計數減1. 只有str仍 指向原引用內存塊 (引用內存塊的引用計數為1, 長度為4, 分配長度為4)

            strcpy(temp, "temp");

            //a指向的引用內存塊的引用計數為1,長度為0,分配長度為10 a.ReleaseBuffer();//注意:a指向的引用內存塊的引用計數為1,長度為4,分配長度為10

            }

            //執行到此,所有的局部變量生命周期都已結束。對象str a b 各自調用自己的析構構

            //函數,所指向的引用內存塊也相應減1

            //注意,str a b 所分別指向的引用內存塊的計數均為0,這導致所分配的內存塊釋放

            通過觀察上面執行過程,我們會發現CString雖然可以多個對象指向同一引用內塊存,但是它們在進行各種拷貝、賦值及改變串內容時,它的處理是很智能并且非常安全的,完全做到了互不干涉、互不影響。當然必須要求你的代碼使用正確恰當,特別是實際使用中會有更復雜的情況,如做函數參數、引用、及有時需保存到CStringList當中,如果哪怕有一小塊地方使用不當,其結果也會導致發生不可預知的錯誤

            5 FreeExtra()的作用

            看這段代碼

            (1) CString str("test");

            (2) LPTSTR temp = str.GetBuffer(50);

            (3) strcpy(temp, "there are 22 character");

            (4) str.ReleaseBuffer();

            (5) str.FreeExtra();

            上面代碼執行到第(4)行時,大家都知道str指向的引用內存塊計數為1,長度為22,分配長度為50. 那么執行str.FreeExtra()時,它會釋放所分配的多余的內存。(引用內存塊計數為1,長度為22,分配長度為22)

            6 Format(...) 與 FormatV(...)

            這條語句在使用中是最容易出錯的。因為它最富有技巧性,也相當靈活。在這里,我沒打算對它細細分析,實際上sprintf(...)怎么用,它就怎么用。我只提醒使用時需注意一點:就是它的參數的特殊性,由于編譯器在編譯時并不能去校驗格式串參數與對應的變元的類型及長度。所以你必須要注意,兩者一定要對應上,

            否則就會出錯。如:

            CString str;

            int a = 12;

            str.Format("first:%l, second: %s", a, "error");//result?試試

            7 LockBuffer() 與 UnlockBuffer()

            顧名思議,這兩個函數的作用就是對引用內存塊進行加鎖及解鎖。但使用它有什么作用及執行過它后對CString串有什么實質上的影響。其實挺簡單,看下面代碼:

            (1) CString str("test");

            (2) str.LockBuffer();

            (3) CString temp = str;

            (4) str.UnlockBuffer();

            (5) str.LockBuffer();

            (6) str = "error";

            (7) str.ReleaseBuffer();

            執行完(3)后,與通常情況下不同,temp與str并不指向同一引用內存塊。你可以在watch窗口用這個表達式(CStringData*)((CStringData*)(str.m_pchData)-1)看看。

            其實在msdn中有說明:

            While in a locked state, the string is protected in two ways:

            No other string can get a reference to the data in the locked string, even if that string is assigned to the locked string.

            The locked string will never reference another string, even if that other string is copied to the locked string.

            8 CString 只是處理串嗎?

            不對,CString不只是能操作串,而且還能處理內存塊數據。功能完善吧!看這段代碼

            char p[20];

            for(int loop=0; loop<sizeof(p); loop++)

            {

            p[loop] = 10-loop;

            }

            CString str((LPCTSTR)p, 20);

            char temp[20];

            memcpy(temp, str, str.GetLength());

            str完全能夠轉載內存塊p到內存塊temp中。所以能用CString來處理二進制數據

            8 AllocSysString()與SetSysString(BSTR*)

            這兩個函數提供了串與BSTR的轉換。使用時須注意一點:當調用AllocSysString()后,須調用它SysFreeString(...)

            9 參數的安全檢驗

            在MFC中提供了多個宏來進行參數的安全檢查,如:ASSERT. 其中在CString中也不例外,有許多這樣的參數檢驗,其實這也說明了代碼的安全性高,可有時我們會發現這很煩,也導致Debug與Release版本不一樣,如有時程序Debug通正常,而Release則程序崩潰;而有時恰相反,Debug不行,Release行。其實我個人認為,我們對CString的使用過程中,應力求代碼質量高,不能在Debug版本中出現任何斷言框,哪怕release運行似乎看起來一切正常。但很不安全。如下代碼:

            (1) CString str("test");

            (2) str.LockBuffer();

            (3) LPTSTR temp = str.GetBuffer(10);

            (4) strcpy(temp, "error");

            (5) str.ReleaseBuffer();

            (6) str.ReleaseBuffer();//執行到此時,Debug版本會彈出錯框

            10 CString的異常處理

            我只想強調一點:只有分配內存時,才有可能導致拋出CMemoryException.

            同樣,在msdn中的函數聲明中,注有throw( CMemoryException)的函數都有重新分配或調整內存的可能。

            11 跨模塊時的Cstring。即一個DLL的接口函數中的參數為CString&時,它會發生怎樣的現象。解答我遇到的問題。我的問題原來已經發貼,地址為:

            http://www.csdn.net/expert/topic/741/741921.xml?temp=.2283136

            構造一個這樣CString對象時,如CString str,你可知道此時的str所指向的引用內存塊嗎?也許你會認為它指向NULL。其實不對,如果這樣的話,CString所采用的引用機制管理內存塊就會有麻煩了,所以CString在構造一個空串的對象時,它會指向一個固定的初始化地址,這塊數據的聲明如下:

            AFX_STATIC_DATA int _afxInitData[] = {-1,0,0,0};

            簡要描述概括一下:當某個CString對象串置空的話,如Empty(),CString a等,它的成員變量m_pchData就會指向_afxInitData這個變量的地址。當這個CString對象生命周期結束時,正常情況下它會去對所指向的引用內存塊計數減1,如果引用計數為0(即沒有任何CString引用它時),則釋放這塊引用內存。而現在的情況是如果CString所指向的引用內存塊是初始化內存塊時,則不會釋放任何內存。

            說了這么多,這與我遇到的問題有什么關系呢?其實關系大著呢?其真正原因就是如果exe模塊與dll模塊有一個是static編譯連接的話。那么這個CString初始化數據在exe模塊與dll模塊中有不同的地址,因為static連接則會在本模塊中有一份源代碼的拷貝。另外一種情況,如果兩個模塊都是share連接的,CString的實現代碼則在另一個單獨的dll中實現,而AFX_STATIC_DATA指定變量只裝一次,所以兩個模塊中_afxInitData有相同的地址。

            現在問題完全明白了吧!你可以自己去演示一下。

            __declspec (dllexport) void test(CString& str)

            {

            str = "abdefakdfj";//如果是static連接,并且傳入的str為空串的話,這里出錯。

            }

            posted @ 2007-07-24 17:11 深邃者 閱讀(581) | 評論 (0)編輯 收藏

            指針

            指針高級--<高質量編程>

            //  execise2.cpp : Defines the entry point for the console application.
            //

            #include 
            " stdafx.h "
            #include 
            < iostream >
            using   namespace  std;
            // -----------------------------------------------
            void  GetMemory1( char   * p)
            {
                p 
            =  ( char   * )malloc( 100 );
            }

            void  Test1( void )
            {
                
            char   * str  =  NULL;
                GetMemory1( str );
                strcpy(str, 
            " hello world " );
                printf(str);
            }

            // -----------------------------------------------
            char   * GetMemory2( void )
            {   
                
            char  p[]  =   " hello world " ;
                
            return  p;
            }

            void  Test2( void )
            {
                
            char   * str  =  NULL;
                str 
            =  GetMemory2();  
                printf(str);
            }

            // -----------------------------------------------
            void  GetMemory3( char   ** p,  int  num)
            {
                
            * =  ( char   * )malloc(num);
            }

            void  Test3( void )
            {
                
            char   * str  =  NULL;
                GetMemory3(
            & str,  100 );
                strcpy(str, 
            " hello " );  
                printf(str);    
            }

            // -----------------------------------------------
            void  Test4( void )
            {
                
            char   * str  =  ( char   * ) malloc( 100 );
                strcpy(str, 
            " hello " );
                free(str);      
                
            if (str  !=  NULL)
                
            {
                    strcpy(str, 
            " world " ); 
                    printf(str);
                }

            }

            // -----------------------------------------------
            main()
            {
                
            // -----------------------------------------------
                
            // 請問運行Test1函數會有什么樣的結果?
                
            //
                
            // 答:程序崩潰。
                
            //
                
            // 因為GetMemory并不能傳遞動態內存,
                
            //
                
            // Test函數中的 str一直都是 NULL。
                
            //
                
            // strcpy(str, "hello world");將使程序崩潰。
                Test1();
                
            // -----------------------------------------------
                
            //     請問運行Test2函數會有什么樣的結果?
                
            //
                
            // 答:可能是亂碼。
                
            //
                
            // 因為GetMemory返回的是指向“棧內存”的指針,
                
            // 該指針的地址不是 NULL,但其原現的內容已經被清除,新內容不可知。
                Test2();
                
            // -----------------------------------------------
                
            //     請問運行Test3函數會有什么樣的結果?
                
            //
                
            // 答:
                
            //
                
            // (1)能夠輸出hello
                
            //
                
            // (2)內存泄漏
                Test3();
                
            // -----------------------------------------------
                
            //     請問運行Test函數會有什么樣的結果?
                
            //
                
            // 答:篡改動態內存區的內容,后果難以預料,非常危險。
                
            //
                
            // 因為free(str);之后,str成為野指針,
                
            //
                
            // if(str != NULL)語句不起作用。
                Test4();
                
            // -----------------------------------------------
            }

            posted @ 2007-07-24 16:48 深邃者 閱讀(124) | 評論 (0)編輯 收藏

            調試Release版本

            很多時候程序的 Debug 版本運行沒有任何問題,但是一旦發布 Release 版本后,運行就出錯,著實讓人郁悶。大家知道,VC++ 中 Release 版本是對無法對源代碼進行調試的。一般的做法是在懷疑有錯誤的代碼前后插入MessageBox 函數,在函數中顯示可能導致錯誤的變量的值。或者插入寫文件的語句,輸出可能導致錯誤的變量的值到一個記錄文件。其實,除了上面講的這個辦法之外,還有其它的途徑來調試 Release 版本的。下面就結合自己的經驗和網上查找的一些資料給出調試 Release 版本的兩個方法:

            方法一、利用 *.PDB 符號文件調試 Release 版本
            在 VCKBASE 的在線雜志中有一篇參考文章:符號文件——Windows 應用程序調試必備(http://www.vckbase.com/document/viewdoc/?id=1710),文章談到了如何產生 Release 版本二進制文件對應的 PDB 文件的問題。有了 PDB 文件后,就可以調試 Release 了,方法是:
                1、在Project Settings里選Settings For為All Configurations。
                2、在C/C++標簽中,Debug info 選 Program Database。
                3、在Link 標簽中,Category選 Debug,選中Debug info 復選框和Microsoft format。
            進行了上述設置后,我們就可以像在調試版本中那樣設置斷點進行測試了,由于代碼優化,有些變量觀察不到,行的運行順序可能也會不同。
            有一點需要注意:ASSERT宏在 Release 版本中不起作用,在 Release 版本中應該使用 VERIFY 來代替 ASSERT 進行調試。如果發行版本運行有問題,可以先禁止所有代碼優化再進行調試。

            方法二、在需要加斷點的地方添加如下匯編語句:
                __asm int 3

            不過調試的時候無法顯示C程序,只有asm代碼。
                
            此處 int 3 是專門用來設置斷點的,是 CPU 定義的,Windows 和 DOS 下的大多數調試器都采用這種方法。

            posted @ 2007-07-24 16:31 深邃者 閱讀(236) | 評論 (0)編輯 收藏

            LINK錯誤

            vc編譯鏈接錯誤--LNK2001,LNK2019,

            --  LINK2001
            學習VC++時經常會遇到鏈接錯誤LNK2001,該錯誤非常討厭,因為對于
            編程者來說,最好改的錯誤莫過于編譯錯誤,而一般說來發生連接錯誤時,
            編譯都已通過。產生連接錯誤的原因非常多,尤其LNK2001錯誤,常常使人不
            明其所以然。如果不深入地學習和理解VC++,要想改正連接錯誤LNK2001非
            常困難。
              初學者在學習VC++的過程中,遇到的LNK2001錯誤的錯誤消息主要為:
              unresolved external symbol “symbol”(不確定的外部“符號”)。
              如果連接程序不能在所有的庫和目標文件內找到所引用的函數、變量或
            標簽,將產生此錯誤消息。一般來說,發生錯誤的原因有兩個:一是所引用
            的函數、變量不存在、拼寫不正確或者使用錯誤;其次可能使用了不同版本
            的連接庫。
              以下是可能產生LNK2001錯誤的原因:
              一.由于編碼錯誤導致的LNK2001。
              1.不相匹配的程序代碼或模塊定義(.DEF)文件能導致LNK2001。例如,
            如果在C++ 源文件內聲明了一變量“var1”,卻試圖在另一文件內以變量
            “VAR1”訪問該變量,將發生該錯誤。
              2.如果使用的內聯函數是在.CPP文件內定義的,而不是在頭文件內定
            義將導致LNK2001錯誤。
              3.調用函數時如果所用的參數類型同函數聲明時的類型不符將會產生
            LNK2001。
              4.試圖從基類的構造函數或析構函數中調用虛擬函數時將會導致LNK2001。
              5.要注意函數和變量的可公用性,只有全局變量、函數是可公用的。
              靜態函數和靜態變量具有相同的使用范圍限制。當試圖從文件外部訪問
            任何沒有在該文件內聲明的靜態變量時將導致編譯錯誤或LNK2001。
              函數內聲明的變量(局部變量) 只能在該函數的范圍內使用。
              C++ 的全局常量只有靜態連接性能。這不同于C,如果試圖在C++的
            多個文件內使用全局變量也會產生LNK2001錯誤。一種解決的方法是需要時在
            頭文件中加入該常量的初始化代碼,并在.CPP文件中包含該頭文件;另一種
            方法是使用時給該變量賦以常數。
              二.由于編譯和鏈接的設置而造成的LNK2001
              1.如果編譯時使用的是/NOD(/NODEFAULTLIB)選項,程序所需要的運行
            庫和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。
              其實,產生LNK2001的原因還有很多,以上的原因只是一部分而已,對初
            學者來說這些就夠理解一陣子了。但是,分析錯誤原因的目的是為了避免錯
            誤的發生。LNK2001錯誤雖然比較困難,但是只要注意到了上述問題,還是能
            夠避免和予以解決的。

            LNK2019
            函數只有申明,沒有實現時,或是DLL中的東東沒有export啊

            posted @ 2007-07-24 16:29 深邃者 閱讀(204) | 評論 (0)編輯 收藏

            內存檢測

            1)工程是MFC工程,或是工程的設置中有Use MFC in a Shared DLL,
            2)很多地方說是要定義以下宏
            #ifdef _DEBUG
            #define new DEBUG_NEW
            #endif
            但是我發現只要include <afx.h> 即可。(大家可以檢測一下)
            3)可以在F5運行程序后,在output窗口中看到如下的內存泄露的顯示。(只在debug下有用哦)
            4)如果有泄露,則顯示如下:
            Detected memory leaks!
            Dumping objects ->
            {214} normal block at 0x00D91618, 4 bytes long.
             Data: <    > 00 00 00 00
            {208} normal block at 0x00D914D0, 4 bytes long.
             Data: <    > 00 00 00 00
            {207} normal block at 0x00D91490, 4 bytes long.
             Data: <    > D0 14 D9 00
            {205} normal block at 0x00D91410, 4 bytes long.
             Data: <    > 00 00 00 00
            {204} normal block at 0x003AFFD8, 4 bytes long.
             Data: <    > 10 14 D9 00
            {203} normal block at 0x003AFF98, 4 bytes long.
             Data: <    > 00 00 00 00
            {202} normal block at 0x003AFF58, 4 bytes long.
             Data: <  : > 98 FF 3A 00
            {200} normal block at 0x003AFF18, 4 bytes long.
             Data: <    > 00 00 00 00
            Object dump complete.

            posted @ 2007-07-24 15:08 深邃者 閱讀(175) | 評論 (0)編輯 收藏

            僅列出標題
            共5頁: 1 2 3 4 5 
            色妞色综合久久夜夜| 国产精品久久久久久福利漫画 | 国产产无码乱码精品久久鸭 | 久久久免费精品re6| www亚洲欲色成人久久精品| 国产产无码乱码精品久久鸭| 国产精品日韩深夜福利久久| 91麻豆国产精品91久久久| 久久久久无码精品| 久久久久99精品成人片牛牛影视 | 亚洲国产精品婷婷久久| 久久久久久久亚洲Av无码| 久久影院综合精品| aaa级精品久久久国产片| 精品一区二区久久| 久久久久久久综合日本亚洲 | 久久久久久久久久久免费精品| 亚洲天堂久久精品| 久久精品女人天堂AV麻| 久久久精品视频免费观看| 精品免费久久久久久久| 无码国内精品久久人妻蜜桃| 久久久久亚洲精品无码蜜桃| 伊人久久综合成人网| 伊人久久大香线蕉AV色婷婷色| 国产亚州精品女人久久久久久 | 久久久久四虎国产精品| 国产精品久久久久久一区二区三区| 欧美成a人片免费看久久| 国产精品欧美久久久久天天影视| 久久国产精品久久久| 欧美大香线蕉线伊人久久| 久久精品国产亚洲av影院| 国产精品青草久久久久福利99 | 久久亚洲精品人成综合网| 久久久久久无码Av成人影院| 久久久久久人妻无码| 国产午夜电影久久| 久久精品国产免费观看三人同眠| 热99RE久久精品这里都是精品免费| AAA级久久久精品无码片|