1. 程式人生 > >#pragma用法詳解----編譯指示

#pragma用法詳解----編譯指示

來自:https://blog.csdn.net/z215367701/article/details/78359179

 

Pragma-編譯指示

(0)前言

  #Pragma 指令的作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。#pragma 指令對每個編譯器給出了一個方法,在保持與C和C++語言完全相容的情況下,給出主機或作業系統專有的特徵。依據定義,編譯指示是機器或作業系統專有的,且對於每個編譯器都是不同的。   
   其格式一般為:   #Pragma   Para   
  其中Para為引數,下面來看一些常用的引數。  

 

(1) #Pragma 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命令列程式 ,如果code_seg沒有帶引數的話,則函式在OBJ檔案中存放在預設在.text節中。
    push (可選引數) 將一個記錄放到內部編譯器的堆疊中,可選引數可以為一個識別符號或者節名
    pop(可選引數) 將一個記錄從堆疊頂端彈出,該記錄可以為一個識別符號或者節名
    identifier (可選引數) 當使用push指令時,為壓入堆疊的記錄指派的一個識別符號,當該識別符號被刪除的時候和其相關的堆疊中的記錄將被彈出堆疊
    "segment-name" (可選引數) 表示函式存放的節名
例如:
            //預設情況下,函式被存放在.text節中
            void func1() {                  // stored in .text
                              }
            //將函式存放在.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 (比較常用)若用在標頭檔案的最開始處就能夠保證標頭檔案被編譯一次.

 

      一般在整個工程中我們只要包含標頭檔案一次就夠了,若多個.c/.cpp 檔案中都要包含同一個標頭檔案,比如 Windows.h,那很多宣告等等豈不是有兩次了?解決這個問題的傳統的方法是在標頭檔案開始出用 #define 定義一個巨集,比如 Windows.h 中:   
              #ifndef   _WINDOWS_   
              #define   _WINDOWS_   
              #endif
      這樣就可以避免被包含多次。但是這樣的後果是程式碼的可讀性較差 (個人觀點),VC給我們提供了另外一個途徑,那就是在檔案的前面加上:   
               #pragma   once”  


    

(4)#pragma  hdrstop表示預編譯標頭檔案到此為止

 

       後面的標頭檔案不進行預編譯。BCB可以預編譯標頭檔案以加快連結的速度,但如果所有標頭檔案都進行預編譯又可能佔太多磁碟空間,所以使用這個選項排除一些標頭檔案.有時單元之間有依賴關係,比如單元A依賴單元B,所以單元B要先於單元A編譯。你可以用#pragma   startup指定編譯優先順序,如果使用了#pragma   package(smart_init) ,BCB就會根據優先順序的大小先後編譯。  


  

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

 

 

(6) #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( 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:用來將一個庫檔案連結到目標檔案中 
比如我們連線的時候用到了WSock32.lib,你當然可以不辭辛苦地把它加入到你的工程中。但是我覺得更方便的方法是使用#pragma指示符,指定要連線的庫:   
                                    #pragma   comment(lib,   "WSock32.lib")
              linker:將一個連結選項放入目標檔案中,你可以使用這個指令來代替由命令列傳入的或者在開發環境中設定的連結選項,你可以指定/include選項來強制包含某個物件,例如:
                                     #pragma comment(linker, "/include:__mySymbol")
你可以在程式中設定下列連結選項
                                    /DEFAULTLIB
                                    /EXPORT
                                    /INCLUDE
                                    /MERGE
                                    / SECTION
   這些選項在這裡就不一一說明了,詳細資訊請看msdn!
             user:將一般的註釋資訊放入目標檔案中commentstring引數包含註釋的文字資訊,這個註釋記錄將被連結器忽略,例如:
                                   #pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )


 

(8)#pragma data_seg建立一個新的資料段並定義共享資料

 

格式為:
             #pragma data_seg ("shareddata")
             HWND sharedwnd=NULL;//共享資料
              #pragma data_seg()

應用1:在DLL中定義一個共享的,有名字的資料段。
       注意:a、這個資料段中的全域性變數能夠被多個程序共享。否則多個程序之間無法共享DLL中的全域性變數。
       b、共享資料必須初始化,否則微軟編譯器會把沒有初始化的資料放到.BSS段中,從而導致多個程序之間的共享行為失敗。
     假如在一個DLL中這麼寫:
                 #pragma data_seg("MyData")
                 int g_Value; // 全域性變數未初始化
                 #pragma data_seg()
      DLL提供兩個介面函式:
                 int GetValue()
                         {
                          return g_Value;
                          }
                  void SetValue(int n)
                          {
                          g_Value = n;
                          }
          然後啟動兩個都呼叫了這個DLL的程序A和B,假如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

應用2: data_seg控制應用程式的啟動次數
        有的時候我們可能想讓一個應用程式只啟動一次,就像單件模式(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++;

 

 

(9)其他用法

 

    編譯程式可以用#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的實現不同,在一個編譯器中有效在別的編譯器中幾乎無效。可從編譯器的文件中檢視。

以上轉自:http://blog.csdn.net/gueter/article/details/2234072

#pragma once 與 #ifndef

為了避免同一個檔案被include多次

1   #ifndef方式
2   #pragma once方式

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

    #ifndef __SOMEFILE_H__
    #define __SOMEFILE_H__
    ... ... // 一些宣告語句
    #endif

    方式二:

    #pragma once
    ... ... // 一些宣告語句


    #ifndef的方式依賴於巨集名字不能衝突,這不光可以保證同一個檔案不會被包含多次,也能保證內容完全相同的兩個檔案不會被不小心同時包含。當然,缺點就是如果不同標頭檔案的巨集名不小心“撞車”,可能就會導致標頭檔案明明存在,編譯器卻硬說找不到宣告的狀況

    #pragma once則由編譯器提供保證:同一個檔案不會被包含多次。注意這裡所說的“同一個檔案”是指物理上的一個檔案,而不是指內容相同的兩個檔案。帶來的好處是,你不必再費勁想個巨集名了,當然也就不會出現巨集名碰撞引發的奇怪問題。對應的缺點就是如果某個標頭檔案有多份拷貝,本方法不能保證他們不被重複包含。當然,相比巨集名碰撞引發的“找不到宣告”的問題,重複包含更容易被發現並修正。

   方式一由語言支援所以移植性好,方式二 可以避免名字衝突