#pragma once 和 #ifndef ... #define ... #endif 的區別【轉載】

在編寫程序的時候,我們經常要用到#pragma指令來設定編譯器的狀態或者是指示編譯器完成一些特定的動作。

1. #pragma message 指令

message能夠在編譯消息輸出窗口中輸出相應的消息,這對於源代碼信息的控制非常重要的。格式如下:

#pragma message(“消息文本”)

編譯器遇到這條指令時就在編譯輸出窗口中將消息文本打印出來。當我們在程序中定義了許多宏來控制源代碼版本的時候,我們自己有可能都會忘記有沒有正確的設置這些宏,此時我們可以用這條指令在編譯的候進行檢查,假設我們希望判斷自己有沒有源代碼的什麼地方定義了_X86這個宏可以用下面的方法:

#ifdef _x86

#pragma message("_x86 macro activated!")

#endif

當我們定義了_X86這個宏以後,應用程序在編譯時就會在編譯輸出窗口裏顯示"_x86 macro activated!"。

2. #pragma code_seg 指令

格式如下:

#pragma code_seg([[{push | pop},][identifier,]]["segment-name",]["segment-class"])

該指令用來指定函數在.obj文件中存放的節,觀察.obj文件可以使用VC自帶的dumpbin命令行程序,函數在.obj文件中默認的存放節爲.text節。

(1)如果code_seg沒有帶參數的話,則函數存放在.txt節中;

(2)push(可選參數):將一個記錄放到內部編譯器的堆棧中,可選參數(記錄名)可以爲一個標識符或者節名;pop(可選參數)將一個記錄從堆棧頂端彈出,該記錄可以爲一個標識符或者節名;

(3)identifier (可選參數):當使用push指令時,爲壓入堆棧的記錄指派的一個標識符,當該標識符被刪除的時候和其相關的堆棧中的記錄將被彈出堆棧;

(4)"segment-name" (可選參數):表示函數存放的節名;

例如:

//默認情況下,函數被存放在.txt節中

