• <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>
            xiaoguozi's Blog
            Pay it forword - 我并不覺(jué)的自豪,我所嘗試的事情都失敗了······習(xí)慣原本生活的人不容易改變,就算現(xiàn)狀很糟,他們也很難改變,在過(guò)程中,他們還是放棄了······他們一放棄,大家就都是輸家······讓愛(ài)傳出去,很困難,也無(wú)法預(yù)料,人們需要更細(xì)心的觀察別人,要隨時(shí)注意才能保護(hù)別人,因?yàn)樗麄兾幢刂雷约阂裁础ぁぁぁぁ?/span>
            摘要

               Windows NT 3.1引入了一種名為PE文件格式的新可執(zhí)行文件格式。PE文件格式的規(guī)范包含在了MSDN的CD中(Specs and Strategy, Specifications, Windows NT File Format Specifications),但是它非常之晦澀。
               然而這一的文檔并未提供足夠的信息,所以開(kāi)發(fā)者們無(wú)法很好地弄懂PE格式。本文旨在解決這一問(wèn)題,它會(huì)對(duì)整個(gè)的PE文件格式作一個(gè)十分徹底的解釋?zhuān)硗?,本文中還帶有對(duì)所有必需結(jié)構(gòu)的描述以及示范如何使用這些信息的源碼示例。
            為了獲得PE文件中所包含的重要信息,我編寫(xiě)了一個(gè)名為PEFILE.DLL的動(dòng)態(tài)鏈接庫(kù),本文中所有出現(xiàn)的源碼示例亦均摘自于此。這個(gè)DLL和它的源代 碼都作為PEFile示例程序的一部分包含在了CD中(譯注:示例程序請(qǐng)?jiān)贛SDN中尋找,本站恕不提供),你可以在你自己的應(yīng)用程序中使用這個(gè)DLL; 同樣,你亦可以依你所愿地使用并構(gòu)建它的源碼。在本文末尾,你會(huì)找到PEFILE.DLL的函數(shù)導(dǎo)出列表和一個(gè)如何使用它們的說(shuō)明。我覺(jué)得你會(huì)發(fā)現(xiàn)這些函 數(shù)會(huì)讓你從容應(yīng)付PE文件格式的。

            介紹

            Windows操作系統(tǒng)家族最近增加的Windows NT為開(kāi)發(fā)環(huán)境和應(yīng)用程序本身帶來(lái)了很大的改變,這之中一個(gè)最為重大的當(dāng)屬PE文件格式了。新的PE文件格式主要來(lái)自于UNIX操作系統(tǒng)所通用的COFF 規(guī)范,同時(shí)為了保證與舊版本MS-DOS及Windows操作系統(tǒng)的兼容,PE文件格式也保留了MS-DOS中那熟悉的MZ頭部。
               在本文之中,PE文件格式是以自頂而下的順序解釋的。在你從頭開(kāi)始研究文件內(nèi)容的過(guò)程之中,本文會(huì)詳細(xì)討論P(yáng)E文件的每一個(gè)組成部分。
            許多單獨(dú)的文件成分定義都來(lái)自于Microsoft Win32 SDK開(kāi)發(fā)包中的WINNT.H文件,在這個(gè)文件中你會(huì)發(fā)現(xiàn)用來(lái)描述文件頭部和數(shù)據(jù)目錄等各種成分的結(jié)構(gòu)類(lèi)型定義。但是,在WINNT.H中缺少對(duì)PE文 件結(jié)構(gòu)足夠的定義,在這種情況下,我定義了自己的結(jié)構(gòu)來(lái)存取文件數(shù)據(jù)。你會(huì)在PEFILE.DLL工程的PEFILE.H中找到這些結(jié)構(gòu)的定義,整套的 PEFILE.H開(kāi)發(fā)文件包含在PEFile示例程序之中。
            本文配套的示例程序除了PEFILE.DLL示例代碼之外,還有一個(gè)單獨(dú)的Win32示例應(yīng)用程序,名為EXEVIEW.EXE。創(chuàng)建這一示例目的有二: 首先,我需要測(cè)試PEFILE.DLL的函數(shù),并且某些情況要求我同時(shí)查看多個(gè)文件;其次,很多解決PE文件格式的工作和直接觀看數(shù)據(jù)有關(guān)。例如,要弄懂 導(dǎo)入地址名稱(chēng)表是如何構(gòu)成的,我就得同時(shí)查看.idata段頭部、導(dǎo)入映像數(shù)據(jù)目錄、可選頭部以及當(dāng)前的.idata段實(shí)體,而EXEVIEW.EXE就 是查看這些信息的最佳示例。
               閑話少敘,讓我們開(kāi)始吧。

            PE文件結(jié)構(gòu)

            PE文件格式被組織為一個(gè)線性的數(shù)據(jù)流,它由一個(gè)MS-DOS頭部開(kāi)始,接著是一個(gè)是模式的程序殘余以及一個(gè)PE文件標(biāo)志,這之后緊接著PE文件頭和可選 頭部。這些之后是所有的段頭部,段頭部之后跟隨著所有的段實(shí)體。文件的結(jié)束處是一些其它的區(qū)域,其中是一些混雜的信息,包括重分配信息、符號(hào)表信息、行號(hào) 信息以及字串表數(shù)據(jù)。我將所有這些成分列于圖1。

            圖1.PE文件映像結(jié)構(gòu)
            從MS-DOS文件頭結(jié)構(gòu)開(kāi)始,我將按照PE文件格式各成分的出現(xiàn)順序依次對(duì)其進(jìn)行討論,并且討論的大部分是以示例代碼為基礎(chǔ)來(lái)示范如何獲得文件的信息 的。所有的源碼均摘自PEFILE.DLL模塊的PEFILE.C文件。這些示例都利用了Windows NT最酷的特色之一——內(nèi)存映射文件,這一特色允許用戶使用一個(gè)簡(jiǎn)單的指針來(lái)存取文件中所包含的數(shù)據(jù),因此所有的示例都使用了內(nèi)存映射文件來(lái)存取PE文件 中的數(shù)據(jù)。
               注意:請(qǐng)查閱本文末尾關(guān)于如何使用PEFILE.DLL的那一段。

            MS-DOS頭部/實(shí)模式頭部

            如上所述,PE文件格式的第一個(gè)組成部分是MS-DOS頭部。在PE文件格式中,它并非一個(gè)新概念,因?yàn)樗cMS-DOS 2.0以來(lái)就已有的MS-DOS頭部是完全一樣的。保留這個(gè)相同結(jié)構(gòu)的最主要原因是,當(dāng)你嘗試在Windows 3.1以下或MS-DOS 2.0以上的系統(tǒng)下裝載一個(gè)文件的時(shí)候,操作系統(tǒng)能夠讀取這個(gè)文件并明白它是和當(dāng)前系統(tǒng)不相兼容的。換句話說(shuō),當(dāng)你在MS-DOS 6.0下運(yùn)行一個(gè)Windows NT可執(zhí)行文件時(shí),你會(huì)得到這樣一條消息:“This program cannot be run in DOS mode.”如果MS-DOS頭部不是作為PE文件格式的第一部分的話,操作系統(tǒng)裝載文件的時(shí)候就會(huì)失敗,并提供一些完全沒(méi)用的信息,例如:“The name specified is not recognized as an internal or external command, operable program or batch file.”
               MS-DOS頭部占據(jù)了PE文件的頭64個(gè)字節(jié),描述它內(nèi)容的結(jié)構(gòu)如下:
            //WINNT.H
            typedef struct _IMAGE_DOS_HEADER { // DOS的.EXE頭部
            USHORT e_magic; // 魔術(shù)數(shù)字
            USHORT e_cblp; // 文件最后頁(yè)的字節(jié)數(shù)
            USHORT e_cp; // 文件頁(yè)數(shù)
            USHORT e_crlc; // 重定義元素個(gè)數(shù)
            USHORT e_cparhdr; // 頭部尺寸,以段落為單位
            USHORT e_minalloc; // 所需的最小附加段
            USHORT e_maxalloc; // 所需的最大附加段
            USHORT e_ss; // 初始的SS值(相對(duì)偏移量)
            USHORT e_sp; // 初始的SP值
            USHORT e_csum; // 校驗(yàn)和
            USHORT e_ip; // 初始的IP值
            USHORT e_cs; // 初始的CS值(相對(duì)偏移量)
            USHORT e_lfarlc; // 重分配表文件地址
            USHORT e_ovno; // 覆蓋號(hào)
            USHORT e_res[4]; // 保留字
            USHORT e_oemid; // OEM標(biāo)識(shí)符(相對(duì)e_oeminfo)
            USHORT e_oeminfo; // OEM信息
            USHORT e_res2[10]; // 保留字
            LONG e_lfanew; // 新exe頭部的文件地址
            } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
            第一個(gè)域e_magic,被稱(chēng)為魔術(shù)數(shù)字,它被用于表示一 個(gè)MS-DOS兼容的文件類(lèi)型。所有MS-DOS兼容的可執(zhí)行文件都將這個(gè)值設(shè)為0x5A4D,表示ASCII字符MZ。MS-DOS頭部之所以有的時(shí)候 被稱(chēng)為MZ頭部,就是這個(gè)緣故。還有許多其它的域?qū)τ贛S-DOS操作系統(tǒng)來(lái)說(shuō)都有用,但是對(duì)于Windows NT來(lái)說(shuō),這個(gè)結(jié)構(gòu)中只有一個(gè)有用的域——最后一個(gè)域e_lfnew,一個(gè)4字節(jié)的文件偏移量,PE文件頭部就是由它定位的。對(duì)于Windows NT的PE文件來(lái)說(shuō),PE文件頭部是緊跟在MS-DOS頭部和實(shí)模式程序殘余之后的。

            實(shí)模式殘余程序

            實(shí)模式殘余程序是一個(gè)在裝載時(shí)能夠被MS-DOS運(yùn)行的實(shí)際程序。對(duì)于一個(gè)MS-DOS的可執(zhí)行映像文件,應(yīng)用程序就是從這里執(zhí)行的。對(duì)于 Windows、OS/2、Windows NT這些操作系統(tǒng)來(lái)說(shuō),MS-DOS殘余程序就代替了主程序的位置被放在這里。這種殘余程序通常什么也不做,而只是輸出一行文本,例如:“This program requires Microsoft Windows v3.1 or greater.”當(dāng)然,用戶可以在此放入任何的殘余程序,這就意味著你可能經(jīng)常看到像這樣的東西:“You can''t run a Windows NT application on OS/2, it''s simply not possible.”
               當(dāng)為Windows 3.1構(gòu)建一個(gè)應(yīng)用程序的時(shí)候,鏈接器將向你的可執(zhí)行文件中鏈接一個(gè)名為WINSTUB.EXE的默認(rèn)殘余程序。你可以用一個(gè)基于MS-DOS的有效程序 取代WINSTUB,并且用STUB模塊定義語(yǔ)句指示鏈接器,這樣就能夠取代鏈接器的默認(rèn)行為。為Windows NT開(kāi)發(fā)的應(yīng)用程序可以通過(guò)使用-STUB:鏈接器選項(xiàng)來(lái)實(shí)現(xiàn)。

            PE文件頭部與標(biāo)志

               PE文件頭部是由MS-DOS頭部的e_lfanew域定位的,這個(gè)域只是給出了文件的偏移量,所以要確定PE頭部的實(shí)際內(nèi)存映射地址,就需要添加文件的內(nèi)存映射基地址。例如,以下的宏是包含在PEFILE.H源文件之中的:
            //PEFILE.H
            #define NTSIGNATURE(a) ((LPVOID)((BYTE *)a + \
            ((PIMAGE_DOS_HEADER)a)->e_lfanew))
            在處理PE文件信息的時(shí)候,我發(fā)現(xiàn)文件之中有些位置需要經(jīng)常查閱。既然這些位置僅僅是對(duì)文件的偏移量,那么用宏來(lái)實(shí)現(xiàn)這些定位就比較容易,因?yàn)樗鼈冚^之函數(shù)有更好的表現(xiàn)。
            請(qǐng)注意這個(gè)宏所獲得的是PE文件標(biāo)志,而并非PE文件頭部的偏移量。那是由于自Windows與OS/2的可執(zhí)行文件開(kāi)始,.EXE文件都被賦予了目標(biāo)操 作系統(tǒng)的標(biāo)志。對(duì)于Windows NT的PE文件格式而言,這一標(biāo)志在PE文件頭部結(jié)構(gòu)之前。在Windows和OS/2的某些版本中,這一標(biāo)志是文件頭的第一個(gè)字。同樣,對(duì)于PE文件格 式,Windows NT使用了一個(gè)DWORD值。
            以上的宏返回了文件標(biāo)志的偏移量,而不管它是哪種類(lèi)型的可執(zhí)行文件。所以,文件頭部是在DWORD標(biāo)志之后,還是在WORD標(biāo)志處,是由這個(gè)標(biāo)志是否 Windows NT文件標(biāo)志所決定的。要解決這個(gè)問(wèn)題,我編寫(xiě)了ImageFileType函數(shù)(如下),它返回了映像文件的類(lèi)型:
            //PEFILE.C
            DWORD WINAPI ImageFileType (LPVOID lpFile)
            {
            /* 首先出現(xiàn)的是DOS文件標(biāo)志 */
            if (*(USHORT *)lpFile == IMAGE_DOS_SIGNATURE)
            {
            /* 由DOS頭部決定PE文件頭部的位置 */
            if (LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
            IMAGE_OS2_SIGNATURE ||
            LOWORD (*(DWORD *)NTSIGNATURE (lpFile)) ==
            IMAGE_OS2_SIGNATURE_LE)
            return (DWORD)LOWORD(*(DWORD *)NTSIGNATURE (lpFile));
            else if (*(DWORD *)NTSIGNATURE (lpFile) ==
            IMAGE_NT_SIGNATURE)
            return IMAGE_NT_SIGNATURE;
            else
            return IMAGE_DOS_SIGNATURE;
            }
            else
            /* 不明文件種類(lèi) */
            return 0;
            }
            
            以上列出的代碼立即告訴了你NTSIGNATURE宏有多么有用。對(duì)于比較不同文件類(lèi)型并且返回一個(gè)適當(dāng)?shù)奈募N類(lèi)來(lái)說(shuō),這個(gè)宏就會(huì)使這兩件事變得非常簡(jiǎn)單。WINNT.H之中定義的四種不同文件類(lèi)型有:
            //WINNT.H
            #define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
            #define IMAGE_OS2_SIGNATURE 0x454E // NE
            #define IMAGE_OS2_SIGNATURE_LE 0x454C // LE
            #define IMAGE_NT_SIGNATURE 0x00004550 // PE00
            
            首先,Windows的可執(zhí)行文件類(lèi)型沒(méi)有出現(xiàn)在這一列表中,這一點(diǎn)看起來(lái)很奇怪。但是,在稍微研究一下之后,就能得到原因了:除了操作 系統(tǒng)版本規(guī)范的不同之外,Windows的可執(zhí)行文件和OS/2的可執(zhí)行文件實(shí)在沒(méi)有什么區(qū)別。這兩個(gè)操作系統(tǒng)擁有相同的可執(zhí)行文件結(jié)構(gòu)。
               現(xiàn)在把我們的注意力轉(zhuǎn)向Windows NT PE文件格式,我們會(huì)發(fā)現(xiàn)只要我們得到了文件標(biāo)志的位置,PE文件之后就會(huì)有4個(gè)字節(jié)相跟隨。下一個(gè)宏標(biāo)識(shí)了PE文件的頭部:
            //PEFILE.C
            #define PEFHDROFFSET(a) ((LPVOID)((BYTE *)a + \
            ((PIMAGE_DOS_HEADER)a)->e_lfanew + \
            SIZE_OF_NT_SIGNATURE))
            
            這個(gè)宏與上一個(gè)宏的唯一不同是這個(gè)宏加入了一個(gè)常量SIZE_OF_NT_SIGNATURE。不幸的是,這個(gè)常量并未定義在WINNT.H之中,于是我將它定義在了PEFILE.H中,它是一個(gè)DWORD的大小。
               既然我們知道了PE文件頭的位置,那么就可以檢查頭部的數(shù)據(jù)了。我們只需要把這個(gè)位置賦值給一個(gè)結(jié)構(gòu),如下:
            PIMAGE_FILE_HEADER pfh;
            pfh = (PIMAGE_FILE_HEADER)PEFHDROFFSET(lpFile);
            在這個(gè)例子中,lpFile表示一個(gè)指向可執(zhí)行文件內(nèi)存映像基地址的指針,這就顯出了內(nèi)存映射文件的好處:不需要執(zhí)行文件的I/O,只需使用指針pfh就能存取文件中的信息。PE文件頭結(jié)構(gòu)被定義為:
            //WINNT.H
            typedef struct _IMAGE_FILE_HEADER {
            USHORT Machine;
            USHORT NumberOfSections;
            ULONG TimeDateStamp;
            ULONG PointerToSymbolTable;
            ULONG NumberOfSymbols;
            USHORT SizeOfOptionalHeader;
            USHORT Characteristics;
            } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
            #define IMAGE_SIZEOF_FILE_HEADER 20
            
            請(qǐng)注意這個(gè)文件頭部的大小已經(jīng)定義在這個(gè)包含文件之中了,這樣一來(lái),想要得到這個(gè)結(jié)構(gòu)的大小就很方便了。但是我覺(jué)得對(duì)結(jié)構(gòu)本身使用 sizeof運(yùn)算符(譯注:原文為“function”)更簡(jiǎn)單一些,因?yàn)檫@樣的話我就不必記住這個(gè)常量的名字 IMAGE_SIZEOF_FILE_HEADER,而只需要記住結(jié)構(gòu)IMAGE_FILE_HEADER的名字就可以了。另一方面,記住所有結(jié)構(gòu)的名字 已經(jīng)夠有挑戰(zhàn)性的了,尤其在是這些結(jié)構(gòu)只有WINNT.H中才有的情況下。
            PE文件中的信息基本上是一些高級(jí)信息,這些信息是被操作系統(tǒng)或者應(yīng)用程序用來(lái)決定如何處理這個(gè)文件的。第一個(gè)域是用來(lái)表示這個(gè)可執(zhí)行文件被構(gòu)建的目標(biāo)機(jī) 器種類(lèi),例如DEC(R) Alpha、MIPS R4000、Intel(R) x86或一些其它處理器。系統(tǒng)使用這一信息來(lái)在讀取這個(gè)文件的其它數(shù)據(jù)之前決定如何處理它。
            Characteristics域表示了文件的一些特征。比如對(duì)于一個(gè)可執(zhí)行文件而言,分離調(diào)試文件是如何操作的。調(diào)試器通常使用的方法是將調(diào)試信息從 PE文件中分離,并保存到一個(gè)調(diào)試文件(.DBG)中。要這么做的話,調(diào)試器需要了解是否要在一個(gè)單獨(dú)的文件中尋找調(diào)試信息,以及這個(gè)文件是否已經(jīng)將調(diào)試 信息分離了。我們可以通過(guò)深入可執(zhí)行文件并尋找調(diào)試信息的方法來(lái)完成這一工作。要使調(diào)試器不在文件中查找的話,就需要用到 IMAGE_FILE_DEBUG_STRIPPED這個(gè)特征,它表示文件的調(diào)試信息是否已經(jīng)被分離了。這樣一來(lái),調(diào)試器可以通過(guò)快速查看PE文件的頭部 的方法來(lái)決定文件中是否存在著調(diào)試信息。
               WINNT.H定義了若干其它表示文件頭信息的標(biāo)記,就和以上的例子差不多。我把研究這些標(biāo)記的事情留給讀者作為練習(xí),由你們來(lái)看看它們是不是很有趣,這些標(biāo)記位于WINNT.H中的IMAGE_FILE_HEADER結(jié)構(gòu)之后。
            PE文件頭結(jié)構(gòu)中另一個(gè)有用的入口是NumberOfSections域,它表示如果你要方便地提取文件信息的話,就需要了解多少個(gè)段——更明確一點(diǎn)來(lái) 說(shuō),有多少個(gè)段頭部和多少個(gè)段實(shí)體。每一個(gè)段頭部和段實(shí)體都在文件中連續(xù)地排列著,所以要決定段頭部和段實(shí)體在哪里結(jié)束的話,段的數(shù)目是必需的。以下的函 數(shù)從PE文件頭中提取了段的數(shù)目:
            PEFILE.C
            int WINAPI NumOfSections(LPVOID lpFile)
            {
            /* 文件頭部中所表示出的段數(shù)目 */
            return (int)((PIMAGE_FILE_HEADER)
            PEFHDROFFSET (lpFile))->NumberOfSections);
            }
            
            如你所見(jiàn),PEFHDROFFSET以及其它宏用起來(lái)非常方便。

            PE可選頭部

               PE可執(zhí)行文件中接下來(lái)的224個(gè)字節(jié)組成了PE可選頭部。雖然它的名字是“可選頭部”,但是請(qǐng)確信:這個(gè)頭部并非“可選”,而是“必需”的。OPTHDROFFSET宏可以獲得指向可選頭部的指針:
            //PEFILE.H
            #define OPTHDROFFSET(a) ((LPVOID)((BYTE *)a + \
            ((PIMAGE_DOS_HEADER)a)->e_lfanew + \
            SIZE_OF_NT_SIGNATURE + \
            sizeof(IMAGE_FILE_HEADER)))
            
            可選頭部包含了很多關(guān)于可執(zhí)行映像的重要信息,例如初始的堆棧大小、程序入口點(diǎn)的位置、首選基地址、操作系統(tǒng)版本、段對(duì)齊的信息等等。IMAGE_OPTIONAL_HEADER結(jié)構(gòu)如下:
            //WINNT.H
            typedef struct _IMAGE_OPTIONAL_HEADER {
            //
            // 標(biāo)準(zhǔn)域
            //
            USHORT Magic;
            UCHAR MajorLinkerVersion;
            UCHAR MinorLinkerVersion;
            ULONG SizeOfCode;
            ULONG SizeOfInitializedData;
            ULONG SizeOfUninitializedData;
            ULONG AddressOfEntryPoint;
            ULONG BaseOfCode;
            ULONG BaseOfData;
            //
            // NT附加域
            //
            ULONG ImageBase;
            ULONG SectionAlignment;
            ULONG FileAlignment;
            USHORT MajorOperatingSystemVersion;
            USHORT MinorOperatingSystemVersion;
            USHORT MajorImageVersion;
            USHORT MinorImageVersion;
            USHORT MajorSubsystemVersion;
            USHORT MinorSubsystemVersion;
            ULONG Reserved1;
            ULONG SizeOfImage;
            ULONG SizeOfHeaders;
            ULONG CheckSum;
            USHORT Subsystem;
            USHORT DllCharacteristics;
            ULONG SizeOfStackReserve;
            ULONG SizeOfStackCommit;
            ULONG SizeOfHeapReserve;
            ULONG SizeOfHeapCommit;
            ULONG LoaderFlags;
            ULONG NumberOfRvaAndSizes;
            IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
            } IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER;
            
            如你所見(jiàn),這個(gè)結(jié)構(gòu)中所列出的域?qū)嵲谑侨唛L(zhǎng)得過(guò)分。為了不讓你對(duì)所有這些域感到厭煩,我會(huì)僅僅討論有用的——就是說(shuō),對(duì)于探究PE文件格式而言有用的。

            標(biāo)準(zhǔn)域

               首先,請(qǐng)注意這個(gè)結(jié)構(gòu)被劃分為“標(biāo)準(zhǔn)域”和“NT附加域”。所謂標(biāo)準(zhǔn)域,就是和UNIX可執(zhí)行文件的COFF格式所公共的部分。雖然標(biāo)準(zhǔn)域保留了COFF中定義的名字,但是Windows NT仍然將它們用作了不同的目的——盡管換個(gè)名字更好一些。
               ·Magic。我不知道這個(gè)域是干什么的,對(duì)于示例程序EXEVIEW.EXE示例程序而言,這個(gè)值是0x010B或267(譯注:0x010B為.EXE,0x0107為ROM映像,這個(gè)信息我是從eXeScope上得來(lái)的)。
               ·MajorLinkerVersion、MinorLinkerVersion。表示鏈接此映像的鏈接器版本。隨Window NT build 438配套的Windows NT SDK包含的鏈接器版本是2.39(十六進(jìn)制為2.27)。
               ·SizeOfCode??蓤?zhí)行代碼尺寸。
               ·SizeOfInitializedData。已初始化的數(shù)據(jù)尺寸。
               ·SizeOfUninitializedData。未初始化的數(shù)據(jù)尺寸。
            ·AddressOfEntryPoint。在標(biāo)準(zhǔn)域中,AddressOfEntryPoint域是對(duì)PE文件格式來(lái)說(shuō)最為有趣的了。這個(gè)域表示應(yīng)用程 序入口點(diǎn)的位置。并且,對(duì)于系統(tǒng)黑客來(lái)說(shuō),這個(gè)位置就是導(dǎo)入地址表(IAT)的末尾。以下的函數(shù)示范了如何從可選頭部獲得Windows NT可執(zhí)行映像的入口點(diǎn)。
            //PEFILE.C
            LPVOID WINAPI GetModuleEntryPoint(LPVOID lpFile)
            {
            PIMAGE_OPTIONAL_HEADER poh;
            poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
            if (poh != NULL)
            return (LPVOID)poh->AddressOfEntryPoint;
            else
            return NULL;
            }
            
            ·BaseOfCode。已載入映像的代碼(“.text”段)的相對(duì)偏移量。
               ·BaseOfData。已載入映像的未初始化數(shù)據(jù)(“.bss”段)的相對(duì)偏移量。

            Windows NT附加域

               添加到Windows NT PE文件格式中的附加域?yàn)閃indows NT特定的進(jìn)程行為提供了裝載器的支持,以下為這些域的概述。
               ·ImageBase。進(jìn)程映像地址空間中的首選基地址。Windows NT的Microsoft Win32 SDK鏈接器將這個(gè)值默認(rèn)設(shè)為0x00400000,但是你可以使用-BASE:linker開(kāi)關(guān)改變這個(gè)值。
               ·SectionAlignment。從ImageBase開(kāi)始,每個(gè)段都被相繼的裝入進(jìn)程的地址空間中。SectionAlignment則規(guī)定了裝載時(shí)段能夠占據(jù)的最小空間數(shù)量——就是說(shuō),段是關(guān)于SectionAlignment對(duì)齊的。
               Windows NT虛擬內(nèi)存管理器規(guī)定,段對(duì)齊不能少于頁(yè)尺寸(當(dāng)前的x86平臺(tái)是4096字節(jié)),并且必須是成倍的頁(yè)尺寸。4096字節(jié)是x86鏈接器的默認(rèn)值,但是它可以通過(guò)-ALIGN: linker開(kāi)關(guān)來(lái)設(shè)置。
            ·FileAlignment。映像文件首先裝載的最小的信息塊間隔。例如,鏈接器將一個(gè)段實(shí)體(段的原始數(shù)據(jù))加零擴(kuò)展為文件中最接近的 FileAlignment邊界。早先提及的2.39版鏈接器將映像文件以0x200字節(jié)的邊界對(duì)齊,這個(gè)值可以被強(qiáng)制改為512到65535這么多。
               ·MajorOperatingSystemVersion。表示W(wǎng)indows NT操作系統(tǒng)的主版本號(hào);通常對(duì)Windows NT 1.0而言,這個(gè)值被設(shè)為1。
               ·MinorOperatingSystemVersion。表示W(wǎng)indows NT操作系統(tǒng)的次版本號(hào);通常對(duì)Windows NT 1.0而言,這個(gè)值被設(shè)為0。
               ·MajorImageVersion。用來(lái)表示應(yīng)用程序的主版本號(hào);對(duì)于Microsoft Excel 4.0而言,這個(gè)值是4。
               ·MinorImageVersion。用來(lái)表示應(yīng)用程序的次版本號(hào);對(duì)于Microsoft Excel 4.0而言,這個(gè)值是0。
               ·MajorSubsystemVersion。表示W(wǎng)indows NT Win32子系統(tǒng)的主版本號(hào);通常對(duì)于Windows NT 3.10而言,這個(gè)值被設(shè)為3。
               ·MinorSubsystemVersion。表示W(wǎng)indows NT Win32子系統(tǒng)的次版本號(hào);通常對(duì)于Windows NT 3.10而言,這個(gè)值被設(shè)為10。
               ·Reserved1。未知目的,通常不被系統(tǒng)使用,并被鏈接器設(shè)為0。
            ·SizeOfImage。表示載入的可執(zhí)行映像的地址空間中要保留的地址空間大小,這個(gè)數(shù)字很大程度上受SectionAlignment的影響。例 如,考慮一個(gè)擁有固定頁(yè)尺寸4096字節(jié)的系統(tǒng),如果你有一個(gè)11個(gè)段的可執(zhí)行文件,它的每個(gè)段都少于4096字節(jié),并且關(guān)于65536字節(jié)邊界對(duì)齊,那 么SizeOfImage域?qū)?huì)被設(shè)為11 * 65536 = 720896(176頁(yè))。而如果一個(gè)相同的文件關(guān)于4096字節(jié)對(duì)齊的話,那么SizeOfImage域的結(jié)果將是11 * 4096 = 45056(11頁(yè))。這只是個(gè)簡(jiǎn)單的例子,它說(shuō)明每個(gè)段需要少于一個(gè)頁(yè)面的內(nèi)存。在現(xiàn)實(shí)中,鏈接器通過(guò)個(gè)別地計(jì)算每個(gè)段的方法來(lái)決定 SizeOfImage確切的值。它首先決定每個(gè)段需要多少字節(jié),并且最后將頁(yè)面總數(shù)向上取整至最接近的SectionAlignment邊界,然后總數(shù) 就是每個(gè)段個(gè)別需求之和了。
               ·SizeOfHeaders。這個(gè)域表示文件中有多少空間用來(lái)保存所有的文件頭部,包括MS-DOS頭部、PE文件頭部、PE可選頭部以及PE段頭部。文件中所有的段實(shí)體就開(kāi)始于這個(gè)位置。
               ·CheckSum。校驗(yàn)和是用來(lái)在裝載時(shí)驗(yàn)證可執(zhí)行文件的,它是由鏈接器設(shè)置并檢驗(yàn)的。由于創(chuàng)建這些校驗(yàn)和的算法是私有信息,所以在此不進(jìn)行討論。
               ·Subsystem。用于標(biāo)識(shí)該可執(zhí)行文件目標(biāo)子系統(tǒng)的域。每個(gè)可能的子系統(tǒng)取值列于WINNT.H的IMAGE_OPTIONAL_HEADER結(jié)構(gòu)之后。
               ·DllCharacteristics。用來(lái)表示一個(gè)DLL映像是否為進(jìn)程和線程的初始化及終止包含入口點(diǎn)的標(biāo)記。
            ·SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、 SizeOfHeapCommit。這些域控制要保留的地址空間數(shù)量,并且負(fù)責(zé)棧和默認(rèn)堆的申請(qǐng)。在默認(rèn)情況下,棧和堆都擁有1個(gè)頁(yè)面的申請(qǐng)值以及16個(gè) 頁(yè)面的保留值。這些值可以使用鏈接器開(kāi)關(guān)-STACKSIZE:與-HEAPSIZE:來(lái)設(shè)置。
               ·LoaderFlags。告知裝載器是否在裝載時(shí)中止和調(diào)試,或者默認(rèn)地正常運(yùn)行。
               ·NumberOfRvaAndSizes。這個(gè)域標(biāo)識(shí)了接下來(lái)的DataDirectory數(shù)組。請(qǐng)注意它被用來(lái)標(biāo)識(shí)這個(gè)數(shù)組,而不是數(shù)組中的各個(gè)入口數(shù)字,這一點(diǎn)非常重要。
               ·DataDirectory。數(shù)據(jù)目錄表示文件中其它可執(zhí)行信息重要組成部分的位置。它事實(shí)上就是一個(gè)IMAGE_DATA_DIRECTORY結(jié)構(gòu)的數(shù)組,位于可選頭部結(jié)構(gòu)的末尾。當(dāng)前的PE文件格式定義了16種可能的數(shù)據(jù)目錄,這之中的11種現(xiàn)在在使用中。

            數(shù)據(jù)目錄

            WINNT.H之中所定義的數(shù)據(jù)目錄為:
            //WINNT.H
            // 目錄入口
            // 導(dǎo)出目錄
            #define IMAGE_DIRECTORY_ENTRY_EXPORT 0
            // 導(dǎo)入目錄
            #define IMAGE_DIRECTORY_ENTRY_IMPORT 1
            // 資源目錄
            #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2
            // 異常目錄
            #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3
            // 安全目錄
            #define IMAGE_DIRECTORY_ENTRY_SECURITY 4
            // 重定位基本表
            #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5
            // 調(diào)試目錄
            #define IMAGE_DIRECTORY_ENTRY_DEBUG 6
            // 描述字串
            #define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7
            // 機(jī)器值(MIPS GP)
            #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8
            // TLS目錄
            #define IMAGE_DIRECTORY_ENTRY_TLS 9
            // 載入配置目錄
            #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10
            
            基本上,每個(gè)數(shù)據(jù)目錄都是一個(gè)被定義為IMAGE_DATA_DIRECTORY的結(jié)構(gòu)。雖然數(shù)據(jù)目錄入口本身是相同的,但是每個(gè)特定的目錄種類(lèi)卻是完全唯一的。每個(gè)數(shù)據(jù)目錄的定義在本文的以后部分被描述為“預(yù)定義段”。
            //WINNT.H
            typedef struct _IMAGE_DATA_DIRECTORY {
            ULONG VirtualAddress;
            ULONG Size;
            } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
            
            每個(gè)數(shù)據(jù)目錄入口指定了該目錄的尺寸和相對(duì)虛擬地址。如果你要定義一個(gè)特定的目錄的話,就需要從可選頭部中的數(shù)據(jù)目錄數(shù)組中決定相對(duì)的地址, 然后使用虛擬地址來(lái)決定該目錄位于哪個(gè)段中。一旦你決定了哪個(gè)段包含了該目錄,該段的段頭部就會(huì)被用于查找數(shù)據(jù)目錄的精確文件偏移量位置。
               所以要獲得一個(gè)數(shù)據(jù)目錄的話,那么首先你需要了解段的概念。我在下面會(huì)對(duì)其進(jìn)行描述,這個(gè)討論之后還有一個(gè)有關(guān)如何定位數(shù)據(jù)目錄的示例。

            PE文件段

            PE文件規(guī)范由目前為止定義的那些頭部以及一個(gè)名為“段”的一般對(duì)象組成。段包含了文件的內(nèi)容,包括代碼、數(shù)據(jù)、資源以及其它可執(zhí)行信息,每個(gè)段都有一個(gè) 頭部和一個(gè)實(shí)體(原始數(shù)據(jù))。我將在下面描述段頭部的有關(guān)信息,但是段實(shí)體則缺少一個(gè)嚴(yán)格的文件結(jié)構(gòu)。因此,它們幾乎可以被鏈接器按任何的方法組織,只要 它的頭部填充了足夠能夠解釋數(shù)據(jù)的信息。

            段頭部

               PE文件格式中,所有的段頭部位于可選頭部之后。每個(gè)段頭部為40個(gè)字節(jié)長(zhǎng),并且沒(méi)有任何的填充信息。段頭部被定義為以下的結(jié)構(gòu):
            //WINNT.H
            #define IMAGE_SIZEOF_SHORT_NAME 8
            typedef struct _IMAGE_SECTION_HEADER {
            UCHAR Name[IMAGE_SIZEOF_SHORT_NAME];
            union {
            ULONG PhysicalAddress;
            ULONG VirtualSize;
            } Misc;
            ULONG VirtualAddress;
            ULONG SizeOfRawData;
            ULONG PointerToRawData;
            ULONG PointerToRelocations;
            ULONG PointerToLinenumbers;
            USHORT NumberOfRelocations;
            USHORT NumberOfLinenumbers;
            ULONG Characteristics;
            } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
            
            你如何才能獲得一個(gè)特定段的段頭部信息?既然段頭部是被連續(xù)的組織起來(lái)的,而且沒(méi)有一個(gè)特定的順序,那么段頭部必須由名稱(chēng)來(lái)定位。以下的函數(shù)示范了如何從一個(gè)給定了段名稱(chēng)的PE映像文件中獲得一個(gè)段頭部:
            //PEFILE.C
            BOOL WINAPI GetSectionHdrByName(LPVOID lpFile, IMAGE_SECTION_HEADER *sh, char *szSection)
            {
            PIMAGE_SECTION_HEADER psh;
            int nSections = NumOfSections (lpFile);
            int i;
            if ((psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile))
            != NULL)
            {
            /* 由名稱(chēng)查找段 */
            for (i = 0; i < nSections; i++)
            {
            if (!strcmp(psh->Name, szSection))
            {
            /* 向頭部復(fù)制數(shù)據(jù) */
            CopyMemory((LPVOID)sh, (LPVOID)psh,
            sizeof(IMAGE_SECTION_HEADER));
            return TRUE;
            }
            else
            psh++;
            }
            }
            return FALSE;
            }
            這個(gè)函數(shù)通過(guò)SECHDROFFSET宏將第一個(gè)段頭部定位,然后它開(kāi)始在所有段中循環(huán),并將要尋找的段名稱(chēng)和每個(gè)段的名稱(chēng)相比較,直到找 到了正確的那一個(gè)為止。當(dāng)找到了段的時(shí)候,函數(shù)將內(nèi)存映像文件的數(shù)據(jù)復(fù)制到傳入函數(shù)的結(jié)構(gòu)中,然后IMAGE_SECTION_HEADER結(jié)構(gòu)的各域就 能夠被直接存取了。

            段頭部的域

               ·Name。每個(gè)段都有一個(gè)8字符長(zhǎng)的名稱(chēng)域,并且第一個(gè)字符必須是一個(gè)句點(diǎn)。
               ·PhysicalAddress或VirtualSize。第二個(gè)域是一個(gè)union域,現(xiàn)在已不使用了。
            ·VirtualAddress。這個(gè)域標(biāo)識(shí)了進(jìn)程地址空間中要裝載這個(gè)段的虛擬地址。實(shí)際的地址由將這個(gè)域的值加上可選頭部結(jié)構(gòu)中的ImageBase 虛擬地址得到。切記,如果這個(gè)映像文件是一個(gè)DLL,那么這個(gè)DLL就不一定會(huì)裝載到ImageBase要求的位置。所以一旦這個(gè)文件被裝載進(jìn)入了一個(gè)進(jìn) 程,實(shí)際的ImageBase值應(yīng)該通過(guò)使用GetModuleHandle來(lái)檢驗(yàn)。
            ·SizeOfRawData。這個(gè)域表示了相對(duì)FileAlignment的段實(shí)體尺寸。文件中實(shí)際的段實(shí)體尺寸將少于或等于 FileAlignment的整倍數(shù)。一旦映像被裝載進(jìn)入了一個(gè)進(jìn)程的地址空間,段實(shí)體的尺寸將會(huì)變得少于或等于FileAlignment的整倍數(shù)。
               ·PointerToRawData。這是一個(gè)文件中段實(shí)體位置的偏移量。
               ·PointerToRelocations、PointerToLinenumbers、NumberOfRelocations、NumberOfLinenumbers。這些域在PE格式中不使用。
               ·Characteristics。定義了段的特征。這些值可以在WINNT.H及本光盤(pán)(譯注:MSDN的光盤(pán))的PE格式規(guī)范中找到。

            值         定義
            0x00000020 代碼段
            0x00000040 已初始化數(shù)據(jù)段
            0x00000080 未初始化數(shù)據(jù)段
            0x04000000 該段數(shù)據(jù)不能被緩存
            0x08000000 該段不能被分頁(yè)
            0x10000000 共享段
            0x20000000 可執(zhí)行段
            0x40000000 可讀段
            0x80000000 可寫(xiě)段

            定位數(shù)據(jù)目錄

               數(shù)據(jù)目錄存在于它們相應(yīng)的數(shù)據(jù)段中。典型地來(lái)說(shuō),數(shù)據(jù)目錄是段實(shí)體中的第一個(gè)結(jié)構(gòu),但不是必需的。由于這個(gè)緣故,如果你需要定位一個(gè)指定的數(shù)據(jù)目錄的話,就需要從段頭部和可選頭部中獲得信息。
               為了讓這個(gè)過(guò)程簡(jiǎn)單一點(diǎn),我編寫(xiě)了以下的函數(shù)來(lái)定位任何一個(gè)在WINNT.H之中定義的數(shù)據(jù)目錄。
            // PEFILE.C
            LPVOID WINAPI ImageDirectoryOffset(LPVOID lpFile,
            DWORD dwIMAGE_DIRECTORY)
            {
            PIMAGE_OPTIONAL_HEADER poh;
            PIMAGE_SECTION_HEADER psh;
            int nSections = NumOfSections(lpFile);
            int i = 0;
            LPVOID VAImageDir;
            /* 必須為0到(NumberOfRvaAndSizes-1)之間 */
            if (dwIMAGE_DIRECTORY >= poh->NumberOfRvaAndSizes)
            return NULL;
            /* 獲得可選頭部和段頭部的偏移量 */
            poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile);
            psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile);
            /* 定位映像目錄的相對(duì)虛擬地址 */
            VAImageDir = (LPVOID)poh->DataDirectory
            [dwIMAGE_DIRECTORY].VirtualAddress;
            /* 定位包含映像目錄的段 */
            while (i++ < nSections)
            {
            if (psh->VirtualAddress <= (DWORD)VAImageDir &&
            psh->VirtualAddress +
            psh->SizeOfRawData > (DWORD)VAImageDir)
            break;
            psh++;
            }
            if (i > nSections)
            return NULL;
            /* 返回映像導(dǎo)入目錄的偏移量 */
            return (LPVOID)(((int)lpFile +
            (int)VAImageDir. psh->VirtualAddress) +
            (int)psh->PointerToRawData);
            }
            
            該函數(shù)首先確認(rèn)被請(qǐng)求的數(shù)據(jù)目錄入口數(shù)字,然后它分別獲取指向可選頭部和第一個(gè)段頭部的兩個(gè)指針。它從可選頭部決定數(shù)據(jù)目錄的虛擬地址, 然后它使用這個(gè)值來(lái)決定數(shù)據(jù)目錄定位在哪個(gè)段實(shí)體之中。如果適當(dāng)?shù)亩螌?shí)體已經(jīng)被標(biāo)識(shí)了,那么數(shù)據(jù)目錄特定的位置就可以通過(guò)將它的相對(duì)虛擬地址轉(zhuǎn)換為文件中 地址的方法來(lái)找到。
            posted @ 2010-10-12 16:38 小果子 閱讀(308) | 評(píng)論 (0)編輯 收藏

            在STL中為了提供通用的操作而又不損失效率,我們用到了一種特殊的技巧,叫traits編程技巧。具體的來(lái)說(shuō),traits就是 通過(guò)定義一些結(jié)構(gòu)體或類(lèi),并利用模板類(lèi)特化和偏特化的能力,給類(lèi)型賦予一些特性,這些特性根據(jù)類(lèi)型的不同而異。在程序設(shè)計(jì)中可以使用這些traits來(lái)判 斷一個(gè)類(lèi)型的一些特性,引發(fā)C++的函數(shù)重載機(jī)制,實(shí)現(xiàn)同一種操作因類(lèi)型不同而異的效果。traits的編程技巧極度彌補(bǔ)了C++語(yǔ)言的不足 。


            舉例:

            現(xiàn)在定義一個(gè)__type_traits可以獲得類(lèi)型的如下屬性:
            1. 是否存在non-trivial default constructor
            2. 是否存在non-trivial copy constructor
            3. 是否存在non-trivial assignment operator
            4. 是否存在non-trivial destructor

            struct __true_type {
            };
            struct __false_type {
            };

            template <class _Tp>
            struct __type_traits {

               typedef __false_type    has_trivial_default_constructor;
               typedef __false_type    has_trivial_copy_constructor;
               typedef __false_type    has_trivial_assignment_operator;
               typedef __false_type    has_trivial_destructor;
            };


            問(wèn)題:為什么把對(duì)象的所有的屬性都定義為_(kāi)_false_type?
            這樣是采用最保守的做法,先把所有的對(duì)象屬性都設(shè)置為_(kāi)_false_type,然后在針對(duì)每個(gè)基本數(shù)據(jù)類(lèi)型設(shè)計(jì)特化的__type_traits,就可以達(dá)到預(yù)期的目的,如可以定義__type_traits<int>如下:

            template <>
            struct __type_traits<int> {
               typedef __true_type    has_trivial_default_constructor;
               typedef __true_type    has_trivial_copy_constructor;
               typedef __true_type    has_trivial_assignment_operator;
               typedef __true_type    has_trivial_destructor;
            };

            template <>
            struct __type_traits<char> {
               typedef __true_type    has_trivial_default_constructor;
               typedef __true_type    has_trivial_copy_constructor;
               typedef __true_type    has_trivial_assignment_operator;
               typedef __true_type    has_trivial_destructor;
            };

                ......

                ......


            其他基本類(lèi)型的traits也可以有相應(yīng)的定義

            __type_traits的偏特化版本
            template <class _Tp>
            struct __type_traits<_Tp*> {
               typedef __true_type    has_trivial_default_constructor;
               typedef __true_type    has_trivial_copy_constructor;
               typedef __true_type    has_trivial_assignment_operator;
               typedef __true_type    has_trivial_destructor;
               typedef __true_type    is_POD_type;
            };

            我們可以自定義__type_traits的特化版本
            比如對(duì)與自定義的Shape類(lèi)型,我們可以這樣定義__type_traits<Shape>
            struct __type_traits<Shape> {
               typedef __false_type    has_trivial_default_constructor;
               typedef __true_type    has_trivial_copy_constructor;
               typedef __true_type    has_trivial_assignment_operator;
               typedef __true_type    has_trivial_destructor;
               typedef __true_type    is_POD_type;
            };

            如果編譯器夠厲害,我們甚至可以不用自己去定義特化的__type_traits,編譯器就能夠幫我們搞定:)

            如何使用呢?

            假設(shè)現(xiàn)在用個(gè)模板函數(shù)fun需要根據(jù)類(lèi)型T是否有non-trivial constructor來(lái)進(jìn)行不同的操作,可以這樣來(lái)實(shí)現(xiàn):

            template<class T>
            void fun()
            {
                 typedef typename __type_traits<T>::has_trivial_constructor _Trivial_constructor;
                __fun(_Trivial_constructor()); // 根據(jù)得到的_Trivial_constructor來(lái)調(diào)用相應(yīng)的函數(shù)
            }

            // 兩個(gè)重載的函數(shù)
            void _fun(_true_type)
            {
            cout<<"fun(_true_type)called"<<endl;
            }
            void _fun(_false_type)
            {
            cout<<"fun(_false_type) called"<<endl;
            }

            //測(cè)試代碼

            int main()
            {
            fun<char>();
            fun<int>();
            fun<char *>();
            fun<double>();
            }

            posted @ 2010-10-11 09:24 小果子 閱讀(1622) | 評(píng)論 (0)編輯 收藏

            用template要求寫(xiě)一個(gè)模板函數(shù),返回值要求是參數(shù)類(lèi)型,初步設(shè)計(jì)
            template<typename T>
            class AIter{
            public:
                AIter(T
            * p=0):ptr(p){};
                T
            * ptr;
                typedef T value_type;
                T
            & operator*()const{
                    
            return *ptr;
                }
                T
            * operator->()const{
                    
            return ptr;
                }
            };
            template
            <typename T>
            typename T::value_type
            func(T val){
                
            return *val;
            }

            這方法一個(gè)缺陷就是對(duì)于不是class type的類(lèi)型無(wú)能為力,比如原生指針,只有class type類(lèi)型才能內(nèi)嵌類(lèi)型

            改進(jìn)--模板偏特化(template partial specialization)
            聲明一個(gè)類(lèi)型
            template<typename T>
            struct stl_iterator_traits{
                typedef typename T::value_type value_type;
            };
            原先的func可以寫(xiě)成這樣
            template<typename T>
            typename stl_iterator_traits<T>::value_type
            func(T val){
                return *val;
            }
            這樣還是處理不了
            int* p=new int(3);
            func(p);
            原生指針類(lèi)型,為其提供特化版本
            template<typename T>
            struct stl_iterator_traits<T*>{
                typedef T value_type;
            };
            這樣就能完美解決剛才問(wèn)題

            但是對(duì)于指向常數(shù)對(duì)象的指針
            stl_iterator_traits<const int*>::value_type
            我們希望暫時(shí)存儲(chǔ)一個(gè)變量,但是我們獲取的類(lèi)型是const int,聲明一個(gè)無(wú)法賦值的臨時(shí)變量無(wú)意義,所以我們?cè)谔峁┮粋€(gè)特化版本
            template<typename T>
            struct stl_iterator_traits<const T*>{
                typedef T value_type;
            };
            iterator example:
            #include <iterator>
            //#using <mscorlib.dll>
            #include <iostream>
            #include 
            <memory>
            #include 
            <vector>
            #include 
            <algorithm>
            //using namespace System;
            using namespace std;

            template
            <typename T>
            class ListItem{
            public:
                ListItem(T value){
                    _value
            =value;
                    _next
            =NULL;
                }
                ListItem(){
                    _next
            =NULL;
                    _value
            =0;
                }
                T value()
            const{
                    
            return _value;
                }
                ListItem
            <T>* _next;
                T _value;    
            };
            template
            <class Item>
            class ListIter:public iterator<std::forward_iterator_tag,Item>{
            public:
                Item
            * ptr;
                ListIter(Item
            * p=0):ptr(p){};
                Item
            & operator*()const{
                    
            return *ptr;
                }
                Item
            * operator->()const{
                    
            return ptr;
                }
                ListIter
            & operator++(){
                    ptr
            =ptr->_next;
                    
            return *this;
                }
                ListIter 
            operator++(int){
                    ListIter tmp
            =*this;
                    
            ++(*this);
                    
            return tmp;
                }
                
            bool operator==(const ListIter& iter)const{
                    
            return ptr==iter.ptr;
                }
                
            bool operator!=(const ListIter& iter)const{
                    
            return ptr!=iter.ptr;
                }
            };
            template
            <typename T>
            bool operator==(ListItem<T>& item,T value){
                
            return item.value()==value;
            }

            template
            <typename T>
            class List{
            public:
                typedef ListIter
            <ListItem<T> > iterator;
                List(){
                    _end
            =new ListItem<T>();
                    _front
            =0;
                }
                
            void insert_front(T value){
                    ListItem
            <T>* item=new ListItem<T>(value);
                    
            if(empty()){
                        item
            ->_next=_end;
                        _front
            =item;
                    }
            else{
                        item
            ->_next=_front;
                        _front
            =item;
                    }
                };
                
            bool empty(){
                    
            return _front==NULL;
                }
                
            void insert_end(T value){
                    
            //ListItem<T>* item=new ListItem<T>(value);
                    if(empty()){
                        _front
            =_end;
                        _end
            ->_value=value;
                        _end
            ->_next=new ListItem<T>();
                        _end
            =_end->_next;
                    }
            else{
                        _end
            ->_value=value;
                        _end
            ->_next=new ListItem<T>();
                        _end
            =_end->_next;
                    }
                };
                
            void display(ostream& os=cout){
                    ListItem
            <T>* head=_front;
                    
            while(head!=_end){
                        cout
            <<head->value()<<endl;
                        head
            =head->_next;
                    }
                };
                ListItem
            <T>* front(){
                    
            return _front;
                }
            private:
                ListItem
            <T>* _end;
                ListItem
            <T>* _front;
                
            long _size;
            };

            template
            <typename T>
            struct stl_iterator_traits{
                typedef typename T::value_type value_type;
            };

            template
            <typename T>
            struct stl_iterator_traits<T*>{
                typedef T value_type;
            };

            template
            <typename T>
            class AIter{
            public:
                AIter(T
            * p=0):ptr(p){};
                T
            * ptr;
                typedef T value_type;
                T
            & operator*()const{
                    
            return *ptr;
                }
                T
            * operator->()const{
                    
            return ptr;
                }
            };
            template
            <typename T>
            typename stl_iterator_traits
            <T>::value_type
            func(T val){
                
            return *val;
            }
            int _tmain(int argc, _TCHAR* argv[])
            {
                List
            <int> list;
                
            for(int i=0;i<5;i++){
                    list.insert_front(i);
                    list.insert_end(i
            +2);
                }
                list.display();
                
                
                List
            <int>::iterator begin(list.front());
                List
            <int>::iterator end;
                List
            <int>::iterator iter;

                
            //vector<int>::iterator itere;
                AIter<int> it(new int(2));
                
                iter
            =find(begin,end,2);
                cout
            <<iter->value()<<endl;
                
            //list.insert_end(1);
                
            //list.insert_end(2);
                
            //list.display();
                
            //list.insert_end(
                return 0;
            }

            現(xiàn)在對(duì)于class type 迭代器AIter,還是原生指針int* 或const int*,都能獲取正確類(lèi)型int
            stl規(guī)定,每個(gè)迭代器都要自己內(nèi)嵌型別定義的方式定義出相應(yīng)型別
            (待續(xù)...)
            posted @ 2010-10-09 13:23 小果子 閱讀(233) | 評(píng)論 (0)編輯 收藏
                 摘要: 第一章 從C轉(zhuǎn)向C++ 對(duì)每個(gè)人來(lái)說(shuō),習(xí)慣C++需要一些時(shí)間,對(duì)于已經(jīng)熟悉C的程序員來(lái)說(shuō),這個(gè)過(guò)程尤其令人苦惱。因?yàn)镃是C++的子集,所有的C的技術(shù)都可以繼續(xù)使用,但很多用起來(lái)又不太合適。例如,C++程序員會(huì)認(rèn)為指針的指針看起來(lái)很古怪,他們會(huì)問(wèn):為什么不用指針的引用來(lái)代替呢?C 是一種簡(jiǎn)單的語(yǔ)言。它真正提供的只有有宏、指針、結(jié)構(gòu)、數(shù)組和函數(shù)。不管什么問(wèn)題,C都靠宏、指針、結(jié)構(gòu)、數(shù)組和函數(shù)來(lái)解決...  閱讀全文
            posted @ 2010-10-08 09:19 小果子 閱讀(810) | 評(píng)論 (0)編輯 收藏
            今天看STL源碼遇到一個(gè)問(wèn)題:
             
            template <class _T1, class _T2>
            inline void _Construct(_T1* __p, const _T2& __value) {
              new ((void*) __p) _T1(__value);
            }
             
            上網(wǎng)搜到了一些文章,分享了:
             
            原帖地址: http://www.ksarea.com/articles/20080124_cc.html
             

            "placement new"? Embarrassed它 到底是什么東東呀?我也是最近幾天才聽(tīng)說(shuō),看來(lái)對(duì)于C++我還差很遠(yuǎn)呀!placement new 是重載operator new的一個(gè)標(biāo)準(zhǔn)、全局的版本,它不能被自定義的版本代替(不像普通的operator new和operator delete能夠被替換成用戶自定義的版本)。

            它的原型如下:
            void *operator new( size_t, void *p ) throw()  { return p; }

            首先我們區(qū)分下幾個(gè)容易混淆的關(guān)鍵詞:new、operator new、placement new
            new和delete操作符我們應(yīng)該都用過(guò),它們是對(duì)中的內(nèi)存進(jìn)行申請(qǐng)和釋放,而這兩個(gè)都是不能被重載的。要實(shí)現(xiàn)不同的內(nèi)存分配行為,需要重載operator new,而不是new和delete。I dont know

            看如下代碼:
            class MyClass {…};
            MyClass * p=new MyClass;

            這里的new實(shí)際上是執(zhí)行如下3個(gè)過(guò)程:


            1. 調(diào)用operator new分配內(nèi)存 ;2. 調(diào)用構(gòu)造函數(shù)生成類(lèi)對(duì)象;3. 返回相應(yīng)指針。

            operator new就像operator+一樣,是可以重載的,但是不能在全局對(duì)原型為void operator new(size_t size)這個(gè)原型進(jìn)行重載,一般只能在類(lèi)中進(jìn)行重載。如果類(lèi)中沒(méi)有重載operator new,那么調(diào)用的就是全局的::operator new來(lái)完成堆的分配。同理,operator new[]、operator delete、operator delete[]也是可以重載的,一般你重載的其中一個(gè),那么最后把其余的三個(gè)都重載一遍。

            至于placement new才是本文的重點(diǎn)。其實(shí)它也只是operator new的一個(gè)重載的版本,只是我們很少用到它。如果你想在已經(jīng)分配的內(nèi)存中創(chuàng)建一個(gè)對(duì)象,使用new時(shí)行不通的。也就是說(shuō)placement new允許你在一個(gè)已經(jīng)分配好的內(nèi)存中(?;蛘叨阎校?gòu)造一個(gè)新的對(duì)象。原型中void*p實(shí)際上就是指向一個(gè)已經(jīng)分配好的內(nèi)存緩沖區(qū)的的首地址。

            我們知道使用new操作符分配內(nèi)存需要在堆中查找足夠大的剩余空間,這個(gè)操作速度是很慢的,而且有可能出現(xiàn)無(wú)法分配內(nèi)存的異常(空間不夠)。 placement new就可以解決這個(gè)問(wèn)題。我們構(gòu)造對(duì)象都是在一個(gè)預(yù)先準(zhǔn)備好了的內(nèi)存緩沖區(qū)中進(jìn)行,不需要查找內(nèi)存,內(nèi)存分配的時(shí)間是常數(shù);而且不會(huì)出現(xiàn)在程序運(yùn)行中途 出現(xiàn)內(nèi)存不足的異常。所以,placement new非常適合那些對(duì)時(shí)間要求比較高,長(zhǎng)時(shí)間運(yùn)行不希望被打斷的應(yīng)用程序。

            使用方法如下:
            1. 緩沖區(qū)提前分配
            可以使用堆的空間,也可以使用棧的空間,所以分配方式有如下兩種:
            class MyClass {…};
            char *buf=new char[N*sizeof(MyClass)+sizeof(int)];或者char buf[N*sizeof(MyClass)+sizeof(int)];

            2. 對(duì)象的構(gòu)造
            MyClass * pClass=new(buf) MyClass;

            3. 對(duì)象的銷(xiāo)毀
            一旦這個(gè)對(duì)象使用完畢,你必須顯式的調(diào)用類(lèi)的析構(gòu)函數(shù)進(jìn)行銷(xiāo)毀對(duì)象。但此時(shí)內(nèi)存空間不會(huì)被釋放,以便其他的對(duì)象的構(gòu)造。
            pClass->~MyClass();

            4. 內(nèi)存的釋放
            如果緩沖區(qū)在堆中,那么調(diào)用delete[] buf;進(jìn)行內(nèi)存的釋放;如果在棧中,那么在其作用域內(nèi)有效,跳出作用域,內(nèi)存自動(dòng)釋放。

            注意:

            • 在C++標(biāo)準(zhǔn)中,對(duì)于placement operator new []有如下的說(shuō)明: placement operator new[] needs implementation-defined amount of additional storage to save a size of array. 所以我們必須申請(qǐng)比原始對(duì)象大小多出sizeof(int)個(gè)字節(jié)來(lái)存放對(duì)象的個(gè)數(shù),或者說(shuō)數(shù)組的大小。
            • 使用方法第二步中的new才是placement new,其實(shí)是沒(méi)有申請(qǐng)內(nèi)存的,只是調(diào)用了構(gòu)造函數(shù),返回一個(gè)指向已經(jīng)分配好的內(nèi)存的一個(gè)指針,所以對(duì)象銷(xiāo)毀的時(shí)候不需要調(diào)用delete釋放空間,但必須調(diào)用析構(gòu)函數(shù)銷(xiāo)毀對(duì)象。



            placement new 是重載operator new 的一個(gè)標(biāo)準(zhǔn)、全局的版本,它不能夠被自定義的版本代替(不像普通版本的operator new 和 operator delete能夠被替換)。

            void *operator new( size_t, void *p ) throw()     { return p; }

            placement new的執(zhí)行忽略了size_t參數(shù),只返還第二個(gè)參數(shù)。其結(jié)果是允許用戶把一個(gè)對(duì)象放到一個(gè)特定的地方,達(dá)到調(diào)用構(gòu)造函數(shù)的效果。

            和其他普通的new不同的是,它在括號(hào)里多了另外一個(gè)參數(shù)。比如:
            Widget * p = new Widget; - - - - - - - - - //ordinary new 
            pi = new (ptr) int; pi = new (ptr) int;     //placement new

            括 號(hào)里的參數(shù)ptr是一個(gè)指針,它指向一個(gè)內(nèi)存緩沖器,placement new將在這個(gè)緩沖器上分配一個(gè)對(duì)象。Placement new的返回值是這 個(gè)被構(gòu)造對(duì)象的地址(比如括號(hào)中的傳遞參數(shù))。placement new主要適用于:在對(duì)時(shí)間要求非常高的應(yīng)用程序中,因?yàn)檫@些程序分配的時(shí)間是確定 的;長(zhǎng)時(shí)間運(yùn)行而不被打斷的程序;以及執(zhí)行一個(gè)垃圾收集器 (garbage collector)。
            ?    new 、operator new 和 placement new 區(qū)別
            new :不能被重載,其行為總是一致的。它先調(diào)用operator new分配內(nèi)存,然后調(diào)用構(gòu)造函數(shù)初始化那段內(nèi)存。

            operator new:要實(shí)現(xiàn)不同的內(nèi)存分配行為,應(yīng)該重載operator new,而不是new。

            delete和operator delete類(lèi)似。

            placement new:只是operator new重載的一個(gè)版本。它并不分配內(nèi)存,只是返回指向已經(jīng)分配好的某段內(nèi)存的一個(gè)指針。因此不能刪除它,但需要調(diào)用對(duì)象的析構(gòu)函數(shù)。
            ?    new 操作符的執(zhí)行過(guò)程
            1. 調(diào)用operator new分配內(nèi)存 ;
            2. 調(diào)用構(gòu)造函數(shù)生成類(lèi)對(duì)象;
            3. 返回相應(yīng)指針。

            operator new 就像operator+一樣,是可以重載的。如果類(lèi)中沒(méi)有重載operator new,那么調(diào)用的就是全局的::operator new來(lái)完成堆的分 配。同理,operator new[]、operator delete、operator delete[]也是可以重載的,其實(shí) operator new也是operator new的一個(gè)重載的版本,只是很少用而已。如果你想在已經(jīng)分配的內(nèi)存中創(chuàng)建一個(gè)對(duì)象,使用new時(shí)行不通 的。也就是說(shuō)placement new允許你在一個(gè)已經(jīng)分配好的內(nèi)存中(?;蛘叨阎校?gòu)造一個(gè)新的對(duì)象。原型中void*p實(shí)際上就是指向一個(gè)已經(jīng)分配 好的內(nèi)存緩沖區(qū)的的首地址。
            ?    Placement new 存在的理由
            1.用Placement new 解決buffer的問(wèn)題

            問(wèn) 題描述:用new分配的數(shù)組緩沖時(shí),由于調(diào)用了默認(rèn)構(gòu)造函數(shù),因此執(zhí)行效率上不佳。若沒(méi)有默認(rèn)構(gòu)造函數(shù)則會(huì)發(fā)生編譯時(shí)錯(cuò)誤。如果你想在預(yù)分配的內(nèi)存上創(chuàng)建 對(duì)象,用缺省的new操作符是行不通的。要解決這個(gè)問(wèn)題,你可以用placement new構(gòu)造。它允許你構(gòu)造一個(gè)新對(duì)象到預(yù)分配的內(nèi)存上。

            2.增大時(shí)空效率的問(wèn)題
             
            使用new操作符分配內(nèi)存需要在堆中查找足夠大的剩余空間,顯然這個(gè)操作速度是很慢的,而且有可能出現(xiàn)無(wú)法分配內(nèi)存的異常(空間不夠)。 
            placement new 就可以解決這個(gè)問(wèn)題。我們構(gòu)造對(duì)象都是在一個(gè)預(yù)先準(zhǔn)備好了的內(nèi)存緩沖區(qū)中進(jìn)行,不需要查找內(nèi)存,內(nèi)存分配的時(shí)間是常數(shù);而且不會(huì)出現(xiàn)在程序運(yùn)行中途出現(xiàn)內(nèi) 存不足的異常。所以,placement new非常適合那些對(duì)時(shí)間要求比較高,長(zhǎng)時(shí)間運(yùn)行不希望被打斷的應(yīng)用程序。
            ?    使用步驟
            在很多情況下,placement new的使用方法和其他普通的new有所不同。這里提供了它的使用步驟。

            第一步  緩存提前分配
            有三種方式:
            1.為了保證通過(guò)placement new使用的緩存區(qū)的memory alignmen(內(nèi)存隊(duì)列)正確準(zhǔn)備,使用普通的new來(lái)分配它:在堆上進(jìn)行分配

            class Task ;

            char * buff = new [sizeof(Task)]; //分配內(nèi)存

            (請(qǐng)注意auto或者static內(nèi)存并非都正確地為每一個(gè)對(duì)象類(lèi)型排列,所以,你將不能以placement new使用它們。)

            2.在棧上進(jìn)行分配

            class Task ;
            char buf[N*sizeof(Task)]; //分配內(nèi)存

            3.還有一種方式,就是直接通過(guò)地址來(lái)使用。(必須是有意義的地址)

            void* buf = reinterpret_cast<void*> (0xF00F);

            第二步:對(duì)象的分配

            在剛才已分配的緩存區(qū)調(diào)用placement new來(lái)構(gòu)造一個(gè)對(duì)象。

            Task *ptask = new (buf) Task
            第三步:使用
            按照普通方式使用分配的對(duì)象:

            ptask->memberfunction();

            ptask-> member;

            //...

            第四步:對(duì)象的析構(gòu)

            一旦你使用完這個(gè)對(duì)象,你必須調(diào)用它的析構(gòu)函數(shù)來(lái)毀滅它。按照下面的方式調(diào)用析構(gòu)函數(shù):

            ptask->~Task(); //調(diào)用外在的析構(gòu)函數(shù)

            第五步:釋放

            你可以反復(fù)利用緩存并給它分配一個(gè)新的對(duì)象(重復(fù)步驟2,3,4)如果你不打算再次使用這個(gè)緩存,你可以象這樣釋放它:

            delete [] buf;

            跳過(guò)任何步驟就可能導(dǎo)致運(yùn)行時(shí)間的崩潰,內(nèi)存泄露,以及其它的意想不到的情況。如果你確實(shí)需要使用placement new,請(qǐng)認(rèn)真遵循以上的步驟。
            posted @ 2010-09-30 17:01 小果子 閱讀(1101) | 評(píng)論 (0)編輯 收藏
            僅列出標(biāo)題
            共58頁(yè): First 26 27 28 29 30 31 32 33 34 Last 
            成人综合久久精品色婷婷| 久久影院午夜理论片无码| 久久久中文字幕日本| 国产99久久久国产精免费| 亚洲色婷婷综合久久| 亚洲欧美日韩中文久久| 亚洲午夜久久久久久噜噜噜| 日韩精品久久无码人妻中文字幕| 18禁黄久久久AAA片| 亚洲精品乱码久久久久66| 亚洲精品乱码久久久久久| 久久国产精品成人片免费| 久久电影网2021| 久久久久久极精品久久久| 国产精品久久久久久久人人看| 久久人人爽人人爽人人片AV麻烦| 中文字幕人妻色偷偷久久| 精品久久人妻av中文字幕| 九九久久99综合一区二区| 国产精品亚洲综合专区片高清久久久| 精品国产一区二区三区久久蜜臀| 久久er国产精品免费观看8| 香蕉久久夜色精品国产2020| 乱亲女H秽乱长久久久| 国产成人精品久久亚洲高清不卡 | 国产A级毛片久久久精品毛片| 久久免费观看视频| 亚洲午夜久久久影院伊人| 91精品无码久久久久久五月天| 天天影视色香欲综合久久| 久久精品a亚洲国产v高清不卡| 超级碰久久免费公开视频| 久久久无码精品亚洲日韩京东传媒| 久久久无码一区二区三区| 日批日出水久久亚洲精品tv| 久久精品中文騷妇女内射| 四虎影视久久久免费观看| 国产一级做a爰片久久毛片| 欧美久久一级内射wwwwww.| 99久久人妻无码精品系列蜜桃| 久久涩综合|