天天看點

C語言#pragma預處理

轉載自:http://c.biancheng.net/cpp/html/469.html
           

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

其格式一般為:

   #pragma para

其中para 為參數,下面來看一些常用的參數。

一、#pragma message

message 參數:Message 參數是我最喜歡的一個參數,它能夠在編譯資訊輸出視窗中輸出相應的資訊,這對于源代碼資訊的控制是非常重要的。其使用方法為:

   #pragma message(“消息文本”)

當編譯器遇到這條指令時就在編譯輸出視窗中将消息文本列印出來。

當我們在程式中定義了許多宏來控制源代碼版本的時候,我們自己有可能都會忘記有沒有正确的設定這些宏,此時我們可以用這條指令在編譯的時候就進行檢查。假設我們希望判斷自己有沒有在源代碼的什麼地方定義了_X86 這個宏可以用下面的方法

   #ifdef _X86

   #Pragma message(“_X86 macro activated!”)

   #endif

當我們定義了_X86 這個宏以後,應用程式在編譯時就會在編譯輸出視窗裡顯示“_X86 macro activated!”。我們就不會因為不記得自己定義的一些特定的宏而抓耳撓腮了。

二、#pragma code_seg

另一個使用得比較多的pragma 參數是code_seg。格式如:

   #pragma code_seg( ["section-name"[,"section-class"] ] )

它能夠設定程式中函數代碼存放的代碼段,當我們開發驅動程式的時候就會使用到它。

三、#pragma once

#pragma once (比較常用)

隻要在頭檔案的最開始加入這條指令就能夠保證頭檔案被編譯一次,這條指令實際上在Visual C++6.0 中就已經有了,但是考慮到相容性并沒有太多的使用它。

四、#pragma hdrstop

#pragma hdrstop 表示預編譯頭檔案到此為止,後面的頭檔案不進行預編譯。BCB 可以預編譯頭檔案以加快連結的速度,但如果所有頭檔案都進行預編譯又可能占太多磁盤空間,是以使用這個選項排除一些頭檔案。

有時單元之間有依賴關系,比如單元A 依賴單元B,是以單元B 要先于單元A 編譯。

你可以用#pragma startup 指定編譯優先級,如果使用了#pragma package(smart_init) ,BCB就會根據優先級的大小先後編譯。

五、#pragma resource

#pragma resource "*.dfm"表示把*.dfm 檔案中的資源加入工程。*.dfm 中包括窗體外觀的定義。

六、#pragma warning

   #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)。

七、#pragma comment

#pragma comment(...)

該指令将一個注釋記錄放入一個對象檔案或可執行檔案中。

常用的lib 關鍵字,可以幫我們連入一個庫檔案。比如:

   #pragma comment(lib, "user32.lib")

該指令用來将user32.lib 庫檔案加入到本工程中。

linker:将一個連結選項放入目标檔案中,你可以使用這個指令來代替由指令行傳入的或者在開發環境中設定的連結選項,你可以指定/include 選項來強制包含某個對象,例如:

   #pragma comment(linker, "/include:__mySymbol")

八、#pragma pack

這裡重點讨論記憶體對齊的問題和#pragma pack()的使用方法。

什麼是記憶體對齊?先看下面的結構:

struct TestStruct1

{

   char c1;

   short s;

   char c2;

   int i;

};

假設這個結構的成員在記憶體中是緊湊排列的,假設c1 的位址是0,那麼s 的位址就應該是1,c2 的位址就是3,i 的位址就是4。也就是c1 位址為00000000, s 位址為00000001, c2位址為00000003, i 位址為00000004。

可是,我們在Visual C++6.0 中寫一個簡單的程式:

struct TestStruct1 a;

printf("c1 %p, s %p, c2 %p, i %p\n",

(unsigned int)(void*)&a.c1 - (unsigned int)(void*)&a,

(unsigned int)(void*)&a.s - (unsigned int)(void*)&a,

(unsigned int)(void*)&a.c2 - (unsigned int)(void*)&a,

(unsigned int)(void*)&a.i - (unsigned int)(void*)&a);

運作,輸出:

c1 00000000, s 00000002, c2 00000004, i 00000008。

為什麼會這樣?這就是記憶體對齊而導緻的問題。

1、為什麼會有記憶體對齊?

字,雙字,和四字在自然邊界上不需要在記憶體中對齊。(對字,雙字,和四字來說,自然邊界分别是偶數位址,可以被4 整除的位址,和可以被8 整除的位址。)無論如何,為了提高程式的性能,資料結構(尤其是棧)應該盡可能地在自然邊界上對齊。原因在于,為了通路未對齊的記憶體,處理器需要作兩次記憶體通路;然而,對齊的記憶體通路僅需要一次通路。

一個字或雙字操作數跨越了4 位元組邊界,或者一個四字操作數跨越了8 位元組邊界,被認為是未對齊的,進而需要兩次總線周期來通路記憶體。一個字起始位址是奇數但卻沒有跨越字邊界被認為是對齊的,能夠在一個總線周期中被通路。某些操作雙四字的指令需要記憶體操作數在自然邊界上對齊。如果操作數沒有對齊,這些指令将會産生一個通用保護異常。

雙四字的自然邊界是能夠被16 整除的位址。其他的操作雙四字的指令允許未對齊的通路(不會産生通用保護異常),然而,需要額外的記憶體總線周期來通路記憶體中未對齊的資料。