void func1() { // stored in .txt }

 

//將函數存放在.my_data1節中

#pragma code_seg(".my_data1")

void func2() { // stored in my_data1 }

 

//r1爲標識符,將函數放入.my_data2節中

#pragma code_seg(push, r1, ".my_data2)

void func3() { // stored in my_data2 }

 

int main() { }

3. #pragma once 指令

格式如下:

#pragma once

這是一個比較常用的指令,只要在頭文件的最開始加入這條指令就能夠保證頭文件只被被編譯一次。

(1)#pragma once是編譯相關的,就是說這個編譯系統上能用,但在其他編譯系統不一定可以,也就是說移植性差,不過現在基本上已經是每個編譯器都有這個定義了。

(2)#ifndef / #define / #endif是C++語言相關的,它是通過C++語言中的宏定義來避免頭文件被多次編譯的。所以在所有支持C++語言的編譯器上都是有效的,如果寫的程序要跨平臺,最好使用這種方式。

關於#pragma once和#ifndef / #define / #endif的詳細區別參見8.。

4. #pragma hdrstop 指令

格式如下:

#pragma hdrstop

它表示預編譯頭文件到此爲止,後面的頭文件不進行預編譯。

BCB(Borland C++ Builder)可以預編譯頭文件以加快鏈接的速度,但如果所有頭文件都進行預編譯又可能佔太多磁盤空間,所以使用這個選項排除一些頭文件。

有時單元之間有依賴關係,比如單元A依賴單元B,所以單元B要先於單元A編譯。你可以用#pragma startup指定編譯優先級,如果使用了#pragma package(smart_init) ,BCB就會根據優先級的大小先後編譯。

5. #pragma warning 指令

格式如下:

#pragma warning( warning-specifier : warning-number-list [; warning-specifier :  warning-number-list...]

#pragma warning( push[ ,n ] )

#pragma warning( pop )

主要用到的警告表示有如下幾個:

once:只顯示一次(警告/錯誤等)消息;

default:重置編譯器的警告行爲到默認狀態;

1,2,3,4:四個警告級別;

disable:禁止指定的警告信息;

error:將指定的警告信息作爲錯誤報告。

【例示】

#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 ] ) //這裏n代表一個警告等級(1---4)

#pragma warning( pop )

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

在使用標準C++進行編程的時候經常會得到很多的警告信息,而這些警告信息都是不必要的提示,所以我們可以使用#pragma warning(disable:4786)來禁止該類型的警告。

在VC中使用ADO時也會得到不必要的警告信息,這個時候我們可以通過#pragma warning(disable:4146)來消除該類型的警告信息。

6. #pragma comment 指令

格式如下:

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

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

comment-type(註釋類型):可以指定爲五種預定義的標識符的其中一種。

五種預定義的標識符分別如下:

(1)compiler:將編譯器的版本號和名稱放入目標文件中,本條註釋記錄將被編譯器忽略,如果你爲該記錄類型提供了commentstring參數,編譯器將會產生一個警告。

例如:#pragma comment( compiler )

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

(3)lib:這是一個非常常用的關鍵字,用來將一個庫文件鏈接到目標文件中。它可以幫我們連入一個庫文件。

例如:#pragma comment(lib, "user32.lib") //將user32.lib庫文件加入到本工程中

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

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

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

/DEFAULTLIB

/EXPORT

/INCLUDE

/MERGE

/SECTION

(5)user:將一般的註釋信息放入目標文件中,commentstring參數包含註釋的文本信息,這個註釋記錄將被鏈接器忽略。

例如:#pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )

7. #pragma pack 指令

用於控制對齊。

例如:

#pragma pack(push)

#pragma pack(1)

struct s_1

{

char szname[1];

int a;

};

#pragma pack(pop)

struct s_2

{

char szname[1];

int a;

};

printf("s_1 size : %d/n", sizeof(struct s_1));

printf("s_2 size : %d/n", sizeof(struct s_2));

分別得到5和8。

8. #pragma once 和 #ifndef / #define / #endif 的區別

兩者的共同點都是爲了避免同一個文件被include多次,但是各有千秋。

在能夠支持這兩種方式的編譯器上,二者並沒有太大的區別,但是兩者仍然還是有一些細微的區別

方式一:

#ifndef __SOMEFILE_H__

#define __SOMEFILE_H__

... ... // 一些聲明語句

#endif

優點:

#ifndef由語言支持,移植性好。它依賴於宏名字不能衝突,這不光可以保證同一個文件不會被包含多次,也能保證內容完全相同的兩個文件不會被不小心同時包含。

另外,爲了保證不同頭文件中的宏名不衝突,故採取類似於_ABC_H_的取名方式。其中,abc.h爲當前頭文件名。

缺點:

如果不同頭文件的宏名不小心“撞車”,可能就會導致頭文件明明存在,編譯器卻硬說找不到聲明的狀況。

方式二:

#pragma once

... ... // 一些聲明語句

優點:

#pragma once由編譯器提供保證:同一個文件不會被包含多次。注意這裏所說的“同一個文件”是指物理上的一個文件,而不是指內容相同的兩個文件。於是不必再費勁想個宏名了,當然也就可以避免宏的名字衝突問題了。

缺點:

如果某個頭文件有多份拷貝,本方法不能保證他們不被重複包含。

綜上,一般可以這樣處理:

# infndef XX

# define XX

# if _MSC_VER > 1000

# pragma once

# endif

     .

     .

# endif

注意:_MSC_VER是出於版本兼容性考慮,定義Defines the compiler version. Defined as 1200 for Microsoft Visual C++ 6.0. Always defined.

【注】由於#ifndef方式可以通過前面介紹的特殊的宏的取名方式來避免名稱衝突問題,於是其缺點也就不復存在了,進而#ifndef方式就更常用了。


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