1. 程式人生 > >【轉】#pragma的用法

【轉】#pragma的用法

導出數據 AS val bad 允許 包括 record -name nds

在所有的預處理指令中,#Pragma 指令可能是最復雜的了,它的作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。#pragma指令對每個編譯器給出了一個方法,在保持與C和C++語言完全兼容的情況下,給出主機或操作系統專有的特征。依據定義,編譯指示是機器或操作系統專有的,且對於每個編譯器都是不同的。

其格式一般為: #pragma para。其中para為參數,下面來看一些常用的參數。

1)message 參數

message參數是我最喜歡的一個參數,它能夠在編譯信息輸出窗口中輸出相應的信息,
這對於源代碼信息的控制是非常重要的。其使用方法為:
#pragma message("消息文本")
當編譯器遇到這條指令時就在編譯輸出窗口中將消息文本打印出來。
當我們在程序中定義了許多宏來控制源代碼版本的時候,我們自己有可能都會忘記有沒有正確的設置這些宏,
此時我們可以用這條指令在編譯的時候就進行檢查。假設我們希望判斷自己有沒有在源代碼的什麽地方定義了_X86這個宏,
可以用下面的方法:
#ifdef _X86
#pragma message("_X86 macro activated!")
#endif
我們定義了_X86這個宏以後,應用程序在編譯時就會在編譯輸出窗口裏顯示"_86 macro activated!"。
我們就不會因為不記得自己定義的一些特定的宏而抓耳撓腮了。

(2)另一個使用得比較多的pragma參數是code_seg

格式如:
#pragma code_seg( ["section-name" [, "section-class"] ] )
它能夠設置程序中函數代碼存放的代碼段,當我們開發驅動程序的時候就會使用到它。

(3)#pragma once (比較常用)

只要在頭文件的最開始加入這條指令就能夠保證頭文件被編譯一次,這條指令實際上在VC6中就已經有了,
但是考慮到兼容性並沒有太多的使用它。


(4)#pragma hdrstop

表示預編譯頭文件到此為止,後面的頭文件不進行預編譯。BCB可以預編譯頭文件以加快鏈接的速度,
但如果所有頭文件都進行預編譯又可能占太多磁盤空間,所以使用這個選項排除一些頭文件。
有時單元之間有依賴關系,比如單元A依賴單元B,所以單元B要先於單元A編譯。
你可以用#pragma startup指定編譯優先級,如果使用了#pragma package(smart_init),
BCB就會根據優先級的大小先後編譯。

(5)#pragma resource "*.dfm"

表示把*.dfm文件中的資源加入工程。*.dfm中包括窗體
外觀的定義。


(6)#pragma warning( disable: 4507 34; once: 4385; error: 164 )

等價於:
#pragma warning( disable: 4507 34 ) // 不顯示4507和34號警告信息
#pragma warning( once: 4385 ) // 4385號警告信息僅報告一次
#pragma warning( error: 164 ) // 把164號警告信息作為一個錯誤。

同時這個pragma warning 也支持如下格式:
#pragma warning( push [, n ] )
#pragma warning( pop )
這裏n代表一個警告等級(1---4)。
#pragma warning( push )保存所有警告信息的現有的警告狀態。
#pragma warning( push, n )保存所有警告信息的現有的警告狀態,並且把全局警告等級設定為n。
#pragma warning( pop )向棧中彈出最後一個警告信息,在入棧和出棧之間所作的一切改動取消。例如:
#pragma warning( push )
#pragma warning( disable: 4705 )
#pragma warning( disable: 4706 )
#pragma warning( disable: 4707 )
//.......
#pragma warning( pop )
在這段代碼的最後,重新保存所有的警告信息(包括4705,4706和4707)。

(7)#pragma comment(...)

該指令將一個註釋記錄放入一個對象文件或可執行文件中。

#pragma comment(comment-type,["commentstring"])

comment-type是一個預定義的標識符,指定註釋的類型,應該是compiler,exestr,lib,linker之一。 comment string是一個提供為comment-type提供附加信息的字符串。


常用的lib關鍵字,可以幫我們連入一個庫文件。如:
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "vfw32.lib")
#pragma comment(lib, "wsock32.lib")

