VC 編譯命令開關 vc能夠能夠經過Settings -->Project-->C/C++-->Customize來設置這個編譯開關 /C:在預處理輸出中保留註釋語句 /c:只編譯,不鏈接,至關於在"Build"菜單下選擇了"Compile" /D:定義常量和宏,與源程序裏的#define 有相同效果 /E:預處理C、C++源文件,將源文件中全部的預編譯指令及宏展開,將註釋去掉,而後將預處理器的輸出拷貝至標準輸出設備輸出,而且在每一個文件的開頭和末尾加入#line /EH:指定編譯器用何種異常處理模型 /EP:同/E,只是去掉了#line /F:設置程序的堆棧大小 /FA:設置生成何種列表文件(彙編、彙編與機器碼、彙編與源碼、彙編與機器碼以及源碼) /Fa:指定用/FA設置的列表文件的存放路徑及(或)文件名 /FD:生成文件的相互依賴信息 /Fd:設置程序數據庫文件(PDB)的存放路徑及(或)文件名 /Fe:設置最終可執行文件的存放路徑及(或)文件名 /FI:預處理指定的頭文件,與源文件中的#include有相同效果 /Fm:建立map文件 /Fo:設置編譯後Obj文件的存放路徑及(或)文件名 /Fp:設置預編譯文件(pch)的存放路徑及(或)文件名 /FR:生成瀏覽信息(sbr)文件 /Fr:同/FR,不一樣之處在於/Fr不包括局部變量信息 /G3:爲80386處理器優化代碼生成 /G4:爲80486處理器優化代碼生成 /G5:爲Pentium處理器優化代碼生成 /G6:爲Pentium Pro處理器優化代碼生成 /GA:爲Windows應用程序做優化 /GB:爲Pentium處理器優化代碼生成,使用8038六、80486、Pentium、Pentium Pro的混合指令集,是代碼生成的默認選項(程序屬性選項中Processor對應Blend) /GD:爲Windows動態庫(dll)做優化,此開關在VC6中沒有實現 /Gd:指定使用__cdecl的函數調用規則 /Ge:激活堆棧檢測 /GF:消除程序中的重複的字符串,並將她放到只讀的緩衝區中 /Gf:消除程序中的重複字符串 /Gh:在每一個函數的開頭調用鉤子(hook)函數--penter /Gi:容許漸進編譯 /Gm:容許最小化rebuild /GR:容許運行時類型信息(Run-Time Type Infomation) /Gr:指定使用__fastcall的函數調用規則 /Gs:控制堆棧檢測所用內存大小 /GT:支持用__declspec(thread)分配的數據的fier-safety /GX:容許同步異常處理,與/EHsc開關等價 /Gy:容許編譯器將每個函數封裝成COMDATs的形式,供鏈接器調用 /GZ:容許在Debug build 的時候捕捉Release build的錯誤 /Gz:指定使用__stdcall的函數調用規則 /H:限制外部名字的長度 /HELP:列出編譯器的全部的命令開關 /I:指定頭文件的搜索路徑 /J:將char的缺省類型從signed char改爲unsigned char /LD:建立一個動態鏈接庫 /LDd:建立一個Debug版本的動態連接庫 /link:將指定的選項傳給鏈接器 /MD:選擇多線程、DLL版本的C Run-Time庫 /MDd:選擇多線程、DLL、Debug版本的C Run-Time庫 /ML:選擇單線程版本的C Run—Time庫 /MLd:選擇單線程、Debug版本的C Run—Time庫 /MT:選擇多線程版本的C Run-Time庫 /MTd:選擇多線程、Debug版本的C Run—Time庫 /nologo:不顯示程序的版權信息 /O1:優化使產生的可執行代碼最小 /O2:優化使產生的可執行代碼速度最快 /Oa:指示編譯器程序裏沒有使用別名,能夠提升程序的執行速度 /Ob:控制內聯(inline)函數的展開 /Od:禁止代碼優化 /Og:使用全局優化 /Oi:用內部函數去代替程序裏的函數調用,可使程序運行的更快,但程序的長度變長 /Op:提升浮點數比較運算的一致性 /Os:產生儘量小的可執行代碼 /Ot:產生儘量塊的可執行代碼 /Ow:指示編譯器在函數體內部沒有使用別名 /Ox:組合了幾個優化開關,達到儘量多的優化 /Oy:阻止調用堆棧裏建立幀指針 /Q1f:對核心級的設備驅動程序生成單獨的調試信息 /QI0f:對Pentium 0x0f錯誤指令做修正 /Qifdiv:對Pentium FDIV錯誤指令做修正 /P:將預處理輸出寫到指定文件裏,文件的後綴名爲I /TC:將命令行上的全部文件都看成C源程序編譯,無論後綴名是否爲.c /Tc:將指定的文件看成C源程序編譯,無論後綴名是否爲.c /TP:將命令行上的全部文件都看成C++源程序編譯,無論後綴名是否爲.cpp /Tp:將指定文件看成C++源程序編譯,無論後綴名是否爲.cpp /U:去掉一個指定的前面定義的符號或常量 /u:去掉全部前面定義的符號或常量 /V:在編譯的obj文件裏嵌入版本號 /vd:禁止/容許構造函數置換 /vmb:選擇指針的表示方法,使用這個開關,在聲明指向某個類的成員的指針以前,必須先定義這個類 /vmg:選擇指針的表示方法,使用這個開關,在聲明指向某個類的成員的指針以前,沒必要先定義這個類,但要首先指定這個類是使用何種繼承方法 /vmm:設置指針的表示方法爲Single Inheritance and Multiple Inheritance /vms:設置指針的表示方法爲Single Inheritance /vmv:設置指針的表示方法爲Any class /W:設置警告等級 /w:禁止全部警告 /X:阻止編譯器搜索標準的include 目錄 /Yc:建立預編譯頭文件(pch) /Yd:在全部的obj文件裏寫上徹底的調試信息 /Yu:在build過程當中使用指定的預編譯頭文件 /YX:指示編譯器若預編譯頭文件存在,則使用它,若不存在,則建立一個 /Z7:生成MSC7.0兼容的調試信息 /Za:禁止語言擴展(Microsoft Extensions to C) /Zd:調試信息只包含外部和全局的符號信息以及行號信息 /Ze:容許語言擴展(Microsoft Extensions to C) /Zg:爲源文件裏面定義的每一個函數生成函數原型 /ZI:生成程序庫文件(Pdb)並支持Edit and Continue調試特性 /Zi:生成程序庫文件(pdb),包含類型信息和符號調試信息 /ZL:從obj文件裏去掉缺省的庫文件名 /Zm:設置編譯器的內存分配xianzhi /Zn:禁止瀏覽信息文件裏面的封裝 /Zp:設置結構成員在內存裏面的封裝格式 /Zs:快速檢查語法錯誤 -------------------------- vc所支持的文件類型 DSW:全稱是Developer Studio Workspace,最高級別的配置文件,記錄了整個工做空間的配置信息,她是一個純文本的文件,在vc建立新項目的時候自動生成 DSP:全稱是Developer Studio Project,也是一個配置文件,不過她記錄的是一個項目的全部配置信息,純文本文件 OPT:與DSW、DSP配合使用的配置文件,她記錄了與機器硬件有關的信息,同一個項目在不一樣的機器上的opt文件內容是不一樣的 CLW:記錄了跟ClassWizard相關的信息,若是丟失了clw文件,那麼在Class View面板裏就沒有類信息 PLG:其實是一個超文本文件,能夠用Internet Explorer打開,記錄了Build的過程,是一個日誌型文件 RC:資源描述文件,記錄了全部的資源信息,在資源編輯器裏做的修改,實際上都是對RC文件的修改 RC2:附加的資源描述文件,不能直接資源編輯器修改,只能手工添加,能夠用來添加額外的資源 RES:通過資源編輯器編譯以後的資源文件,以二進制方式存放 SBR:編譯器生成的瀏覽信息文件,在代碼導航的時候很是有用,她須要在編譯時指定/FR或者/Fr開關 BSC:BSCMAKE.EXE將全部的SBR文件做爲輸入,通過處理以後輸出一個BSC文件,在代碼導航的時候實際用到的是BSC文件 ILK:當選定漸增型編譯鏈接時,鏈接器自動生成ILK文件,記錄鏈接信息 PDB:全稱是Program DataBase,即程序數據庫文件,用來記錄調試信息,是一個至關重要的文件,沒有他,程序沒法正常調試 LIB:若是項目輸出是Dll的話,通常會輸出一個跟項目同名的Lib文件,記錄輸出的函數信息 EXP:同Lib,是跟Dll一塊兒生成的輸出文件 PCH:全稱是PreCompiled Header,就是預先編譯好的頭文件,在編譯時指定/Yu開關時編譯器自動生成 vc一些預處理 1) #if defined XXX_XXX #endif 是條件編譯,是根據你是否認義了XXX_XXX這個宏,而使用不一樣的代碼。 通常.h文件裏最外層的 #if !defined XXX_XXX #define XXX_XXX #endif 是爲了防止這個.h頭文件被重複include。 #undef爲解除定義,#ifndef是if not defined的縮寫,即若是沒有定義。 2) #error XXXX 是用來產生編譯時錯誤信息XXXX的,通常用在預處理過程當中; 例子: #if !defined(__cplusplus) #error C++ compiler required. #endif 3) extern 全局變量 至關於C#中的public 4) __cdecl和__stdcall是兩種C++函數調用規則的系統約定。 __cdecl的: Argument-passing order Right to left Stack-maintenance responsibility Calling function pops the arguments from the stack Name-decoration convention Underscore character (_) is prefixed to names, except when exporting __cdecl functions that use C linkage. Case-translation convention No case translation __stdcall的: Argument-passing order Right to left. Argument-passing convention By value, unless a pointer or reference type is passed. Stack-maintenance responsibility Called function pops its own arguments from the stack. Name-decoration convention An underscore (_) is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list. Therefore, the function declared as int func( int a, double b ) is decorated as follows: _func@12 Case-translation convention None 5) #pragma pack( [ show ] | [ push | pop ] [, identifier ] , n ) 這個是用來指定類、結構的內存對齊的; 這個提及來有點多,你上網查內存對齊能查到; 6) __declspec( dllimport ) declarator __declspec( dllexport ) declarator 這兩個是與Dll有關的,聲明能夠從dll文件中輸出和輸入的函數、類或數據; 7) #pragma once 是規定當編譯時這個文件只能被include一次; 相似 #if define 8) disable message 才疏學淺,沒見過。。。。 9) __int64 是64位的整數類型,是爲了防止32位的int不夠用時用的; 10) #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] ) 是用來定義函數被放置在obj文件的哪一個段裏。 --------------------------------------------------- #if defined MACRO_NAME 與 #ifdef MACRO_NAME 仍是稍微有點區別的。 譬如咱們公司就鼓勵寫第一種,由於它能夠這麼寫: #if defined(MACRO_NAME) && defined(MACRO_NAME) 這個#error是可讓用戶在編譯時手動產生一個編譯錯誤,更準確的說是在預處理的時候。 #if !defined(__cplusplus) #error C++ compiler required. #endif 這個就是說若是你的程序不是C++的,譬如C的,就會報錯。裏面的錯誤信息是用戶本身決定的。 #pragma once基本和前面那個選擇編譯同樣的。 VC中預處理指令與宏定義的妙用 妙用一 剛接觸到MFC編程的人每每會被MFC 嚮導生成的各類宏定義和預處理指令所嚇倒,可是預處理和宏定義又是C語言的一個強大工具。使用它們能夠進行簡單的源代碼控制,版本控制,預警或者完成一些特殊的功能。 一個經典的例子 使用預處理與宏定義最經典的例子莫過於加在一個頭文件中以免頭文件被兩次編譯。試想這種的狀況,有一個文件headerfile.h 它被包含在headerfile1.h中,同時在headerfile2.h 中也被包含了,如今有一個CPP文件,implement.cpp 包含了headerfile1.h 和headerfile2.h: #include 「headerfile1.h」 #include 「headerfile2.h」 假設headerfile.h 中定義了一個全局變量 iglobal 。 int iglobal; 在編譯的時候編譯器兩次編譯headerfile,也就會發現iglobal被定義了兩次,這時就會發生變量重定義的編譯錯誤。 傳統的解決辦法是使用#ifdef 以及#endif 來避免頭文件的重複編譯,在上面的例子中,只須要加上這麼幾行: #ifndef smartnose_2002_6_21_headerfile_h #define smartnose_2002_6_21_headerfile_h int iglobal; #endif 仔細的考慮上面的宏定義,會發現當編譯器編譯過一次headerfile.h之後,smartnose_2002_6_21_headerfile_h 這個宏就被定義了,之後對headerfile.h的編譯都會跳過int iglobal 這一行。固然smartnose_2002_6_21_headerfile_h 這個宏是能夠任意定義的,可是這個宏自己不能和其它文件中定義的宏重複,因此MFC在自動生成的文件中老是使用一個隨機產生的長度很是長的宏,但我以爲這沒有必要,我建議在這個宏中加入一些有意義的信息,比方做者,文件名,文件建立時間等等,由於咱們有時候會忘記在註釋中加入這些信息。 在VC.Net 中咱們不會再看見這些宏定義了,由於在這裏會廣泛使用一個預處理指令: #pragma once 只要在頭文件的最開始加入這條指令就可以保證頭文件被編譯一次,這條指令實際上在VC6中就已經有了,可是考慮到兼容性並無太多的使用它。 源代碼版本控制 當咱們爲許多平臺開發多個版本的時候預編譯指令和宏定義也可以幫咱們的忙。假設咱們如今爲WINDOWS 和LINUX開發了一套軟件,因爲這兩種系統的不一樣,咱們不得不在程序控制源代碼的版本。比方內存的分配,咱們能夠在LINUX上使用標準C的malloc 函數,可是咱們但願在 WINDOWS上使用HeapAlloc API。下面的代碼演示了這種狀況: main() { ……………….. #ifdef _WINDOWS_PLATFORM HeapAlloc(5); #else malloc(5); #endif ……………….. } 當咱們在WINDOWS 平臺上編譯此程序的時候,只須要定義_WINDOWS_PLATFORM這個宏,那麼HeapAlloc這條語句就可以起做用了。這樣就可以讓咱們在同一個文件中爲不一樣的平臺實現不一樣版本的代碼,同時保持程序的良好結構。在許多狀況下,咱們還能夠爲一個方法使用不一樣的算法,而後用宏定義來針對不一樣的狀況選擇其中的一個進行編譯。這在MFC應用程序中是使用得最多的。最明顯的就是文件中常常存在的 #ifdef _DEBUG …………………….some code……….. #endif 這樣的代碼,這些代碼在應用程序的調試版(DEBUG)中會發揮其做用。 #Pragma 指令 在全部的預處理指令中,#Pragma 指令多是最複雜的了,它的做用是設定編譯器的狀態或者是指示編譯器完成一些特定的動做。其格式通常爲 #Pragma Para 其中Para 爲參數,下面來看一些經常使用的參數。 message 參數。 Message 參數是我最喜歡的一個參數,它可以在編譯信息輸出窗口中輸出相應的信息,這對於源代碼信息的控制是很是重要的。其使用方法爲: #Pragma message(「消息文本」) 當編譯器遇到這條指令時就在編譯輸出窗口中將消息文本打印出來。 當咱們在程序中定義了許多宏來控制源代碼版本的時候,咱們本身有可能都會忘記有沒有正確的設置這些宏,此時咱們能夠用這條指令在編譯的時候就進行檢查。假設咱們但願判斷本身有沒有在源代碼的什麼地方定義了_X86這個宏能夠用下面的方法 #ifdef _X86 #Pragma message(「_X86 macro activated!」) #endif 當咱們定義了_X86這個宏之後,應用程序在編譯時就會在編譯輸出窗口裏顯示「_X86 macro activated!」。咱們就不會由於不記得本身定義的一些特定的宏而抓耳撓腮了。 另外一個使用得比較多的pragma參數是code_seg。格式如: #pragma code_seg( ["section-name"[,"section-class"] ] ) 它可以設置程序中函數代碼存放的代碼段,當咱們開發驅動程序的時候就會使用到它。 最後一個比較經常使用的就是上面所說的#pragma once 指令了。 VC預約義的宏 在VC中有一類宏並非由用戶用#define語句定義的,而是編譯器自己就可以識別它們。這些宏的做用也是至關大的。讓咱們來看第一個,也是MFC中使用得最頻繁的一個:__FILE__ 。 當編譯器遇到這個宏時就把它展開成當前被編譯文件的文件名。好了,咱們立刻就能夠想到能夠用它來作什麼,當應用程序發生錯誤時,咱們能夠報告這個錯誤發生的程序代碼在哪一個文件裏,比方在文件test.cpp中有這樣的代碼: try { char * p=new(char[10]); } catch(CException *e ) { TRACE(「 there is an error in file: %s\n」,__FILE__); } 在程序運行的時候,若是內存分配出現了錯誤,那麼在調試窗口中會出現there is an error in file: test.cpp 這句話,固然,咱們還能夠把這個錯誤信息顯示在別的地方。 若是咱們還可以記錄錯誤發生在哪一行就行了,幸運的是,與__FILE__宏定義同樣,還有一個宏記錄了當前代碼所在的行數,這個宏是__LINE__。使用上面的兩個宏,咱們能夠寫出一個相似於VC提供的ASSERT語句。下面是方法 #define MyAssert(x) \ if(!(x)) \ MessageBox(__FILE__,__LINE__,NULL,MB_OK); 咱們在應用程序中能夠象使用ASSERT語句同樣使用它,在錯誤發生時,它會彈出一個對話框,其標題和內容告訴了咱們錯誤發生的文件和代碼行號,方便咱們的調試,這對於不能使用ASSERT語句的項目來講是很是有用的。 除了這兩個宏之外,還有記錄編譯時間的__TIME__,記錄日期的__DATE__,以及記錄文件修改時間的__TIMESTAMP__宏。 使用這些預約義的宏,咱們幾乎能夠生成和VC可以生成的同樣完整的源代碼信息報表。 結論 翻開MFC和Linux的源代碼,宏定義幾乎佔據了半邊天,消息映射,隊列操做,平臺移植,版本管理,甚至內核模塊的拆卸安裝都用宏定義完成。絕不誇張的說,有些文件甚至就只能看見宏定義。因此學習宏定義,熟練的使用宏定義對於學習C語言乃至VC都是很是關鍵的。 妙用二 在上一篇文章中,我演示了幾個經常使用的宏定義和預處理指令,但能夠說這些都是至關常規的技巧。下面要介紹的宏定義與預處理指令的用法也是ATL,MFC以及LINUX中使用得比較多的很是重要的技巧。 ## 鏈接符與# 符 ## 鏈接符號由兩個井號組成,其功能是在帶參數的宏定義中將兩個子串(token)聯接起來,從而造成一個新的子串。但它不能夠是第一個或者最後一個子串。所謂的子串(token)就是指編譯器可以識別的最小語法單元。具體的定義在編譯原理裏有詳盡的解釋,但不知道也無所謂。同時值得注意的是#符是把傳遞過來的參數當成字符串進行替代。下面來看看它們是怎樣工做的。這是MSDN上的一個例子。 假設程序中已經定義了這樣一個帶參數的宏: #define paster( n ) printf( "token" #n " = %d", token##n ) 同時又定義了一個整形變量: int token9 = 9; 如今在主程序中如下面的方式調用這個宏: paster( 9 ); 那麼在編譯時,上面的這句話被擴展爲: printf( "token" "9" " = %d", token9 ); 注意到在這個例子中,paster(9);中的這個」9」被原封不動的當成了一個字符串,與」token」鏈接在了一塊兒,從而成爲了token9。而#n也被」9」所替代。 可想而知,上面程序運行的結果就是在屏幕上打印出token9=9 在ATL的編程中,咱們查看它的源代碼就會常常看見這樣的一段: #define IMPLEMENTS_INTERFACE(Itf) \ {&IID_##Itf, ENTRY_IS_OFFSET,BASE_OFFSET(_ITCls, Itf) }, 咱們常常不假思索的這樣使用它: …… IMPLEMENTS_INTERFACE(ICat) …… 實際上IID_ICat 已經在別的地方由ATL嚮導定義了。當沒有嚮導的時候,你只要遵循把IID_加在你的接口名前面來定義GUID的規則就也可使用這個宏。在實際的開發過程當中可能不多用到這種技巧,可是ATL使用得如此普遍,而其中又出現了很多這樣的源代碼,因此明白它是怎麼一回事也是至關重要的。個人一個朋友就是由於不知道IMPLEMENTS_INTERFACE宏是怎麼定義的,而又不當心改動了IID_ICat的定義而忙活了一成天。 Linux的怪圈 在剛開始閱讀Linux的時候有一個小小的宏讓我百思不得其解: #define wait_event(wq,condition) \ do{ \ if(condition) \ break; \ __wait_event(wq,condition); \ }while(0) 這是一個奇怪的循環,它根本就只會運行一次,爲何不去掉外面的do{..}while結構呢?我曾一度在內心把它叫作「怪圈」。原來這也是很是巧妙的技巧。在工程中可能常常會引發麻煩,而上面的定義可以保證這些麻煩不會出現。下面是解釋: 假設有這樣一個宏定義 #define macro(condition) \ if(condition) dosomething(); 如今在程序中這樣使用這個宏: if(temp) macro(i); else doanotherthing(); 一切看起來很正常,可是仔細想一想。這個宏會展開成: if(temp) if(condition) dosomething(); else doanotherthing(); 這時的else不是與第一個if語句匹配,而是錯誤的與第二個if語句進行了匹配,編譯經過了,可是運行的結果必定是錯誤的。 爲了不這個錯誤,咱們使用do{….}while(0) 把它包裹起來,成爲一個獨立的語法單元,從而不會與上下文發生混淆。同時由於絕大多數的編譯器都可以識別do{…}while(0)這種無用的循環並進行優化,因此使用這種方法也不會致使程序的性能下降。 幾個小小的警告 正如微軟聲稱的同樣,宏定義與預編譯器指令是強大的,可是它又使得程序難以調試。因此在定義宏的時候不要節省你的字符串,必定要力爭完整的描述這個宏的功能。同時在定義宏的時候若有必要(比方使用了if語句)就要使用do{…}while(0)將它封閉起來。在宏定義的時候必定要注意各個宏之間的相互依賴關係,儘可能避免這種依賴關係的存在。下面就有這樣一個例子。 設有一個靜態數組組成的整型隊列,在定義中使用了這樣的方法: int array[]={5, 6, 7, 8}; 咱們還須要在程序中遍歷這個數組。一般的作法是使用一個宏定義 #define ELE_NUM 4 ………………………….. …………………………….. for(int I=0;I<ELE_NUM;I++) { cout<<array[I]; } 因爲某種偶然的緣由,咱們刪除了定義中的一個元素,使它變成: array[]={5,6,7} 而卻忘了修改ELE_NUM的值。那麼在上面的代碼中立刻就會發生訪問異常,程序崩潰。而後是徹夜不眠的調試,最後發現問題出在這個宏定義上。解決這個問題的方法是不使用 array[]={….}這樣的定義,而顯式的申明數組的大小: array[ELE_NUM]={….} 這樣在改動數組定義的時候,咱們就不會不記得去改宏定義了。總之,就是在使用宏定義的時候可以用宏定義的地方通通都用上。 我發現的另外一個有趣的現象是這樣的: 假設如今有一個課程管理系統,學生的人數用宏定義爲: #define STU_NUM 50 而老師的人數剛好也是50人,因而不少人把全部涉及到老師人數的地方統統用上STU_NUM這個宏。另外一個學期過去,學生中的一個被開除了,系統須要改變。怎麼辦呢?簡單的使用#define STU_NUM 49 麼?若是是這樣,一個老師也就被開除了,咱們不得不手工在程序中去找那些STU_NUM宏而後判斷它是不是表示學生的數目,若是是,就把它改爲49。天哪,這個宏定義製造的麻煩比使用它帶來的方便還多。正確的方法應該是爲老師的數目另外定義一個宏: #define TEA_NUM 50 當學生的數目改變之後只要把STU_NUM 定義爲49就完成了系統的更改。因此,當程序中的兩個量之間沒有必然聯繫的時候必定不要用其中的一個宏去替代另外一個,那隻會讓你的程序根本沒法改動。 最後,建議C/C++語言的初學者儘量多的在你的程序中使用宏定義和預編譯指令。多看看MFC,ATL或者LINUX的源代碼,你會發現C語言強大的緣由所在。