1. 程式人生 > >C/C++中的預編譯指令(轉)

C/C++中的預編譯指令(轉)

reference:https://blog.csdn.net/sunshinewave/article/details/51020421

  程式的編譯過程可以分為預處理、編譯、彙編三部分,其中預處理是首先執行的過程,預處理過程掃描程式原始碼,對其進行初步的轉換,產生新的原始碼提供給編譯器。
  預處理過程讀入原始碼之後,會檢查程式碼裡包含的預處理指令,完成諸如包含其他原始檔、定義巨集、根據條件決定編譯時是否包含某些程式碼的工作。下面介紹一些C/C++中預編譯的指令。

一 #指令
  預處理指令以#號開頭,並且#號必須是該行除了任何空白字元外的第一個字元。
  #後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字元。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對原始碼做某些轉換。
  單純一個#號表示空指令,沒有任何作用。
二 #include指令


#include預處理指令的作用是在指令處展開被包含的檔案。展開被包含的檔案之後,在程式碼就可以正常地呼叫該檔案中所宣告的變數和函式。#include指令有兩種使用方法
#include <xxx.h>
#include "xxx.h"
  第一種方法將待包含的標頭檔案使用尖括號括起來,預處理程式會在系統預設目錄或者括號內的路徑查詢,通常用於包含系統中自帶的公共標頭檔案。
  第二種方法將待包含的標頭檔案使用雙引號引起來,預處理程式會在程式原始檔所在目錄查詢,如果未找到則去系統預設目錄查詢,通常用於包含程式作者編寫的私有標頭檔案。
檔案包含可以是多重的,也就是說一個被包含的檔案中還可以包含其他檔案。在大型的程式中可能會產生重複包含的問題,如
  #include "a.h"
  #include "b.h"
一個程式包含了a.h和b.h兩個標頭檔案,但a.h和b.h可能同時又都包含了c.h,於是該程式就包含了兩次c.h,這在一些場合下會導致程式的錯誤,可以通過下面的條件編譯進行解決。
三 #define、#undef指令

  #define指令定義了一個識別符號及一個串,識別符號稱為巨集名,源程式中巨集名的每次出現都會用其定義的串進行替換,稱為巨集替換。
  #undef指令取消一個已定義的巨集。
  巨集一般使用大寫字母定義,其可以出現在程式的任意地方。巨集替換僅僅是以文字串代替巨集識別符號的過程,該過程很容易出現一些邏輯上的錯誤,需要仔細處理一些關於括號的問題。
  以下程式碼用巨集定義了一個常量PI,但C++中建議使用const進行常量定義,因為巨集替換並不會進行型別匹配之類的安全性檢查。同時用巨集定義了一個MAX函式,其好處是沒有函式呼叫的額外開銷,執行速度較快,但容易出錯,而且大量的巨集替換會增加程式碼的長度。

void test1()
{
#define PI 3.14
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
cout << PI << endl;	//3.14
cout << MAX(2, 3) << endl; //3
#undef PI
//cout << PI << endl;	//編譯出錯
}

另外還有兩個特殊的運算子:

  巨集定義中的#運算子把跟在其後的引數轉換成一個字串稱為字串化運算子。
  巨集定義中的##運算子把出現在##兩側的引數合併成一個符號。

void test2()
{
#define CAT(n) "ABC"#n
cout << CAT(123) << endl;//ABC123
#define NUM(a,b) a##b
#define STR(a,b) a##b
cout << NUM(1, 2) << endl;//12
cout << STR("Hello", "World") << endl;//HelloWorld
}

四 #if、#elif、#else、#endif指令
  這幾個指令稱為條件編譯指令,可對程式原始碼的各部分有選擇地進行編譯。
  跟一般的if、else if、else語句類似,如果一個條件上的值為真,則編譯它對應的程式碼,否則跳過這些程式碼,測試下一個條件上的值是否為真。注意,作為條件的表示式是在編譯時求值的,它必須僅含常量及已定義過的識別符號,不可使用變數,也不可以含有操作符sizeof(sizeof也是編譯時求值)。
命令#endif標識一個#if塊的結束。

void test3()
{
#define OPTION 2

#if OPTION == 1
cout << "Option: 1" << endl;
#elif OPTION == 2
cout << "Option: 2" << endl; //選擇這句
#else
cout << "Option: Illegal" << endl;
#endif
}

五 #ifdef、#ifndef、#endif指令
  這幾個也是條件編譯指令,其檢查後面指定的巨集是否已經定義,然後根據檢查結果選擇是否要編譯後面語句。其中#ifdef表示”如果有定義“,#ifndef表示”如果沒有定義“。
  這個通常可以用於防止重複包含標頭檔案的問題,如下所示:

#ifndef MYHEAD_H
#define MYHEAD_H
#include "myHead.h"
#endif

六 #line指令
C語言中可以使用__FILE__表示本行語句所在原始檔的檔名,使用__LINE__表示本行語句在原始檔中的位置資訊。#line指令可以重新設定這兩個變數的值,其語法格式為
#line number["filename"]
其中第二個引數檔名是可省略的,並且其指定的行號在實際的下一行語句才會發生作用。

void test4()
{
cout << "Current File: " << __FILE__ << endl; //Current File: d:\test.cpp
cout << "Current Line: " << __LINE__ << endl; //Current Line: 48
#line 1000 "wrongfile"
cout << "Current File: " << __FILE__ << endl; //Current File: d:\wrongfile
cout << "Current Line: " << __LINE__ << endl; //Current Line: 1001
}

七 #error指令
#error指令在編譯時輸出編譯錯誤資訊,可以方便程式設計師檢查出現的錯誤。

void test5()
{
#define OPTION 3
#if OPTION == 1
cout << "Option: 1" << endl;
#elif OPTION == 2
cout << "Option: 2" << endl;
#else
#error ILLEGAL OPTION! //fatal error C1189: #error : ILLEGAL OPTION!
#endif
}

八 #pragma指令
  該指令用來來設定編譯器的狀態或者是指示編譯器完成一些特定的動作,它有許多不同的引數。
1. #pragma once
  在標頭檔案的最開始加入這條指令可以保證標頭檔案只被編譯一次。它可以實現上述使用#ifndef實現不重複包含標頭檔案同樣的功能,但可能會有部分編譯系統不支援。

2. #pragma message
  該指令能夠讓編譯器遇到這條指令時就在編譯輸出視窗中將訊息文字打印出來。其使用方法為:#pragma message(“訊息文字”)
  通過這條指令我們可以方便地記錄是否在原始碼中定義過某個巨集,如

#define ISPC
#ifdef ISPC
#pragma message("Macro ISPC is defined") //編譯輸出:Macro ISPC is defined
#endif

3. #pragma warning
該指令能夠控制編譯器發出警告的方式,其用法舉例如:#pragma warning(disable : 4507 34; once : 4385; error : 164)
這個指令有三部分組成,其中disable部分表示忽略編號為4507和34的警告資訊,once部分表示編號為4385的警告資訊只顯示一次,error部分表示把編號為164的警告資訊當做錯誤。
另外,其還有兩個用法
#pragma warning(push [, n]):儲存所有警告資訊的現有的警告狀態,後面n是可選的,表示把全域性警告等級設為n。
#pragma warning(pop):彈出最後一個警告資訊,取消在入棧和出棧之間所作的一切改動。
具體例如如下:

void test6()
{
#pragma warning(push) //儲存編譯器警告狀態
#pragma warning(disable:4305) //取消4305的警告
 bool a = 5; //無警告資訊
#pragma warning(pop) //恢復之前的警告轉改
bool b = 5; //warning C4305: 'initializing' : truncation from 'int' to 'bool'
}

4. #pragma comment
該指令將一個註釋記錄放入一個物件檔案或可執行檔案中。其使用方法為:#pragma comment(comment-type ,["commentstring"])
其中comment-type是一個預定義的識別符號,指定註釋的型別,應該是compiler,exestr,lib,linker之一。常用的是lib關鍵字,可以幫我們連入一個庫檔案。 如
#pragma comment(lib, "my.lib")
5. #pragma hdrstop
該指令表示預編譯標頭檔案到此為止,後面的標頭檔案不進行預編譯。
6. #pragma resource
該指令表示把指定檔案中的資源加入工程,如
#pragma resource "*.dfm"
7. #pragma code_seg
該指令能夠設定程式中函式程式碼存放的程式碼段,開發驅動程式的時候會使用到。使用方法為:#pragma code_seg(["section-name" [,"section-class"] ])。

8. #pragma data_seg
該指令建立一個新的資料段並定義共享資料。一般用於DLL中,在DLL中定義一個共享的有名字的資料段,這個資料段中的全域性變數可以被多個程序共享,否則多個程序之間無法共享DLL中的全域性變數。其使用方法為:

<pre name="code" class="cpp">#pragma data_seg("MyData")
int value; //共享資料
#pragma data_seg()

9. #pragma pack
該指令規定資料在記憶體中的對齊長度,具體可以參考這裡。

#pragma pack(1)
struct S{char a; int b; };
void test7(){ cout << sizeof(S) << endl; } //5