預設情況下,編譯器預設将結構、棧中的成員資料進行記憶體對齊。是以,上面的程式輸出就變成了:c1 00000000, s 00000002, c2 00000004, i 00000008。編譯器将未對齊的成員向後移,将每一個都成員對齊到自然邊界上,進而也導緻了整個結構的尺寸變大。盡管會犧牲一點空間(成員之間有部分記憶體空閑),但提高了性能。也正是這個原因,我們不可以斷言sizeof(TestStruct1)的結果為8。在這個例子中,sizeof(TestStruct1)的結果為12。

2、如何避免記憶體對齊的影響?

那麼,能不能既達到提高性能的目的,又能節約一點空間呢?有一點小技巧可以使用。比如我們可以将上面的結構改成:

struct TestStruct2

{

   char c1;

   char c2;

   short s;

   int i;

};

這樣一來,每個成員都對齊在其自然邊界上,進而避免了編譯器自動對齊。在這個例子中,sizeof(TestStruct2)的值為8。這個技巧有一個重要的作用,尤其是這個結構作為API的一部分提供給第三方開發使用的時候。第三方開發者可能将編譯器的預設對齊選項改變,進而造成這個結構在你的發行的DLL 中使用某種對齊方式,而在第三方開發者哪裡卻使用另外一種對齊方式。這将會導緻重大問題。

比如,TestStruct1 結構,我們的DLL 使用預設對齊選項,對齊為c1 00000000, s 00000002, c2 00000004, i 00000008,同時sizeof(TestStruct1)的值為12。

而第三方将對齊選項關閉,導緻c1 00000000, s 00000001, c2 00000003, i 00000004,同時sizeof(TestStruct1)的值為8。

除此之外我們還可以利用#pragma pack()來改變編譯器的預設對齊方式(當然一般編譯器也提供了一些改變對齊方式的選項,這裡不讨論)。

使用指令#pragma pack (n),編譯器将按照n 個位元組對齊。

使用指令#pragma pack (),編譯器将取消自定義位元組對齊方式。

在#pragma pack (n)和#pragma pack ()之間的代碼按n 個位元組對齊。但是,成員對齊有一個重要的條件,即每個成員按自己的方式對齊.也就是說雖然指定了按n 位元組對齊,但并不是所有的成員都是以n 位元組對齊。其對齊的規則是,每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數(這裡是n 位元組)中較小的一個對齊,即:min( n, sizeof( item )) 。并且結構的長度必須為所用過的所有對齊參數的整數倍,不夠就補空位元組。看如下例子:

#pragma pack(8)

struct TestStruct4

{

   char a;

   long b;

};

struct TestStruct5

{

   char c;

   TestStruct4 d;

   long long e;

};

#pragma pack()

問題:

A)

sizeof(TestStruct5) = ?

B)

TestStruct5 的c 後面空了幾個位元組接着是d?

TestStruct4 中,成員a 是1 位元組預設按1 位元組對齊,指定對齊參數為8,這兩個值中取1,a

按1 位元組對齊;成員b 是4 個位元組,預設是按4 位元組對齊,這時就按4 位元組對齊,是以sizeof(TestStruct4)應該為8;TestStruct5 中,c 和TestStruct4 中的a 一樣,按1 位元組對齊,而d 是個結構,它是8 個位元組,它

按什麼對齊呢?對于結構來說,它的預設對齊方式就是它的所有成員使用的對齊參數中最大的一個, TestStruct4 的就是4.是以,成員d 就是按4 位元組對齊.成員e 是8 個位元組,它是預設按8位元組對齊,和指定的一樣,是以它對到8 位元組的邊界上,這時,已經使用了12 個位元組了,是以又添加了4 個位元組的空,從第16 個位元組開始放置成員e.這時,長度為24,已經可以被8(成員e 按8位元組對齊)整除.這樣,一共使用了24 個位元組.記憶體布局如下(*表示空閑記憶體,1 表示使用記憶體。機關為1byete):

a b

TestStruct4 的記憶體布局:1***,1111,

c

TestStruct4.a TestStruct4.b d

TestStruct5 的記憶體布局: 1***, 1***, 1111, ****,11111111

這裡有三點很重要:

首先,每個成員分别按自己的方式對齊,并能最小化長度。

其次,複雜類型(如結構)的預設對齊方式是它最長的成員的對齊方式,這樣在成員是複雜類型時,可以最小化長度。

然後,對齊後的長度必須是成員中最大的對齊參數的整數倍,這樣在處理數組時可以保證每一項都邊界對齊。

補充一下,對于數組,比如:char a[3];它的對齊方式和分别寫3 個char 是一樣的.也就是說它還是按1 個位元組對齊.如果寫: typedef char Array3[3];Array3 這種類型的對齊方式還是按1個位元組對齊,而不是按它的長度。

但是不論類型是什麼,對齊的邊界一定是1,2,4,8,16,32,64....中的一個。

另外,注意别的#pragma pack 的其他用法:

#pragma pack(push) //儲存目前對其方式到packing stack

#pragma pack(push,n) 等效于

#pragma pack(push)

#pragma pack(n) //n=1,2,4,8,16 儲存目前對齊方式,設定按n 位元組對齊

#pragma pack(pop) //packing stack 出棧,并将對其方式設定為出棧的對齊方