[轉帖]解析#pragma指令

在所有的預處理指令中,#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/"] ] )
    它能夠設置程序中函數代碼存放的代碼段,當我們開發驅動程序的時候就會使用到它。

在主文件中,用#pragma data_seg建立一

個新的數據段並定義共享數據,其具體格式爲:

#pragma data_seg ("shareddata") //名稱可以

//自己定義,但必須與下面的一致。

HWND sharedwnd=NULL;//共享數據

#pragma data_seg()

 

僅定義一個數據段還不能達到共享數據的目的,還要告訴編譯器該段的屬性,有兩種方法可以實現該目的 (其效果是相同的),一種方法是在.DEF文件中加入如下語句: SETCTIONS shareddata READ WRITE SHARED 另一種方法是在項目設置鏈接選項(Project Setting --〉Link)中加入如下語句: /SECTION:shareddata,rws

第一點:什麼是共享數據段?爲什麼要用共享數據段??它有什麼用途??
在Win16環境中,DLL的全局數據對每個載入它的進程來說都是相同的;而在Win32環境中,情況卻發生了變化,DLL函數中的代碼所創建的任何對象(包括變量)都歸調用它的線程或進程所有。當進程在載入DLL時,操作系統自動把DLL地址映射到該進程的私有空間,也就是進程的虛擬地址空間,而且也複製該DLL的全局數據的一份拷貝到該進程空間。也就是說每個進程所擁有的相同的DLL的全局數據,它們的名稱相同,但其值卻並不一定是相同的,而且是互不干涉的。

因此,在Win32環境下要想在多個進程中共享數據,就必須進行必要的設置。在訪問同一個Dll的各進程之間共享存儲器是通過存儲器映射文件技術實現的。也可以把這些需要共享的數據分離出來,放置在一個獨立的數據段裏,並把該段的屬性設置爲共享。必須給這些變量賦初值,否則編譯器會把沒有賦初始值的變量放在一個叫未被初始化的數據段中。

 

#pragma data_seg預處理指令用於設置共享數據段。例如:

#pragma data_seg("SharedDataName") HHOOK hHook=NULL; //必須在定義的同時進行初始化!!!!#pragma data_seg()

在#pragma data_seg("SharedDataName")和#pragma data_seg()之間的所有變量將被訪問該Dll的所有進程看到和共享。再加上一條指令#pragma comment(linker,"/section:.SharedDataName,rws"),[注意:數據節的名稱is case sensitive]那麼這個數據節中的數據可以在所有DLL的實例之間共享。所有對這些數據的操作都針對同一個實例的,而不是在每個進程的地址空間中都有一份。

 

當進程隱式或顯式調用一個動態庫裏的函數時,系統都要把這個動態庫映射到這個進程的虛擬地址空間裏(以下簡稱"地址空間")。這使得DLL成爲進程的一部分,以這個進程的身份執行,使用這個進程的堆棧。(這項技術又叫code Injection技術,被廣泛地應用在了病毒、黑客領域!呵呵^_^)

 

第二點:在具體使用共享數據段時需要注意的一些問題!

Win32 DLLs are mapped into the address space of the calling process. By default, each process using a DLL has its own instance of all the DLLs global and static variables. (注意: 即使是全局變量和靜態變量也都不是共享的!) If your DLL needs to share data with other instances of it loaded by other applications, you can use either of the following approaches:

· Create named data sections using the data_seg pragma.

· Use memory mapped files. See the Win32 documentation about memory mapped files.

Here is an example of using the data_seg pragma:

#pragma data_seg (".myseg")
int i = 0;
char a[32] = "hello world";
#pragma data_seg()

data_seg can be used to create a new named section (.myseg in this example). The most typical usage is to call the data segment .shared for clarity. You then must specify the correct sharing attributes for this new named data section in your .def file or with the linker option /SECTION:.MYSEC,RWS. (這個編譯參數既可以使用pragma指令來指定,也可以在VC的IDE中指定!)

There are restrictions to consider before using a shared data segment:

· Any variables in a shared data segment must be statically initialized. In the above example, i is initialized to 0 and a is 32 characters initialized to hello world.

· All shared variables are placed in the compiled DLL in the specified data segment. Very large arrays can result in very large DLLs. This is true of all initialized global variables.

· Never store process-specific information in a shared data segment. Most Win32 data structures or values (such as HANDLEs) are really valid only within the context of a single process.

· Each process gets its own address space. It is very important that pointers are never stored in a variable contained in a shared data segment. A pointer might be perfectly valid in one application but not in another.

· It is possible that the DLL itself could get loaded at a different address in the virtual address spaces of each process. It is not safe to have pointers to functions in the DLL or to other shared variables.

應用一:單應用程序。

有的時候我們可能想讓一個應用程序只啓動一次,就像單件模式(singleton)一樣,實現的方法可能有多種,這裏說說用#pragma data_seg來實現的方法,很是簡潔便利。