註釋類型: 1、compiler: 放置編譯器的版本或者名字到一個對象文件,該選項是被linker忽略的。 2、exestr: 在以後的版本將被取消。 3、lib: 放置一個庫搜索記錄到對象文件中,這個類型應該是和comment string(指定你要Linker搜索的lib的名稱和路徑)這個庫的名字放在Object文件的默認庫搜索記錄的後面,linker搜索這個這個庫就像你在命令行輸入這個命令一樣。你可以在一個源文件中設置多個庫記錄,它們在object文件中的順序和在源文件中的順序一樣。如果默認庫和附加庫的次序是需要區別的,使用Z編譯開關是防止默認庫放到object模塊。 我們經常用到的是#pragma comment(lib,"*.lib")這類的。#pragma comment(lib,"Ws2_32.lib")表示鏈接Ws2_32.lib這個庫。 和在工程設置裏寫上鏈入Ws2_32.lib的效果一樣,不過這種方法寫的 程序別人在使用你的代碼的時候就不用再設置工程settings了。 常用的lib關鍵字,可以幫我們連入一個庫文件。如:
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "vfw32.lib")
#pragma comment(lib, "wsock32.lib") 4、linker: 指定一個連接選項,這樣就不用在命令行輸入或者在開發環境中設置了。 只有下面的linker選項能被傳給Linker.
1 /DEFAULTLIB,/EXPORT,/INCLUDE,/MANIFESTDEPENDENCY,/MERGE,/SECTION
(1) /DEFAULTLIB:library /DEFAULTLIB 選項將一個 library 添加到 LINK 在解析引用時搜索的庫列表。用 /DEFAULTLIB指定的庫在命令行上指定的庫之後和 .obj 文件中指定的默認庫之前被搜索。忽略所有默認庫 (/NODEFAULTLIB) 選項重寫 /DEFAULTLIB:library。如果在兩者中指定了相同的 library 名稱,忽略庫 (/NODEFAULTLIB:library) 選項將重寫 /DEFAULTLIB:library(2)/EXPORT:entryname[,@ordinal[,NONAME]][,DATA] 使用該選項,可以從程序導出函數,以便其他程序可以調用該函數。也可以導出數據。通常在 DLL 中定義導出。entryname是調用程序要使用的函數或數據項的名稱。ordinal 在導出表中指定範圍在 1 至 65,535 的索引;如果沒有指定 ordinal,則 LINK 將分配一個。NONAME關鍵字只將函數導出為序號,沒有 entryname。 DATA 關鍵字指定導出項為數據項。客戶程序中的數據項必須用 extern __declspec(dllimport)來聲明。 有三種導出定義的方法,按照建議的使用順序依次為: 源代碼中的 __declspec(dllexport).def 文件中的 EXPORTS 語句LINK 命令中的 /EXPORT 規範所有這三種方法可以用在同一個程序中。LINK 在生成包含導出的程序時還創建導入庫,除非生成中使用了 .exp 文件。 LINK 使用標識符的修飾形式。編譯器在創建 .obj 文件時修飾標識符。如果 entryname以其未修飾的形式指定給鏈接器(與其在源代碼中一樣),則 LINK 將試圖匹配該名稱。如果無法找到唯一的匹配名稱,則 LINK 發出錯誤信息。當需要將標識符指定給鏈接器時,請使用 Dumpbin 工具獲取該標識符的修飾名形式。 (3)/INCLUDE:symbol /INCLUDE 選項通知鏈接器將指定的符號添加到符號表。 若要指定多個符號,請在符號名稱之間鍵入逗號 (,)、分號 (;) 或空格。在命令行上,對每個符號指定一次 /INCLUDE:symbol。 鏈接器通過將包含符號定義的對象添加到程序來解析 symbol。該功能對於添包含不會鏈接到程序的庫對象非常有用。用該選項指定符號將通過 /OPT:REF 重寫該符號的移除。

每個編譯程序可以用#pragma指令激活或終止該編譯程序支持的一些編譯功能。

例如,對循環優化功能:
#pragma loop_opt(on) // 激活
#pragma loop_opt(off) // 終止

有時,程序中會有些函數會使編譯器發出你熟知而想忽略的警告,
如“Parameter xxx is never used in function xxx”,可以這樣:
#pragma warn —100 // Turn off the warning message for warning #100
int insert_record(REC *r)
{ /* function body */ }
#pragma warn +100 // Turn the warning message for warning #100 back on
函數會產生一條有唯一特征碼100的警告信息,如此可暫時終止該警告。

每個編譯器對#pragma的實現不同,在一個編譯器中有效在別的編譯器中幾乎無效。可從編譯器的文檔中查看。

#pragma disable

  在函數前聲明,只對一個函數有效。該函數調用過程中將不可被中斷。一般在C51中使用較多。

#pragma data_seg

介紹 用#pragma data_seg建立一個新的數據段並定義共享數據,其具體格式為:

