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

            論調(diào)用約定

            在C語言中,假設(shè)我們有這樣的一個(gè)函數(shù):

            int function(int a,int b)

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

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

            函數(shù)調(diào)用時(shí),調(diào)用者依次把參數(shù)壓棧,然后調(diào)用函數(shù),函數(shù)被調(diào)用以后,在堆棧中取得數(shù)據(jù),并進(jìn)行計(jì)算。函數(shù)計(jì)算結(jié)束以后,或者調(diào)用者、或者函數(shù)本身修改堆棧,使堆棧恢復(fù)原裝。

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

            • 當(dāng)參數(shù)個(gè)數(shù)多于一個(gè)時(shí),按照什么順序把參數(shù)壓入堆棧
            • 函數(shù)調(diào)用后,由誰來把堆棧恢復(fù)原裝

            在高級語言中,通過函數(shù)調(diào)用約定來說明這兩個(gè)問題。常見的調(diào)用約定有:

            • stdcall
            • cdecl
            • fastcall
            • thiscall
            • naked call

            stdcall調(diào)用約定

            stdcall很多時(shí)候被稱為pascal調(diào)用約定,因?yàn)閜ascal是早期很常見的一種教學(xué)用計(jì)算機(jī)程序設(shè)計(jì)語言,其語法嚴(yán)謹(jǐn),使用的函數(shù)調(diào)用約定就是stdcall。在Microsoft C++系列的C/C++編譯器中,常常用PASCAL宏來聲明這個(gè)調(diào)用約定,類似的宏還有WINAPI和CALLBACK。

            stdcall調(diào)用約定聲明的語法為(以前文的那個(gè)函數(shù)為例):

            int __stdcall function(int a,int b)

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

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

            push 2 第二個(gè)參數(shù)入棧 push 1 第一個(gè)參數(shù)入棧 call function 調(diào)用參數(shù),注意此時(shí)自動(dòng)把cs:eip入棧

            而對于函數(shù)自身,則可以翻譯為:

            push ebp 保存ebp寄存器,該寄存器將用來保存堆棧的棧頂指針,可以在函數(shù)退出時(shí)恢復(fù) 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 恢復(fù)esp pop ebp ret 8

            而在編譯時(shí),這個(gè)函數(shù)的名字被翻譯成_function@8

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

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

            cdecl調(diào)用約定

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

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

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

            調(diào)用處 push 1 push 2 call function add esp,8 注意:這里調(diào)用者在恢復(fù)堆棧被調(diào)用函數(shù)_function處 push ebp 保存ebp寄存器,該寄存器將用來保存堆棧的棧頂指針,可以在函數(shù)退出時(shí)恢復(fù) 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 恢復(fù)esp pop ebp ret 注意,這里沒有修改堆棧

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

            由于參數(shù)按照從右向左順序壓棧,因此最開始的參數(shù)在最接近棧頂?shù)奈恢茫虼水?dāng)采用不定個(gè)數(shù)參數(shù)時(shí),第一個(gè)參數(shù)在棧中的位置肯定能知道,只要不定的參數(shù)個(gè)數(shù)能夠根據(jù)第一個(gè)后者后續(xù)的明確的參數(shù)確定下來,就可以使用不定參數(shù),例如對于CRT中的sprintf函數(shù),定義為:

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

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

            fastcall

            fastcall調(diào)用約定和stdcall類似,它意味著:

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

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

            thiscall

            thiscall是唯一一個(gè)不能明確指明的函數(shù)修飾,因?yàn)閠hiscall不是關(guān)鍵字。它是C++類成員函數(shù)缺省的調(diào)用約定。由于成員函數(shù)調(diào)用還有一個(gè)this指針,因此必須特殊處理,thiscall意味著:

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

            為了說明這個(gè)調(diào)用約定,定義如下類和使用代碼:

            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函數(shù)被翻譯成匯編后就變成:

            //函數(shù)function1調(diào)用 0401C1D push 2 00401C1F push 1 00401C21 lea ecx,[ebp-8] 00401C24 call function1 注意,這里this沒有被入棧 //函數(shù)function2調(diào)用 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

            可見,對于參數(shù)個(gè)數(shù)固定情況下,它類似于stdcall,不定時(shí)則類似cdecl

            naked call

            這是一個(gè)很少見的調(diào)用約定,一般程序設(shè)計(jì)者建議不要使用。編譯器不會給這種函數(shù)增加初始化和清理代碼,更特殊的是,你不能用return返回返回值,只能用插入?yún)R編返回結(jié)果。這一般用于實(shí)模式驅(qū)動(dòng)程序設(shè)計(jì),假設(shè)定義一個(gè)求和的加法程序,可以定義為:

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

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

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

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

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

            至于這種函數(shù)被調(diào)用,則和普通的cdecl及stdcall調(diào)用函數(shù)一致。

            函數(shù)調(diào)用約定導(dǎo)致的常見問題

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

            1. 函數(shù)原型聲明和函數(shù)體定義不一致
            2. DLL導(dǎo)入函數(shù)時(shí)聲明了不同的函數(shù)約定

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

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

            使用時(shí)代碼為:

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

            由于調(diào)用者沒有理解WINAPI的含義錯(cuò)誤的增加了這個(gè)修飾,上述代碼必然導(dǎo)致堆棧被破壞,MFC在編譯時(shí)插入的checkesp函數(shù)將告訴你,堆棧被破壞了。

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


            2006年5月22日

            1、文件操作的方法

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

            (1)使用標(biāo)準(zhǔn)C運(yùn)行庫函數(shù),包括fopen、fclose、fseek等。

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

            ???(3)使用Win32下的文件和目錄操作函數(shù),如CreateFile,CopyFile,DeleteFile,F(xiàn)indNextFile,等等。

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

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


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

            2.1、CFile的結(jié)構(gòu)

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

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

            1. OpenFlags

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

              enum OpenFlags {

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

              modeRead = 0x0000,

              modeWrite = 0x0001,

              modeReadWrite = 0x0002,

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

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

              shareExclusive = 0x0010,//獨(dú)占方式,禁止其他進(jìn)程讀寫

              shareDenyWrite = 0x0020,//禁止其他進(jìn)程寫

              shareDenyRead = 0x0030,//禁止其他進(jìn)程讀

              shareDenyNone = 0x0040,//允許其他進(jìn)程寫

              //第八位,打開文件時(shí)的文件繼承方式

              modeNoInherit = 0x0080,//不允許子進(jìn)程繼承

              //第十三、十四位,是否創(chuàng)建新文件和創(chuàng)建方式

              modeCreate = 0x1000,//創(chuàng)建新文件,文件長度0

              modeNoTruncate = 0x2000,//創(chuàng)建新文件時(shí)如文件已存在則打開

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

              typeText = 0x4000,

              typeBinary = (int)0x8000

              };

            2. Attribute

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

              enum Attribute {

              normal = 0x00,

              readOnly = 0x01,

              hidden = 0x02,

              system = 0x04,

              volume = 0x08,

              directory = 0x10,

              archive = 0x20

              }

            3. SeekPosition

              SeekPosition定義了三種文件位置:頭、尾、當(dāng)前:

              enum SeekPosition{

              begin = 0x0,

              current = 0x1,

              end = 0x2

              };

            4. hFileNull

            hFileNull定義了空文件句柄

            enum { hFileNull = -1 };

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

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

            UINT m_hFile

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

            BOOL m_bCloseOnDelete;

            CString m_strFileName;

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

            2.1.3、CFile的成員函數(shù)

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

            表11-1 CFile函數(shù)對Win32文件函數(shù)的封裝

            虛擬

            靜態(tài)

            成員函數(shù)

            對應(yīng)的Win32函數(shù)

            文件的創(chuàng)建、打開、關(guān)閉

             

            Abort

            CloseHandle

             

            Duplicate

            DuplicateHandle

             

            Open

            CreateFile

             

            Close

            CloseHandle

            文件的讀寫

             

            Read

            ReadFile

               

            ReadHuge(向后兼容)

            調(diào)用Read成員函數(shù)

             

            Write

            WriteFile

               

            WriteHuage(向后兼容)

            調(diào)用Write成員函數(shù)

             

            Flush

            FlushFileBuffers

            文件定位

             

            Seek

            SetFilePointer

               

            SeekToBegin

            調(diào)用Seek成員函數(shù)

               

            SeekToEnd

            調(diào)用Seek成員函數(shù)

             

            GetLength

            調(diào)用Seek成員函數(shù)

             

            SetLength

            SetEndOfFile

            文件的鎖定/解鎖

             

            LockRange

            LockFile

             

            UnlockRange

            UnlockFile

            文件狀態(tài)操作函數(shù)

             

            GetPosition

            SetFilePointer

               

            GetStatus(CFileStatus&)

            GetFileTime,GetFileSize等

             

            GetStatus(LPSTR lpszFileName CFileStatus&)

            FindFirstFile

             

            GetFileName

            不是簡單地映射到某個(gè)函數(shù)

             

            GetFileTitle

             

             

            GetFilePath

             

             

            SetFilePath

             
             

            SetStatus

             

            改名和刪除

             

            Rename

            MoveFile

             

            Remove

            DeleteFile


            2.1.4、CFile的部分實(shí)現(xiàn)


            這里主要討論CFile對象的構(gòu)造函數(shù)和文件的打開/創(chuàng)建的過程。

            1. 構(gòu)造函數(shù)

            CFile有如下幾個(gè)構(gòu)造函數(shù):

            • CFile()

            缺省構(gòu)造函數(shù),僅僅構(gòu)造一個(gè)CFile對象,還必須使用Open成員函數(shù)來打開文件。

            • CFile(int hFile)

            已經(jīng)打開了一個(gè)文件hFile,在此基礎(chǔ)上構(gòu)造一個(gè)CFile對象來給它打包。HFile將被賦值給CFile的成員變量m_hFile。

            • CFile(LPCTSTR lpszFileName, UINT nOpenFlags)

            指定一個(gè)文件名和文件打開方式,構(gòu)造CFile對象,調(diào)用Open打開/創(chuàng)建文件,把文件句柄保存到m_hFile。

            1. 打開/創(chuàng)建文件

            Open的原型如下:

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

            CFileException* pException)

            Open調(diào)用Win32函數(shù)::CreateFile打開文件,并把文件句柄保存到成員變量m_hFile中。

            CreateFile函數(shù)的原型如下:

            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必須把自己的兩個(gè)參數(shù)lpszFileName和nOpenFlags映射到CreateFile的七個(gè)參數(shù)上。

            從OpenFlags的定義可以看出,(nOpenFlags & 3)表示了讀寫標(biāo)識,映射成變量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定義了一個(gè)局部的SECURITY_ATTRIBUTES變量sa,(nOpenFlags & 0x80)被賦值給sa.bInheritHandle。

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

            在生成了上述參數(shù)之后,先調(diào)用::CreateFile:

            HANDLE hFile =::CreateFile(lpszFileName,

            dwAccess, dwShareMode, &sa,

            dwCreateFlag, FILE_ATTRIBUTE_NORMAL, NULL);

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

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

            在CFile對象使用時(shí),如果它是在堆中分配的,則應(yīng)該銷毀它;如果在棧中分配的,則CFile對象將被自動(dòng)銷毀。銷毀時(shí)析構(gòu)函數(shù)被調(diào)用,析構(gòu)函數(shù)是虛擬函數(shù)。若m_bCloseOnDelete為真且m_hFile非空,則析構(gòu)函數(shù)調(diào)用Close關(guān)閉文件。

            至于其他CFile成員函數(shù)的實(shí)現(xiàn),這里不作分析了。

            2.1.5、CFile的派生類

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

            1. CStdioFile

              CStdioFile對文本文件進(jìn)行操作。

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

              另外,CStdioFile不支持CFile的Dumplicate、LockRange、UnlockRange操作,但是實(shí)現(xiàn)了兩個(gè)新的操作ReadString和WriteString。

            2. CMemFile

              CMemFile把一塊內(nèi)存當(dāng)作一個(gè)文件來操作,所以,它沒有打開文件的操作,而是設(shè)計(jì)了Attach和Detach用來分配或者釋放一塊內(nèi)存。相應(yīng)地,它提供了Alloc、Free虛擬函數(shù)來操作內(nèi)存文件,它覆蓋了Read、Write來讀寫內(nèi)存文件。

            3. CFileFind

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

            使用CFileStatus結(jié)構(gòu)來描述文件的屬性,其定義如下:

            struct CFileStatus

            {

            CTime m_ctime; // 文件創(chuàng)建時(shí)間

            CTime m_mtime; // 文件最近一次修改時(shí)間

            CTime m_atime; // 文件最近一次訪問時(shí)間

            LONG m_size; // 文件大小

            BYTE m_attribute; // 文件屬性

            BYTE _m_padding; // 沒有實(shí)際含義,用來增加一個(gè)字節(jié)

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

            #ifdef _DEBUG

            //實(shí)現(xiàn)Dump虛擬函數(shù),輸出文件屬性

            void Dump(CDumpContext& dc) const;

            #endif

            };

            例如:

            CFileStatus status;

            pFile->GetStatus(status);

            #ifdef _DEBUG

            status.dump(afxDump);

            #endif

             

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


            2006年4月17日

            10.1內(nèi)存分配?
            ??10.1.1?內(nèi)存分配函數(shù)

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

            1. Win32的堆分配函數(shù)

              每一個(gè)進(jìn)程都可以使用堆分配函數(shù)創(chuàng)建一個(gè)私有的堆──調(diào)用進(jìn)程地址空間的一個(gè)或者多個(gè)頁面。DLL創(chuàng)建的私有堆必定在調(diào)用DLL的進(jìn)程的地址空間內(nèi),只能被調(diào)用進(jìn)程訪問。

              HeapCreate用來創(chuàng)建堆;HeapAlloc用來從堆中分配一定數(shù)量的空間,HeapAlloc分配的內(nèi)存是不能移動(dòng)的;HeapSize可以確定從堆中分配的空間的大小;HeapFree用來釋放從堆中分配的空間;HeapDestroy銷毀創(chuàng)建的堆。

            2. Windows傳統(tǒng)的全局或者局部內(nèi)存分配函數(shù)

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

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

            3. C語言的標(biāo)準(zhǔn)內(nèi)存分配函數(shù)

              C語言的標(biāo)準(zhǔn)內(nèi)存分配函數(shù)包括以下函數(shù):

              malloc,calloc,realloc,free,等。

              這些函數(shù)最后都映射成堆API函數(shù),所以,malloc分配的內(nèi)存是不能移動(dòng)的。這些函數(shù)的調(diào)式版本為

              malloc_dbg,calloc_dbg,realloc_dbg,free_dbg,等。

            4. Win32的虛擬內(nèi)存分配函數(shù)

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

            • LPVOID VirtualAlloc(LPVOID lpvAddress,

            DWORD cbSize,

            DWORD fdwAllocationType,

            DWORD fdwProtect);

            該函數(shù)用來分配一定范圍的虛擬頁。參數(shù)1指定起始地址;參數(shù)2指定分配內(nèi)存的長度;參數(shù)3指定分配方式,取值MEM_COMMINT或者M(jìn)EM_RESERVE;參數(shù)4指定控制訪問本次分配的內(nèi)存的標(biāo)識,取值為PAGE_READONLY、PAGE_READWRITE或者PAGE_NOACCESS。

            • LPVOID VirtualAllocEx(HANDLE process,

            LPVOID lpvAddress,

            DWORD cbSize,

            DWORD fdwAllocationType,

            DWORD fdwProtect);

            該函數(shù)功能類似于VirtualAlloc,但是允許指定進(jìn)程process。VirtaulFree、VirtualProtect、VirtualQuery都有對應(yīng)的擴(kuò)展函數(shù)。

            • BOOL VirtualFree(LPVOID lpvAddress,

            DWORD dwSize,

            DWORD dwFreeType);

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

            • BOOL VirtualProtect(LPVOID lpvAddress,

            DWORD cbSize,

            DWORD fdwNewProtect,

            PDWORD pfdwOldProtect);

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

            • DWORD VirtualQuery(LPCVOID lpAddress,

            PMEMORY_BASIC_INFORMATION lpBuffer,

            DWORD dwLength

            );

            該函數(shù)用來查詢內(nèi)存中指定頁的特性。參數(shù)1指向希望查詢的虛擬地址;參數(shù)2是指向內(nèi)存基本信息結(jié)構(gòu)的指針;參數(shù)3指定查詢的長度。

            • BOOL VirtualLock(LPVOID lpAddress,DWORD dwSize);

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

            • BOOL VirtualUnLock(LPVOID lpAddress,DWORD dwSize);

            參數(shù)1指定要解鎖的內(nèi)存的起始地址;參數(shù)2指定要解鎖的內(nèi)存的長度。


            C++的new 和 delete操作符

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

            1. 全局范圍內(nèi)的new和delete操作符

              原型如下:

              void _cdecl ::operator new(size_t nSize);

              void __cdecl operator delete(void* p);

              調(diào)試版本:

              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操作符是類的靜態(tài)成員函數(shù),對該類的對象來說將覆蓋全局的operator new。全局的operator new用來給內(nèi)部類型對象(如int)、沒有定義operator new操作符的類的對象分配內(nèi)存。

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



            10.1.2調(diào)試手段

            MFC應(yīng)用程序可以使用C運(yùn)行庫的調(diào)試手段,也可以使用MFC提供的調(diào)試手段。兩種調(diào)試手段分別論述如下。

            1. C運(yùn)行庫提供和支持的調(diào)試功能

              C運(yùn)行庫提供和支持的調(diào)試功能如下:

              1. 調(diào)試信息報(bào)告函數(shù)

                用來報(bào)告應(yīng)用程序的調(diào)試版本運(yùn)行時(shí)的警告和出錯(cuò)信息。包括:

                _CrtDbgReport 用來報(bào)告調(diào)試信息;

                _CrtSetReportMode 設(shè)置是否警告、出錯(cuò)或者斷言信息;

                _CrtSetReportFile 設(shè)置是否把調(diào)試信息寫入到一個(gè)文件。

              2. 條件驗(yàn)證或者斷言宏:

                斷言宏主要有:

                assert 檢驗(yàn)?zāi)硞€(gè)條件是否滿足,不滿足終止程序執(zhí)行。

                驗(yàn)證函數(shù)主要有:

                _CrtIsValidHeapPointer 驗(yàn)證某個(gè)指針是否在本地堆中;

                _CrtIsValidPointer 驗(yàn)證指定范圍的內(nèi)存是否可以讀寫;

                _CrtIsMemoryBlock 驗(yàn)證某個(gè)內(nèi)存塊是否在本地堆中。

              3. 內(nèi)存(堆)調(diào)試:

              malloc_dbg 分配內(nèi)存時(shí)保存有關(guān)內(nèi)存分配的信息,如在什么文件、哪一行分配的內(nèi)存等。有一系列用來提供內(nèi)存診斷的函數(shù):

              _CrtMemCheckpoint 保存內(nèi)存快照在一個(gè)_CrtMemState結(jié)構(gòu)中;

              _CrtMemDifference 比較兩個(gè)_CrtMemState;

              _CrtMemDumpStatistics 轉(zhuǎn)儲輸出一_CrtMemState結(jié)構(gòu)的內(nèi)容;

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

              _CrtDumpMemoryLeaks 檢測程序執(zhí)行以來的內(nèi)存漏洞,如果有漏洞則輸出所有分配的對象。

              2.????? MFC提供的調(diào)試手段

              MFC在C運(yùn)行庫提供和支持的調(diào)試功能基礎(chǔ)上,設(shè)計(jì)了一些類、函數(shù)等來協(xié)助調(diào)試。

              1. MFC的TRACE、ASSERT

                ASSERT

                使用ASSERT斷言判定程序是否可以繼續(xù)執(zhí)行。

                TRACE

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

                void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...)

                {

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

                if (!afxTraceEnabled)

                return;

                #endif

                //處理個(gè)數(shù)不定的參數(shù)

                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指定了跟蹤什么消息,則輸出有關(guān)跟蹤信息,例如為了指定“Multilple Application Debug”,令A(yù)fxTraceFlags|=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

                };

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

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

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

                allMessages[1].nMsg = WM_CREATE,

                allMessages[1].lpszMsg = “WM_CREATE”

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

                allMessages和函數(shù)_AfxTraceMsg的詳細(xì)實(shí)現(xiàn)可以參見AfxTrace.cpp。

              2. MFC對象內(nèi)容轉(zhuǎn)儲

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

                虛擬函數(shù)Dump的定義:

                class ClassName : public CObject

                {

                public:

                #ifdef _DEBUG

                virtual void Dump( CDumpContext& dc ) const;

                #endif

                };

                在使用Dump時(shí),必須給它提供一個(gè)CDumpContext類型的參數(shù),該參數(shù)指定的對象將負(fù)責(zé)輸出調(diào)試信息。為此,MFC提供了一個(gè)預(yù)定義的全局CDumpContext對象afxDump,它把調(diào)試信息輸送給調(diào)試器的調(diào)試窗口。從前面AfxTrace的實(shí)現(xiàn)可以知道,MFC使用了afxDump輸出跟蹤信息到調(diào)試窗口。

                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類提供的功能,所有從它派生的類都可以通過覆蓋虛擬函數(shù)AssertValid來支持該功能。在講述CObject類時(shí)曾提到過。

              虛擬函數(shù)AssertValid的定義:

              class ClassName : public CObject

              {

              public:

              #ifdef _DEBUG

              virtual void AssertValid( ) const;

              #endif

              };

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

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

              AfxIsMemoryBlock,AfxIsString,AfxIsValidAddress。

              10.1.3內(nèi)存診斷

              MFC使用DEBUG_NEW來跟蹤內(nèi)存分配時(shí)的執(zhí)行的源碼文件和行數(shù)。

              把#define new DEBUG_NEW插入到每一個(gè)源文件中,這樣,調(diào)試版本就使用_malloc_dbg來分配內(nèi)存。MFC Appwizard在創(chuàng)建框架文件時(shí)已經(jīng)作了這樣的處理。

              1. AfxDoForAllObjects

                MFC提供了函數(shù)AfxDoForAllObjects來追蹤動(dòng)態(tài)分配的內(nèi)存對象,函數(shù)原型如下:

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

                void* pContext), void* pContext );

                其中:

                參數(shù)1是一個(gè)函數(shù)指針,AfxDoForAllObjects對每個(gè)對象調(diào)用該指針表示的函數(shù)。

                參數(shù)2將傳遞給參數(shù)1指定的函數(shù)。

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

              10.1.4內(nèi)存漏洞檢測

              僅僅用于new的DEBUG版本分配的內(nèi)存。

              完成內(nèi)存漏洞檢測,需要如下系列步驟:

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

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

              afxMemDF,delayFreeMemDF,checkAlwaysMemDF

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

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

              • 創(chuàng)建一個(gè)CMemState類型的變量oldMemState,調(diào)用CMemState的成員函數(shù)CheckPoint獲得初次內(nèi)存快照。

              • 執(zhí)行了系列內(nèi)存分配或者釋放之后,創(chuàng)建另一個(gè)CMemState類型變量newMemState,調(diào)用CMemState的成員函數(shù)CheckPoint獲得新的內(nèi)存快照。

              • 創(chuàng)建第三個(gè)CMemState類型變量difMemState,調(diào)用CMemState的成員函數(shù)Difference比較oldMemState和newMemState,結(jié)果保存在變量difMemState中。如果沒有不同,則返回FALSE,否則返回TRUE。

              • 如果不同,則調(diào)用成員函數(shù)DumpStatistics輸出比較結(jié)果。

              例如:

              // 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在應(yīng)用程序(調(diào)試版)結(jié)束時(shí),自動(dòng)進(jìn)行內(nèi)存漏洞檢測,如果存在漏洞,則輸出漏洞的有關(guān)信息。

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


            僅列出標(biāo)題  

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

            Copyright © 逃逃

            无码人妻久久一区二区三区蜜桃 | av色综合久久天堂av色综合在| 久久精品亚洲乱码伦伦中文| 久久久婷婷五月亚洲97号色| 一本久久久久久久| 尹人香蕉久久99天天拍| 18岁日韩内射颜射午夜久久成人| 亚洲第一极品精品无码久久| 国产毛片久久久久久国产毛片| 久久久久久久久久久久久久| 久久久久久久尹人综合网亚洲| 久久综合亚洲鲁鲁五月天| 国产午夜福利精品久久| 久久国产免费观看精品3| 少妇熟女久久综合网色欲| 精品国产青草久久久久福利| 久久精品人人做人人爽97| 久久青青草视频| 久久亚洲av无码精品浪潮| 久久美女网站免费| 精品久久久噜噜噜久久久| 国产毛片欧美毛片久久久| 色综合久久久久综合99| 久久精品夜色噜噜亚洲A∨| 欧美777精品久久久久网| www性久久久com| av无码久久久久不卡免费网站| 久久婷婷色综合一区二区| 亚洲国产精品无码久久九九| 久久精品国产一区二区三区| 精品久久久久久99人妻| 91精品国产综合久久四虎久久无码一级| 国产精品99久久久精品无码| 东方aⅴ免费观看久久av | 久久精品国产影库免费看| 国产午夜精品久久久久免费视 | 国产精品99久久久久久董美香| 精品久久香蕉国产线看观看亚洲| 97久久国产亚洲精品超碰热| AV无码久久久久不卡蜜桃| 国内精品久久久久久久久电影网|