應用程序的入口文件前面加上

#pragma data_seg("flag_data")
int app_count = 0;
#pragma data_seg()
#pragma comment(linker,"/SECTION:flag_data,RWS")

然後程序啓動的地方加上

 if(app_count>0)    // 如果計數大於0,則退出應用程序。
 {
  //MessageBox(NULL, "已經啓動一個應用程序", "Warning", MB_OK);

  //printf("no%d application", app_count);

  return FALSE;
 }
 app_count++;

(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中包括窗體
外觀的定義。   [Page]


(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:將編譯器的版本號和名稱放入目標文件中,本條註釋記錄將被編譯器忽略
         如果你爲該記錄類型提供了commentstring參數,編譯器將會產生一個警告
例如:#pragma comment( compiler )

exestr:將commentstring參數放入目標文件中,在鏈接的時候這個字符串將被放入到可執行文件中,
       當操作系統加載可執行文件的時候,該參數字符串不會被加載到內存中.但是,該字符串可以被
       dumpbin之類的程序查找出並打印出來,你可以用這個標識符將版本號碼之類的信息嵌入到可
       執行文件中!

lib:這是一個非常常用的關鍵字,用來將一個庫文件鏈接到目標文件中


常用的lib關鍵字,可以幫我們連入一個庫文件。 
例如:
#pragma comment(lib, "user32.lib") 
該指令用來將user32.lib庫文件加入到本工程中


linker:將一個鏈接選項放入目標文件中,你可以使用這個指令來代替由命令行傳入的或者在開發環境中
       設置的鏈接選項,你可以指定/include選項來強制包含某個對象,例如:
       #pragma comment(linker, "/include:__mySymbol")

你可以在程序中設置下列鏈接選項

/DEFAULTLIB
/EXPORT
/INCLUDE
/MERGE
/SECTION
這些選項在這裏就不一一說明了,詳細信息請看msdn!

user:將一般的註釋信息放入目標文件中commentstring參數包含註釋的文本信息,這個註釋記錄將被鏈接器忽略
例如:
#pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )

 

每個編譯程序可以用#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 [Page]
int  insert_record(REC  *r)
{  /*  function  body  */  }
#pragma  warn  +100          //  Turn  the  warning  message  for  warning  #100  back  on
函數會產生一條有唯一特徵碼100的警告信息,如此可暫時終止該警告。

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

補充 —— #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。[Page]
    第一個結構成員之後的每個結構成員都被存儲在更小的成員類型或n 字節界限內。
如果你使用無參量的#pragma  pack, 結構成員被緊湊爲以/Zp 指定的值。該缺省/Zp 緊湊值爲/Zp8 。


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

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

    帶一個pop參量的pack編譯指示的每次出現都會檢索內部編譯器堆棧頂的值,並且使該值爲新的緊湊對齊值。
[NextPage]

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

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


(3) 棧內存對齊

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


 

 

補充:關於#pragma warning
1.       #pragma warning只對當前文件有效(對於.h,對包含它的cpp也是有效的),而不是對整個工程的所有文件有效。當該文件編譯結束,設置也就失去作用。

2.       #pragma warning(push)

存儲當前報警設置。

#pragma warning(push, n)

存儲當前報警設置,並設置報警級別爲n。n

爲從1到4的自然數。

3.       #pragma warning(pop)

恢復之前壓入堆棧的報警設置。在一對push和pop之間作的任何報警相關設置都將失效。

4.       #pragma warning(disable: n)

將某個警報置爲失效

5.       #pragma warning(default: n)

將報警置爲默認

6.       某些警告如C4309是從上到下生效的。即文件內#pragma warning從上到下遍歷,依次生效。

例如:

void func()

{

      #pragma warning(disable: 4189)

      char s;

      s = 128;

      #pragma warning(default: 4189)

      char c;

      c = 128;

}

則s = 128

不會產生C4309報警,而C4309會產生報警。

7.       某些警告例如C4189是以函數中最後出現的#pragma warning設置爲準的,其餘針對該報警的設置都是無效的。

例如:

void func()

{

      #pragma warning(disable: 4189)

      int x = 1;

      #pragma warning(default: 4189)

}

則C4189仍然會出現,因爲default指令是函數的最後一條。在該文件內的其他函數中,如果沒有重新設置,C4189也是以#pragma warning(default: 4189)爲準。如果重新設置,同樣是按照其函數中的最後一個#pragma warning爲準。

8.       某些警告(MSDN認爲是大於等於C4700的警告)是在函數結束後才能生效。

例如:

#pragma warning(disable:4700)

void Func()

{

int x;

int y = x;  

          #pragma warning(default:4700)



           int z= x;

}

則y = x和z = x都不會產生C4700報警。只有在函數結束後的後的另外一個函數中,#pragma warning(default:4700)才能生效。
 

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章