1 #pragma data_seg("shareddata")
2 HWNDsharedwnd=NULL;//共享數據
3 #pragma data_seg()
1,#pragma data_seg()一般用於DLL中。也就是說,在DLL中定義一個共享的有名字的數據段。最關鍵的是:這個數據段中的全局變量可以被多個進程共享,否則多個進程之間無法共享DLL中的全局變量。 2,共享數據必須初始化,否則微軟編譯器會把沒有初始化的數據放到.BSS段中,從而導致多個進程之間的共享行為失敗。例如, 技術分享圖片
 1 #pragma data_seg("MyData")
 2 intg_Value;//Notethattheglobalisnotinitialized.
 3 #pragma data_seg()
 4 //DLL提供兩個接口函數:
 5 int GetValue()
 6 {
 7     return g_Value;
 8 }
 9 void SetValue(int n)
10 {
11     g_Value=n;
12 }
技術分享圖片 然後啟動兩個進程A和B,A和B都調用了這個DLL,假如A調用了SetValue(5); B接著調用int m = GetValue(); 那麽m的值不一定是5,而是一個未定義的值。因為DLL中的全局數據對於每一個調用它的進程而言,是私有的,不能共享的。假如你對g_Value進行了初始化,那麽g_Value就一定會被放進MyData段中。換句話說,如果A調用了SetValue(5); B接著調用int m = GetValue(); 那麽m的值就一定是5,這就實現了跨進程之間的數據通信。

#pragma region

#pragma region是Visual C++中特有的預處理指令。它可以讓你折疊特定的代碼塊,從而使界面更加清潔,便於編輯其他代碼。折疊後的代碼塊不會影響編譯。你也可以隨時展開代碼塊以進行編輯等操作。 格式:
1 #pragma region name#pragma endregion comment

使用示例如下:

技術分享圖片
1 #pragma region Variables
2  
3 HWND hWnd;
4  
5 const size_t Max_Length = 20;
6  
7 //other variables
8  
9 #pragma endregion This region contains global variables.
技術分享圖片 如上邊所示,需要折疊的代碼必須包含在#pragma region和#pragma endregion之間。#pragma region和#pragma endregion之後可以添加一些用來註釋的文字。當你折疊代碼塊後,這些文字會顯示在折疊的位置。 折疊代碼塊的方法:如同Visual C++中折疊函數、類、命名空間,當代碼被包含在如上所述的指令之間後,#pragma region這一行的左邊會出現一個“-”號,單擊以折疊內容,同時“-”號會變成“+”號,再次單擊可以展開代碼塊。 此預編譯指令在Visual Studio 2005及以上版本可以使用。但是在Visual Studio 2005中,當#pragma region之後包含類似“1st”這類的文字,會導致“error C2059: syntax error : ‘bad suffix on number‘”的編譯錯誤。避免使用數字或者將數字與字母分離可以解決這個問題。

#pragma pack

許多實際的計算機系統對基本類型數據在內存中存放的位置有限制,它們會要求這些數據的首地址的值是某個數k
(通常它為4或8)的倍數,這就是所謂的內存對齊,而這個k則被稱為該數據類型的對齊模數(alignment modulus)。

Win32平臺下的微軟C編譯器(cl.exe for 80x86)在默認情況下采用如下的對齊規則:
任何基本數據類型T的對齊模數就是T的大小,即sizeof(T)。比如對於double類型(8字節),
就要求該類型數據的地址總是8的倍數,而char類型數據(1字節)則可以從任何一個地址開始。

Linux下的GCC奉行的是另外一套規則(在資料中查得,並未驗證,如錯誤請指正):
任何2字節大小(包括單字節嗎?)的數據類型(比如short)的對齊模數是2,而其它所有超過2字節的數據類型
(比如long,double)都以4為對齊模數。

ANSI C規定一種結構類型的大小是它所有字段的大小以及字段之間或字段尾部的填充區大小之和。
填充區就是為了使結構體字段滿足內存對齊要求而額外分配給結構體的空間。那麽結構體本身有什麽對齊要求嗎?
有的,ANSI C標準規定結構體類型的對齊要求不能比它所有字段中要求最嚴格的那個寬松,可以更嚴格。


如何使用c/c++中的對齊選項

vc6中的編譯選項有 /Zp[1|2|4|8|16] ,/Zp1表示以1字節邊界對齊,相應的,/Zpn表示以n字節邊界對齊。
n字節邊界對齊的意思是說,一個成員的地址必須安排在成員的尺寸的整數倍地址上或者是n的整數倍地址上,取它們中的最小值。
也就是:
min ( sizeof ( member ), n)

實際上,1字節邊界對齊也就表示了結構成員之間沒有空洞。
/Zpn選項是應用於整個工程的,影響所有的參與編譯的結構。
要使用這個選項,可以在vc6中打開工程屬性頁,c/c++頁,選擇Code Generation分類,在Struct member alignment可以選擇。

