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

            2006年7月17日

            10:04:20

            論調用約定

            在C語言中,假設我們有這樣的一個函數:

            int function(int a,int b)

            調用時只要用result = function(1,2)這樣的方式就可以使用這個函數。但是,當高級語言被編譯成計算機可以識別的機器碼時,有一個問題就凸現出來:在CPU中,計算機沒有辦法知道一個函數調用需要多少個、什么樣的參數,也沒有硬件可以保存這些參數。也就是說,計算機不知道怎么給這個函數傳遞參數,傳遞參數的工作必須由函數調用者和函數本身來協調。為此,計算機提供了一種被稱為棧的數據結構來支持參數傳遞。

            棧是一種先進后出的數據結構,棧有一個存儲區、一個棧頂指針。棧頂指針指向堆棧中第一個可用的數據項(被稱為棧頂)。用戶可以在棧頂上方向棧中加入數據,這個操作被稱為壓棧(Push),壓棧以后,棧頂自動變成新加入數據項的位置,棧頂指針也隨之修改。用戶也可以從堆棧中取走棧頂,稱為彈出棧(pop),彈出棧后,棧頂下的一個元素變成棧頂,棧頂指針隨之修改。

            函數調用時,調用者依次把參數壓棧,然后調用函數,函數被調用以后,在堆棧中取得數據,并進行計算。函數計算結束以后,或者調用者、或者函數本身修改堆棧,使堆?;謴驮b。

            在參數傳遞中,有兩個很重要的問題必須得到明確說明:

            • 當參數個數多于一個時,按照什么順序把參數壓入堆棧
            • 函數調用后,由誰來把堆?;謴驮b

            在高級語言中,通過函數調用約定來說明這兩個問題。常見的調用約定有:

            • stdcall
            • cdecl
            • fastcall
            • thiscall
            • naked call

            stdcall調用約定

            stdcall很多時候被稱為pascal調用約定,因為pascal是早期很常見的一種教學用計算機程序設計語言,其語法嚴謹,使用的函數調用約定就是stdcall。在Microsoft C++系列的C/C++編譯器中,常常用PASCAL宏來聲明這個調用約定,類似的宏還有WINAPI和CALLBACK。

            stdcall調用約定聲明的語法為(以前文的那個函數為例):

            int __stdcall function(int a,int b)

            stdcall的調用約定意味著:1)參數從右向左壓入堆棧,2)函數自身修改堆棧 3)函數名自動加前導的下劃線,后面緊跟一個@符號,其后緊跟著參數的尺寸

            以上述這個函數為例,參數b首先被壓棧,然后是參數a,函數調用function(1,2)調用處翻譯成匯編語言將變成:

            push 2 第二個參數入棧 push 1 第一個參數入棧 call function 調用參數,注意此時自動把cs:eip入棧

            而對于函數自身,則可以翻譯為:

            push ebp 保存ebp寄存器,該寄存器將用來保存堆棧的棧頂指針,可以在函數退出時恢復 mov ebp,esp 保存堆棧指針 mov eax,[ebp + 8H] 堆棧中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆棧中ebp + 12處保存了b mov esp,ebp 恢復esp pop ebp ret 8

            而在編譯時,這個函數的名字被翻譯成_function@8

            注意不同編譯器會插入自己的匯編代碼以提供編譯的通用性,但是大體代碼如此。其中在函數開始處保留esp到ebp中,在函數結束恢復是編譯器常用的方法。

            從函數調用看,2和1依次被push進堆棧,而在函數中又通過相對于ebp(即剛進函數時的堆棧指針)的偏移量存取參數。函數結束后,ret 8表示清理8個字節的堆棧,函數自己恢復了堆棧。

            cdecl調用約定

            cdecl調用約定又稱為C調用約定,是C語言缺省的調用約定,它的定義語法是:

            int function (int a ,int b) //不加修飾就是C調用約定 int __cdecl function(int a,int b)//明確指出C調用約定

            在寫本文時,出乎我的意料,發現cdecl調用約定的參數壓棧順序是和stdcall是一樣的,參數首先由有向左壓入堆棧。所不同的是,函數本身不清理堆棧,調用者負責清理堆棧。由于這種變化,C調用約定允許函數的參數的個數是不固定的,這也是C語言的一大特色。對于前面的function函數,使用cdecl后的匯編碼變成:

            調用處 push 1 push 2 call function add esp,8 注意:這里調用者在恢復堆棧被調用函數_function處 push ebp 保存ebp寄存器,該寄存器將用來保存堆棧的棧頂指針,可以在函數退出時恢復 mov ebp,esp 保存堆棧指針 mov eax,[ebp + 8H] 堆棧中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆棧中ebp + 12處保存了b mov esp,ebp 恢復esp pop ebp ret 注意,這里沒有修改堆棧

            MSDN中說,該修飾自動在函數名前加前導的下劃線,因此函數名在符號表中被記錄為_function,但是我在編譯時似乎沒有看到這種變化。

            由于參數按照從右向左順序壓棧,因此最開始的參數在最接近棧頂的位置,因此當采用不定個數參數時,第一個參數在棧中的位置肯定能知道,只要不定的參數個數能夠根據第一個后者后續的明確的參數確定下來,就可以使用不定參數,例如對于CRT中的sprintf函數,定義為:

            int sprintf(char* buffer,const char* format,...)

            由于所有的不定參數都可以通過format確定,因此使用不定個數的參數是沒有問題的。

            fastcall

            fastcall調用約定和stdcall類似,它意味著:

            • 函數的第一個和第二個DWORD參數(或者尺寸更小的)通過ecx和edx傳遞,其他參數通過從右向左的順序壓棧
            • 被調用函數清理堆棧
            • 函數名修改規則同stdcall

            其聲明語法為:int fastcall function(int a,int b)

            thiscall

            thiscall是唯一一個不能明確指明的函數修飾,因為thiscall不是關鍵字。它是C++類成員函數缺省的調用約定。由于成員函數調用還有一個this指針,因此必須特殊處理,thiscall意味著:

            • 參數從右向左入棧
            • 如果參數個數確定,this指針通過ecx傳遞給被調用者;如果參數個數不確定,this指針在所有參數壓棧后被壓入堆棧。
            • 對參數個數不定的,調用者清理堆棧,否則函數自己清理堆棧

            為了說明這個調用約定,定義如下類和使用代碼:

            class A { public: ?? int function1(int a,int b); ?? int function2(int a,...); }; int A::function1 (int a,int b) { ?? return a+b; } #include  int A::function2(int a,...) { ?? va_list ap; ?? va_start(ap,a); ?? int i; ?? int result = 0; ?? for(i = 0 ; i < a ; i ++) ?? { ???? result += va_arg(ap,int); ?? } ?? return result; } void callee() { ?? A a; ?? a.function1 (1,2); ?? a.function2(3,1,2,3); } 

            callee函數被翻譯成匯編后就變成:

            //函數function1調用 0401C1D push 2 00401C1F push 1 00401C21 lea ecx,[ebp-8] 00401C24 call function1 注意,這里this沒有被入棧 //函數function2調用 00401C29 push 3 00401C2B push 2 00401C2D push 1 00401C2F push 3 00401C31 lea eax,[ebp-8] 這里引入this指針 00401C34 push eax 00401C35 call function2 00401C3A add esp,14h

            可見,對于參數個數固定情況下,它類似于stdcall,不定時則類似cdecl

            naked call

            這是一個很少見的調用約定,一般程序設計者建議不要使用。編譯器不會給這種函數增加初始化和清理代碼,更特殊的是,你不能用return返回返回值,只能用插入匯編返回結果。這一般用于實模式驅動程序設計,假設定義一個求和的加法程序,可以定義為:

            __declspec(naked) int add(int a,int b) { __asm mov eax,a __asm add eax,b __asm ret } 

            注意,這個函數沒有顯式的return返回值,返回通過修改eax寄存器實現,而且連退出函數的ret指令都必須顯式插入。上面代碼被翻譯成匯編以后變成:

            mov eax,[ebp+8] add eax,[ebp+12] ret 8

            注意這個修飾是和__stdcall及cdecl結合使用的,前面是它和cdecl結合使用的代碼,對于和stdcall結合的代碼,則變成:

            __declspec(naked) int __stdcall function(int a,int b) { __asm mov eax,a __asm add eax,b __asm ret 8 //注意后面的8 } 

            至于這種函數被調用,則和普通的cdecl及stdcall調用函數一致。

            函數調用約定導致的常見問題

            如果定義的約定和使用的約定不一致,則將導致堆棧被破壞,導致嚴重問題,下面是兩種常見的問題:

            1. 函數原型聲明和函數體定義不一致
            2. DLL導入函數時聲明了不同的函數約定

            以后者為例,假設我們在dll種聲明了一種函數為:

            __declspec(dllexport) int func(int a,int b);//注意,這里沒有stdcall,使用的是cdecl

            使用時代碼為:

             typedef int (*WINAPI DLLFUNC)func(int a,int b); hLib = LoadLibrary(...); DLLFUNC func = (DLLFUNC)GetProcAddress(...)//這里修改了調用約定 result = func(1,2);//導致錯誤 

            由于調用者沒有理解WINAPI的含義錯誤的增加了這個修飾,上述代碼必然導致堆棧被破壞,MFC在編譯時插入的checkesp函數將告訴你,堆棧被破壞了。

            posted @ 2006-07-17 10:06 逃逃 閱讀(441) | 評論 (1)編輯 收藏


            2006年5月22日

            1、文件操作的方法

            ???使用Visual C++編程,有如下方法進行文件操作:

            (1)使用標準C運行庫函數,包括fopen、fclose、fseek等。

            ???(2)使用Win16下的文件和目錄操作函數,如lopen、lclose、lseek等。不過,在Win32下,這些函數主要是為了和Win16向后兼容。

            ???(3)使用Win32下的文件和目錄操作函數,如CreateFile,CopyFile,DeleteFile,FindNextFile,等等。

            ??????Win32下,打開和創建文件都由CreateFile完成,成功的話,得到一個Win32下的句柄,這不同于“C”的fopen返回的句柄。在Win16下,該句柄和C運行庫文件操作函數相容。但在Win32下,“C”的文件操作函數不能使用該句柄,如果需要的話,可以使用函數_open_osfhandle從Win32句柄得到一個“C”文件函數可以使用的文件句柄。 關閉文件使用Win32的CloseHandle。 在Win32下,CreateFile可以操作的對象除了磁盤文件外,還包括設備文件如通訊端口、管道、控制臺輸入、郵件槽等等。

            (4)使用CFile和其派生類進行文件操作。CFile從CObject派生,其派生類包括操作文本文件的CStdioFile,操作內存文件的CmemFile,等等。CFile是建立在Win32的文件操作體系的基礎上,它封裝了部分Win32文件操作函數。最好是使用CFile類(或派生類)的對象來操作文件,必要的話,可以從這些類派生自己的文件操作類。統一使用CFile的界面可以得到好的移植性。


            2、文件操作的方法
            MFC用一些類來封裝文件訪問的Win32 API。以CFile為基礎,從CFile派生出幾個類,如CStdioFile,CMemFile,MFC內部使用的CMiororFile,等等。

            2.1、CFile的結構

            ?????2.1.1、CFile定義的枚舉類型

            ???????????????CFile類定義了一些和文件操作相關的枚舉類型,主要有四種:OpenFlags,Attribute,SeekPosition,hFileNull。下面,分別解釋這些枚舉類型。

            1. OpenFlags

              OpenFlags定義了13種文件訪問和共享模式:

              enum OpenFlags {

              //第一(從右,下同)至第二位,打開文件時訪問模式,讀/寫/讀寫

              modeRead = 0x0000,

              modeWrite = 0x0001,

              modeReadWrite = 0x0002,

              shareCompat = 0x0000, //32位MFC中沒用

              //第五到第七位,打開文件時的共享模式

              shareExclusive = 0x0010,//獨占方式,禁止其他進程讀寫

              shareDenyWrite = 0x0020,//禁止其他進程寫

              shareDenyRead = 0x0030,//禁止其他進程讀

              shareDenyNone = 0x0040,//允許其他進程寫

              //第八位,打開文件時的文件繼承方式

              modeNoInherit = 0x0080,//不允許子進程繼承

              //第十三、十四位,是否創建新文件和創建方式

              modeCreate = 0x1000,//創建新文件,文件長度0

              modeNoTruncate = 0x2000,//創建新文件時如文件已存在則打開

              //第十五、十六位,文件以二進制或者文本方式打開,在派生類CStdioFile中用

              typeText = 0x4000,

              typeBinary = (int)0x8000

              };

            2. Attribute

              Attribute定義了文件屬性:正常、只讀、隱含、系統文件,文件或者目錄等。

              enum Attribute {

              normal = 0x00,

              readOnly = 0x01,

              hidden = 0x02,

              system = 0x04,

              volume = 0x08,

              directory = 0x10,

              archive = 0x20

              }

            3. SeekPosition

              SeekPosition定義了三種文件位置:頭、尾、當前:

              enum SeekPosition{

              begin = 0x0,

              current = 0x1,

              end = 0x2

              };

            4. hFileNull

            hFileNull定義了空文件句柄

            enum { hFileNull = -1 };

            2.1.2、CFile的其他一些成員變量

            CFile除了定義枚舉類型,還定義了一些成員變量。例如:

            UINT m_hFile

            該成員變量是public訪問屬性,保存::CreateFile返回的操作系統的文件句柄。MFC重載了運算符號HFILE來返回m_hFile,這樣在使用HFILE類型變量的地方可以使用CFile對象。

            BOOL m_bCloseOnDelete;

            CString m_strFileName;

            這兩個成員變量是protected訪問屬性。m_bCloseOnDelete用來指示是否在關閉文件時刪除CFile對象;m_strFileName用來保存文件名。

            2.1.3、CFile的成員函數

            CFile的成員函數實現了對Win32文件操作函數的封裝,完成以下動作:打開、創建、關閉文件,文件指針定位,文件的鎖定與解鎖,文件狀態的讀取和修改,等等。其中,用到了m_hFile文件句柄的一般是虛擬函數,和此無關的一般是靜態成員函數。一般地,成員函數被映射到對應的Win32函數,如表11-1所示。

            表11-1 CFile函數對Win32文件函數的封裝

            虛擬

            靜態

            成員函數

            對應的Win32函數

            文件的創建、打開、關閉

             

            Abort

            CloseHandle

             

            Duplicate

            DuplicateHandle

             

            Open

            CreateFile

             

            Close

            CloseHandle

            文件的讀寫

             

            Read

            ReadFile

               

            ReadHuge(向后兼容)

            調用Read成員函數

             

            Write

            WriteFile

               

            WriteHuage(向后兼容)

            調用Write成員函數

             

            Flush

            FlushFileBuffers

            文件定位

             

            Seek

            SetFilePointer

               

            SeekToBegin

            調用Seek成員函數

               

            SeekToEnd

            調用Seek成員函數

             

            GetLength

            調用Seek成員函數

             

            SetLength

            SetEndOfFile

            文件的鎖定/解鎖

             

            LockRange

            LockFile

             

            UnlockRange

            UnlockFile

            文件狀態操作函數

             

            GetPosition

            SetFilePointer

               

            GetStatus(CFileStatus&)

            GetFileTime,GetFileSize等

             

            GetStatus(LPSTR lpszFileName CFileStatus&)

            FindFirstFile

             

            GetFileName

            不是簡單地映射到某個函數

             

            GetFileTitle

             

             

            GetFilePath

             

             

            SetFilePath

             
             

            SetStatus

             

            改名和刪除

             

            Rename

            MoveFile

             

            Remove

            DeleteFile


            2.1.4、CFile的部分實現


            這里主要討論CFile對象的構造函數和文件的打開/創建的過程。

            1. 構造函數

            CFile有如下幾個構造函數:

            • CFile()

            缺省構造函數,僅僅構造一個CFile對象,還必須使用Open成員函數來打開文件。

            • CFile(int hFile)

            已經打開了一個文件hFile,在此基礎上構造一個CFile對象來給它打包。HFile將被賦值給CFile的成員變量m_hFile。

            • CFile(LPCTSTR lpszFileName, UINT nOpenFlags)

            指定一個文件名和文件打開方式,構造CFile對象,調用Open打開/創建文件,把文件句柄保存到m_hFile。

            1. 打開/創建文件

            Open的原型如下:

            BOOL CFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags,

            CFileException* pException)

            Open調用Win32函數::CreateFile打開文件,并把文件句柄保存到成員變量m_hFile中。

            CreateFile函數的原型如下:

            HANDLE CreateFile(

            LPCTSTR lpFileName,// pointer to name of the file

            DWORD dwDesiredAccess,// access (read-write) mode

            DWORD dwShareMode,// share mode

            LPSECURITY_ATTRIBUTES lpSecurityAttributes, //pointer to security descriptor

            DWORD dwCreationDistribution,// how to create

            DWORD dwFlagsAndAttributes,// file attributes

            HANDLE hTemplateFile// handle to file with attributes to copy

            );

            顯然,Open必須把自己的兩個參數lpszFileName和nOpenFlags映射到CreateFile的七個參數上。

            從OpenFlags的定義可以看出,(nOpenFlags & 3)表示了讀寫標識,映射成變量dwAccess,可以取值為Win32的GENERIC_READ、GENERIC_WRITE、GENERIC_READ|GENERIC_WRITE。

            (nOpenFlags & 0x70)表示了共享模式,映射成變量dwShareMode,可以取值為Win32的FILE_SHARE_READ、FILE_SHARE_WRITE、FILE_SHARE_WRITE|FILE_SHARE_READ。

            Open定義了一個局部的SECURITY_ATTRIBUTES變量sa,(nOpenFlags & 0x80)被賦值給sa.bInheritHandle。

            (nOpenFlags & modeCreate)表示了創建方式,映射成變量dwCreateFlag,可以取值為Win32的OPEN_ALWAYS、CREATE_ALWAYS、OPEN_EXISTING。

            在生成了上述參數之后,先調用::CreateFile:

            HANDLE hFile =::CreateFile(lpszFileName,

            dwAccess, dwShareMode, &sa,

            dwCreateFlag, FILE_ATTRIBUTE_NORMAL, NULL);

            然后,hFile被賦值給成員變量m_hFile,m_bCloseOnDelete被設置為TRUE。

            由上可以看出,CFile打開(創建)一個文件時大大簡化了:: CreateFile函數的復雜性,即只需要指定一個文件名、一個打開文件的參數即可。若該參數指定為0,則表示以只讀方式打開一個存在的文件,獨占使用,不允許子進程繼承。

            在CFile對象使用時,如果它是在堆中分配的,則應該銷毀它;如果在棧中分配的,則CFile對象將被自動銷毀。銷毀時析構函數被調用,析構函數是虛擬函數。若m_bCloseOnDelete為真且m_hFile非空,則析構函數調用Close關閉文件。

            至于其他CFile成員函數的實現,這里不作分析了。

            2.1.5、CFile的派生類

            這里主要簡要地介紹CStdioFile和CmemFile及CFileFind。

            1. CStdioFile

              CStdioFile對文本文件進行操作。

              CStdioFile定義了新的成員變量m_pStream,類型是FILE*。在打開或者創建文件時,使用_open_osfhandle從m_hFile(Win32文件句柄)得到一個“C”的FILE類型的文件指針,然后,在文件操作中,使用“C”的文件操作函數。例如,讀文件使用_fread,而不是::ReadFile,寫文件使用了_fwrite,而不是::WriteFile,等等。m_hFile是CFile的成員變量。

              另外,CStdioFile不支持CFile的Dumplicate、LockRange、UnlockRange操作,但是實現了兩個新的操作ReadString和WriteString。

            2. CMemFile

              CMemFile把一塊內存當作一個文件來操作,所以,它沒有打開文件的操作,而是設計了Attach和Detach用來分配或者釋放一塊內存。相應地,它提供了Alloc、Free虛擬函數來操作內存文件,它覆蓋了Read、Write來讀寫內存文件。

            3. CFileFind

            為了方便文件查找,MFC把有關功能歸結成為一個類CFileFind。CFileFind派生于CObject類。首先,它使用FindFile和FineNextFile包裝了Win32函數::FindFirstFile和::FindNextFile;其次,它提供了許多函數用來獲取文件的狀態或者屬性。

            使用CFileStatus結構來描述文件的屬性,其定義如下:

            struct CFileStatus

            {

            CTime m_ctime; // 文件創建時間

            CTime m_mtime; // 文件最近一次修改時間

            CTime m_atime; // 文件最近一次訪問時間

            LONG m_size; // 文件大小

            BYTE m_attribute; // 文件屬性

            BYTE _m_padding; // 沒有實際含義,用來增加一個字節

            TCHAR m_szFullName[_MAX_PATH]; //絕對路徑

            #ifdef _DEBUG

            //實現Dump虛擬函數,輸出文件屬性

            void Dump(CDumpContext& dc) const;

            #endif

            };

            例如:

            CFileStatus status;

            pFile->GetStatus(status);

            #ifdef _DEBUG

            status.dump(afxDump);

            #endif

             

            posted @ 2006-05-22 16:13 逃逃 閱讀(674) | 評論 (1)編輯 收藏


            2006年4月17日

            10.1內存分配?
            ??10.1.1?內存分配函數

            MFCWin32或者C語言的內存分配API,有四種內存分配API可供使用。

            1. Win32的堆分配函數

              每一個進程都可以使用堆分配函數創建一個私有的堆──調用進程地址空間的一個或者多個頁面。DLL創建的私有堆必定在調用DLL的進程的地址空間內,只能被調用進程訪問。

              HeapCreate用來創建堆;HeapAlloc用來從堆中分配一定數量的空間,HeapAlloc分配的內存是不能移動的;HeapSize可以確定從堆中分配的空間的大??;HeapFree用來釋放從堆中分配的空間;HeapDestroy銷毀創建的堆。

            2. Windows傳統的全局或者局部內存分配函數

              由于Win32采用平面內存結構模式,Win32下的全局和局部內存函數除了名字不同外,其他完全相同。任一函數都可以用來分配任意大小的內存(僅僅受可用物理內存的限制)。用法可以和Win16下基本一樣。

              Win32下保留這類函數保證了和Win16的兼容。

            3. C語言的標準內存分配函數

              C語言的標準內存分配函數包括以下函數:

              malloc,calloc,realloc,free,等。

              這些函數最后都映射成堆API函數,所以,malloc分配的內存是不能移動的。這些函數的調式版本為

              malloc_dbg,calloc_dbg,realloc_dbg,free_dbg,等。

            4. Win32的虛擬內存分配函數

            虛擬內存API是其他API的基礎。虛擬內存API以頁為最小分配單位,X86上頁長度為4KB,可以用GetSystemInfo函數提取頁長度。虛擬內存分配函數包括以下函數:

            • LPVOID VirtualAlloc(LPVOID lpvAddress,

            DWORD cbSize,

            DWORD fdwAllocationType,

            DWORD fdwProtect);

            該函數用來分配一定范圍的虛擬頁。參數1指定起始地址;參數2指定分配內存的長度;參數3指定分配方式,取值MEM_COMMINT或者MEM_RESERVE;參數4指定控制訪問本次分配的內存的標識,取值為PAGE_READONLY、PAGE_READWRITE或者PAGE_NOACCESS。

            • LPVOID VirtualAllocEx(HANDLE process,

            LPVOID lpvAddress,

            DWORD cbSize,

            DWORD fdwAllocationType,

            DWORD fdwProtect);

            該函數功能類似于VirtualAlloc,但是允許指定進程process。VirtaulFree、VirtualProtect、VirtualQuery都有對應的擴展函數。

            • BOOL VirtualFree(LPVOID lpvAddress,

            DWORD dwSize,

            DWORD dwFreeType);

            該函數用來回收或者釋放分配的虛擬內存。參數1指定希望回收或者釋放內存的基地址;如果是回收,參數2可以指向虛擬地址范圍內的任何地方,如果是釋放,參數2必須是VirtualAlloc返回的地址;參數3指定是否釋放或者回收內存,取值為MEM_DECOMMINT或者MEM_RELEASE。

            • BOOL VirtualProtect(LPVOID lpvAddress,

            DWORD cbSize,

            DWORD fdwNewProtect,

            PDWORD pfdwOldProtect);

            該函數用來把已經分配的頁改變成保護頁。參數1指定分配頁的基地址;參數2指定保護頁的長度;參數3指定頁的保護屬性,取值PAGE_READ、PAGE_WRITE、PAGE_READWRITE等等;參數4用來返回原來的保護屬性。

            • DWORD VirtualQuery(LPCVOID lpAddress,

            PMEMORY_BASIC_INFORMATION lpBuffer,

            DWORD dwLength

            );

            該函數用來查詢內存中指定頁的特性。參數1指向希望查詢的虛擬地址;參數2是指向內存基本信息結構的指針;參數3指定查詢的長度。

            • BOOL VirtualLock(LPVOID lpAddress,DWORD dwSize);

            該函數用來鎖定內存,鎖定的內存頁不能交換到頁文件。參數1指定要鎖定內存的起始地址;參數2指定鎖定的長度。

            • BOOL VirtualUnLock(LPVOID lpAddress,DWORD dwSize);

            參數1指定要解鎖的內存的起始地址;參數2指定要解鎖的內存的長度。


            C++的new 和 delete操作符

            MFC定義了兩種作用范圍的new和delete操作符。對于new,不論哪種,參數1類型必須是size_t,且返回void類型指針。

            1. 全局范圍內的new和delete操作符

              原型如下:

              void _cdecl ::operator new(size_t nSize);

              void __cdecl operator delete(void* p);

              調試版本:

              void* __cdecl operator new(size_t nSize, int nType,

              LPCSTR lpszFileName, int nLine)

            2. 類定義的new和delete操作符

            原型如下:

            void* PASCAL classname::operator new(size_t nSize);

            void PASCAL classname::operator delete(void* p);

            類的operator new操作符是類的靜態成員函數,對該類的對象來說將覆蓋全局的operator new。全局的operator new用來給內部類型對象(如int)、沒有定義operator new操作符的類的對象分配內存。

            new操作符被映射成malloc或者malloc_dbg,delete被映射成free或者free_dbg。



            10.1.2調試手段

            MFC應用程序可以使用C運行庫的調試手段,也可以使用MFC提供的調試手段。兩種調試手段分別論述如下。

            1. C運行庫提供和支持的調試功能

              C運行庫提供和支持的調試功能如下:

              1. 調試信息報告函數

                用來報告應用程序的調試版本運行時的警告和出錯信息。包括:

                _CrtDbgReport 用來報告調試信息;

                _CrtSetReportMode 設置是否警告、出錯或者斷言信息;

                _CrtSetReportFile 設置是否把調試信息寫入到一個文件。

              2. 條件驗證或者斷言宏:

                斷言宏主要有:

                assert 檢驗某個條件是否滿足,不滿足終止程序執行。

                驗證函數主要有:

                _CrtIsValidHeapPointer 驗證某個指針是否在本地堆中;

                _CrtIsValidPointer 驗證指定范圍的內存是否可以讀寫;

                _CrtIsMemoryBlock 驗證某個內存塊是否在本地堆中。

              3. 內存(堆)調試:

              malloc_dbg 分配內存時保存有關內存分配的信息,如在什么文件、哪一行分配的內存等。有一系列用來提供內存診斷的函數:

              _CrtMemCheckpoint 保存內存快照在一個_CrtMemState結構中;

              _CrtMemDifference 比較兩個_CrtMemState;

              _CrtMemDumpStatistics 轉儲輸出一_CrtMemState結構的內容;

              _CrtMemDumpAllObjectsSince 輸出上次快照或程序開始執行以來在堆中分配的所有對象的信息;

              _CrtDumpMemoryLeaks 檢測程序執行以來的內存漏洞,如果有漏洞則輸出所有分配的對象。

              2.????? MFC提供的調試手段

              MFC在C運行庫提供和支持的調試功能基礎上,設計了一些類、函數等來協助調試。

              1. MFC的TRACE、ASSERT

                ASSERT

                使用ASSERT斷言判定程序是否可以繼續執行。

                TRACE

                使用TRACE宏顯示或者打印調試信息。TRACE是通過函數AfxTrace實現的。由于AfxTrace函數使用了cdecl調用約定,故可以接受個數不定的參數,如同printf函數一樣。它的定義和實現如下:

                void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...)

                {

                #ifdef _DEBUG // all AfxTrace output is controlled by afxTraceEnabled

                if (!afxTraceEnabled)

                return;

                #endif

                //處理個數不定的參數

                va_list args;

                va_start(args, lpszFormat);

                int nBuf;

                TCHAR szBuffer[512];

                nBuf = _vstprintf(szBuffer, lpszFormat, args);

                ASSERT(nBuf < _countof(szBuffer));

                if ((afxTraceFlags & traceMultiApp) && (AfxGetApp() != NULL))

                afxDump << AfxGetApp()->m_pszExeName << ": ";

                afxDump << szBuffer;

                va_end(args);

                }

                #endif //_DEBUG

                在程序源碼中,可以控制是否顯示跟蹤信息,顯示什么跟蹤信息。如果全局變量afxTraceEnabled為TRUE,則TRACE宏可以輸出;否則,沒有TRACE信息被輸出。如果通過afxTraceFlags指定了跟蹤什么消息,則輸出有關跟蹤信息,例如為了指定“Multilple Application Debug”,令AfxTraceFlags|=traceMultiApp。可以跟蹤的信息有:

                enum AfxTraceFlags

                {

                traceMultiApp = 1, // multi-app debugging

                traceAppMsg = 2, // main message pump trace (includes DDE)

                traceWinMsg = 4, // Windows message tracing

                traceCmdRouting = 8, // Windows command routing trace

                //(set 4+8 for control notifications)

                traceOle = 16, // special OLE callback trace

                traceDatabase = 32, // special database trace

                traceInternet = 64 // special Internet client trace

                };

                這樣,應用程序可以在需要的地方指定afxTraceEnabled的值打開或者關閉TRACE開關,指定AfxTraceFlags的值過濾跟蹤信息。

                Visual C++提供了一個TRACE工具,也可以用來完成上述功能。

                為了顯示消息信息,MFC內部定義了一個AFX_MAP_MESSAG類型的數組allMessages,儲存了Windows消息和消息名映射對。例如:

                allMessages[1].nMsg = WM_CREATE,

                allMessages[1].lpszMsg = “WM_CREATE”

                MFC內部還使用函數_AfxTraceMsg顯示跟蹤消息,它可以接收一個字符串和一個MSG指針,然后,把該字符串和MSG的各個域的信息組合成一個大的字符串并使用AfxTrace顯示出來。

                allMessages和函數_AfxTraceMsg的詳細實現可以參見AfxTrace.cpp。

              2. MFC對象內容轉儲

                對象內容轉儲是CObject類提供的功能,所有從它派生的類都可以通過覆蓋虛擬函數DUMP來支持該功能。在講述CObject類時曾提到過。

                虛擬函數Dump的定義:

                class ClassName : public CObject

                {

                public:

                #ifdef _DEBUG

                virtual void Dump( CDumpContext& dc ) const;

                #endif

                };

                在使用Dump時,必須給它提供一個CDumpContext類型的參數,該參數指定的對象將負責輸出調試信息。為此,MFC提供了一個預定義的全局CDumpContext對象afxDump,它把調試信息輸送給調試器的調試窗口。從前面AfxTrace的實現可以知道,MFC使用了afxDump輸出跟蹤信息到調試窗口。

                CDumpContext類沒有基類,它提供了以文本形式輸出診斷信息的功能。

                例如:

                CPerson* pMyPerson = new CPerson;

                // set some fields of the CPerson object...

                //...

                // now dump the contents

                #ifdef _DEBUG

                pMyPerson->Dump( afxDump );

                #endif

              3. MFC對象有效性檢測

              對象有效性檢測是CObject類提供的功能,所有從它派生的類都可以通過覆蓋虛擬函數AssertValid來支持該功能。在講述CObject類時曾提到過。

              虛擬函數AssertValid的定義:

              class ClassName : public CObject

              {

              public:

              #ifdef _DEBUG

              virtual void AssertValid( ) const;

              #endif

              };

              使用ASSERT_VALID宏判斷一個對象是否有效,該對象的類必須覆蓋了AssertValid函數。形式為:ASSERT_VALID(pObject)。

              另外,MFC提供了一些函數來判斷地址是否有效,如:

              AfxIsMemoryBlock,AfxIsString,AfxIsValidAddress。

              10.1.3內存診斷

              MFC使用DEBUG_NEW來跟蹤內存分配時的執行的源碼文件和行數。

              把#define new DEBUG_NEW插入到每一個源文件中,這樣,調試版本就使用_malloc_dbg來分配內存。MFC Appwizard在創建框架文件時已經作了這樣的處理。

              1. AfxDoForAllObjects

                MFC提供了函數AfxDoForAllObjects來追蹤動態分配的內存對象,函數原型如下:

                void AfxDoForAllObjects( void (*pfn)(CObject* pObject,

                void* pContext), void* pContext );

                其中:

                參數1是一個函數指針,AfxDoForAllObjects對每個對象調用該指針表示的函數。

                參數2將傳遞給參數1指定的函數。

                AfxDoForAllObjects可以檢測到所有使用new分配的CObject對象或者CObject類派生的對象,但全局對象、嵌入對象和棧中分配的對象除外。

              10.1.4內存漏洞檢測

              僅僅用于new的DEBUG版本分配的內存。

              完成內存漏洞檢測,需要如下系列步驟:

              • 調用AfxEnableMemoryTracking(TRUE/FALSE)打開/關閉內存診斷。在調試版本下,缺省是打開的;關閉內存診斷可以加快程序執行速度,減少診斷輸出。

              • 使用MFC全局變量afxMemDF更精確地指定診斷輸出的特征,缺省值是allocMemDF,可以取如下值或者這些值相或:

              afxMemDF,delayFreeMemDF,checkAlwaysMemDF

              其中:allocMemDF表示可以進行內存診斷輸出;delayFreeMemDF表示是否是在應用程序結束時才調用free或者delete,這樣導致程序最大可能的分配內存;checkAlwaysMemDF表示每一次分配或者釋放內存之后都調用函數AfxCheckMemory進行內存檢測(AfxCheckMemory檢查堆中所有通過new分配的內存(不含malloc))。

              這一步是可選步驟,非必須。

              • 創建一個CMemState類型的變量oldMemState,調用CMemState的成員函數CheckPoint獲得初次內存快照。

              • 執行了系列內存分配或者釋放之后,創建另一個CMemState類型變量newMemState,調用CMemState的成員函數CheckPoint獲得新的內存快照。

              • 創建第三個CMemState類型變量difMemState,調用CMemState的成員函數Difference比較oldMemState和newMemState,結果保存在變量difMemState中。如果沒有不同,則返回FALSE,否則返回TRUE。

              • 如果不同,則調用成員函數DumpStatistics輸出比較結果。

              例如:

              // Declare the variables needed

              #ifdef _DEBUG

              CMemoryState oldMemState, newMemState, diffMemState;

              oldMemState.Checkpoint();

              #endif

              // do your memory allocations and deallocations...

              CString s = "This is a frame variable";

              // the next object is a heap object

              CPerson* p = new CPerson( "Smith", "Alan", "581-0215" );

              #ifdef _DEBUG

              newMemState.Checkpoint();

              if( diffMemState.Difference( oldMemState, newMemState ) )

              {

              TRACE( "Memory leaked!\n" );

              diffMemState.DumpStatistics();

              //or diffMemState.DumpAllObjectsSince();

              }

              #endif

              MFC在應用程序(調試版)結束時,自動進行內存漏洞檢測,如果存在漏洞,則輸出漏洞的有關信息。

            posted @ 2006-04-17 16:09 逃逃 閱讀(663) | 評論 (2)編輯 收藏


            僅列出標題  

            posts - 3, comments - 4, trackbacks - 0, articles - 0

            Copyright © 逃逃

            久久91精品久久91综合| 久久婷婷色香五月综合激情| 99久久精品国产麻豆| 久久精品国产亚洲网站| 久久夜色精品国产亚洲av| 97视频久久久| 亚洲国产精品热久久| 理论片午午伦夜理片久久 | 亚洲乱码精品久久久久..| 久久精品国产亚洲AV电影| 狠狠色伊人久久精品综合网 | 老司机午夜网站国内精品久久久久久久久| 亚洲国产综合久久天堂| 狠狠色丁香久久综合五月| 色青青草原桃花久久综合| 久久国产成人精品麻豆| 少妇人妻综合久久中文字幕| 国产精品热久久无码av| 99久久国语露脸精品国产| 狠狠精品久久久无码中文字幕| 久久精品国产影库免费看| 一本色道久久HEZYO无码| 久久亚洲天堂| 国产L精品国产亚洲区久久| 久久99亚洲网美利坚合众国| 亚洲精品午夜国产VA久久成人| 午夜精品久久影院蜜桃 | 国产精品欧美亚洲韩国日本久久| 日韩久久久久久中文人妻| 久久WWW免费人成一看片| 久久久精品人妻无码专区不卡| 久久99国产亚洲高清观看首页| 久久久久久国产精品免费无码 | 香蕉aa三级久久毛片| 久久久WWW成人免费毛片| 国产激情久久久久影院老熟女| 久久Av无码精品人妻系列| 久久久久亚洲AV无码麻豆| 亚洲精品乱码久久久久久蜜桃不卡 | 伊人久久大香线蕉AV一区二区| 久久亚洲av无码精品浪潮|