要專門針對某些結構定義使用對齊選項,可以使用#pragma pack編譯指令:


(1) #pragma pack( [ n ] )

該指令指定結構和聯合成員的緊湊對齊。而一個完整的轉換單元的結構和聯合的緊湊對齊由/Zp 選項設置。
緊湊對齊用pack編譯指示在數據說明層設置。該編譯指示在其出現後的第一個結構或聯合說明處生效。
該編譯指示對定義無效。
當你使用#pragma pack ( n ) 時, 這裏n 為1、2、4、8 或16。
第一個結構成員之後的每個結構成員都被存儲在更小的成員類型或n 字節界限內。
如果你使用無參量的#pragma pack, 結構成員被緊湊為以/Zp 指定的值。該缺省/Zp 緊湊值為/Zp8 。


(2) 編譯器也支持以下增強型語法:
#pragma pack( [ [ { push | pop } , ] [ identifier, ] ] [ n] )

若不同的組件使用pack編譯指示指定不同的緊湊對齊, 這個語法允許你把程序組件組合為一個單獨的轉換單元。
帶push參量的pack編譯指示的每次出現將當前的緊湊對齊存儲到一個內部編譯器堆棧中。
編譯指示的參量表從左到右讀取。如果你使用push, 則當前緊湊值被存儲起來;
如果你給出一個n 的值, 該值將成為新的緊湊值。若你指定一個標識符, 即你選定一個名稱,
則該標識符將和這個新的的緊湊值聯系起來。

帶一個pop參量的pack編譯指示的每次出現都會檢索內部編譯器堆棧頂的值,並且使該值為新的緊湊對齊值。
如果你使用pop參量且內部編譯器堆棧是空的,則緊湊值為命令行給定的值, 並且將產生一個警告信息。
若你使用pop且指定一個n的值, 該值將成為新的緊湊值。若你使用p o p 且指定一個標識符,
所有存儲在堆棧中的值將從棧中刪除, 直到找到一個匹配的標識符, 這個與標識符相關的緊湊值也從棧中移出,
並且這個僅在標識符入棧之前存在的緊湊值成為新的緊湊值。如果未找到匹配的標識符,
將使用命令行設置的緊湊值, 並且將產生一個一級警告。缺省緊湊對齊為8 。

pack編譯指示的新的增強功能讓你編寫頭文件, 確保在遇到該頭文件的前後的
緊湊值是一樣的。

(3) 棧內存對齊

在vc6中棧的對齊方式不受結構成員對齊選項的影響。它總是保持對齊,而且對齊在4字節邊界上。

在網絡協議編程中,經常會處理不同協議的數據報文。一種方法是通過指針偏移的方法來得到各種信息,但這樣做不僅編程復雜,而且一旦協議有變化,程序修改起來也比較麻煩。在了解了編譯器對結構空間的分配原則之後,我們完全可以利用這一特性定義自己的協議結構,通過訪問結構的成員來獲取各種信息。這樣做,不僅簡化了編程,而且即使協議發生變化,我們也只需修改協議結構的定義即可,其它程序無需修改,省時省力。下面以TCP協議首部為例,說明如何定義協議結構。

其協議結構定義如下:

技術分享圖片
 1 #pragma pack(1)//按照1字節方式進行對齊
 2 struct TCPHEADER
 3 {
 4 shortSrcPort;//16位源端口號
 5 shortDstPort;//16位目的端口號
 6 intSerialNo;//32位序列號
 7 intAckNo;//32位確認號
 8 unsignedcharHaderLen:4;//4位首部長度
 9 unsignedcharReserved1:4;//保留16位中的4位
10 unsignedcharReserved2:2;//保留16位中的2位
11 unsignedcharURG:1;
12 unsignedcharACK:1;
13 unsignedcharPSH:1;
14 unsignedcharRST:1;
15 unsignedcharSYN:1;
16 unsignedcharFIN:1;
17 shortWindowSize;//16位窗口大小
18 shortTcpChkSum;//16位TCP檢驗和
19 shortUrgentPointer;//16位緊急指針
20 };
21 #pragm apop()//取消1字節對齊方式
技術分享圖片

#pragma pack規定的對齊長度,實際使用的規則是: 結構,聯合,或者類的數據成員,第一個放在偏移為0的地方,以後每個數據成員的對齊,按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。 但是,當#pragma pack的值等於或超過最長數據成員的長度的時候,這個值的大小將不產生任何效果。 而結構整體的對齊,則按照結構體中最大的數據成員進行。

【來源】

【轉】#pragma的用法