C語言#line、#error和#pragma命令,以及_pragma運算子

2020-07-16 10:04:27
本文將介紹幾種預處理命令及其使用方法,其中包括 #line 命令、#error 命令和 #pragma 命令。此外,還講述了_Pragma 運算子的相關知識。

定義行號

編譯器會在警告訊息、錯誤訊息與偵錯資訊中包含程式碼所在的行號與所在的原始檔名,並提供給偵錯工具。你可以在原始碼中利用 #line 命令改變編譯器預設指定的檔名與行號資訊。#line 命令的語法如下:
#line line_number ["filename"]

#line 命令的下一行行號會指定為 line_number。如果該命令也包含可選字串字面量"filename",那麼該編譯器會把該字串名稱作為當前原始檔名。

line_number 必須是大於 0 的十進位制常數。如下例所示:
#line 1200 "primary.c"

包含 #line 命令的那一行程式碼,也可以包含其他宏。如果包含其他宏,前處理器會先展開所有宏,然後再執行 #line 命令。但要確保在宏展開後,#line 命令是正確的。

程式可以利用標準預定義宏 __LINE__ 和 __FILE__ 來存取當前的行號和檔名設定
printf( "This message was printed by line %d in the file %s.n",
        __LINE__, __FILE__ );

#line 命令通常用在將 C 原始碼作為輸出的程式上。通過將對應的輸入檔案行號放置在 #line 命令內,程式可以讓 C 編譯器的錯誤訊息指向原始檔中相應的行。

生成錯誤訊息

無論是否有實際錯誤,#error 命令都會讓前處理器發出錯誤訊息。它的語法如下:
#error [text]

如果上述命令存在可選項 text,則 text 就會被包含在前處理器的錯誤訊息中。然後,編譯器會終止處理原始碼,並結束執行,彷彿遇到了嚴重錯誤。text可以是任意前處理器記號序列。如果 text 中有其他宏,它們都不會被展開。最好在這裡使用字串字面量,以避免標點符號字元(如單引號)的影響。

下面的例子測試標準宏 __STDC__ 是否已經被定義。如果沒有,則生成一個錯誤訊息:
#ifndef __STDC__
  #error "This compiler does not conform to the ANSI C standard."
#endif

#pragma 命令

#pragma 命令是向編譯器提供額外資訊的標準方法,其格式如下:
#pragma [tokens]

如果 #pragma 之後的第一個標記(token)是 STDC,那麼該命令就是一個標準 pragma。否則,該 #pragma 命令的作用取決於實現版本。為了保障程式碼的可移植性,應該盡量少使用 #pragma 命令。

如果前處理器支援所指定的標記,就會執行這些標記所代表的動作,或者把該資訊傳遞給編譯器。如果前處理器不支援所指定的標記,就忽略該 #pragma 命令。

例如,最新版本的 GNU C 編譯器和微軟 Visual C 編譯器都支援 #pragma pack(n),它使得編譯器讓結構成員對齊到特定的位元組邊界。下面的例子使用 pack(1)指示每個結構成員必須對齊到位元組邊界:
#if defined( __GNUC__ ) || defined( _MSC_VER )
  #pragma pack(1)                             // 對齊位元組,沒有填充
#endif

單位元組對齊方式可以確保結構成員之間不會有間隙。pack 的引數 n 通常是 2 的冪(但冪值較小)。例如,pack(2)把結構成員對齊到偶數地址,而 pack(4)把結構成員對齊到 4 為倍數的地址。pack()沒有引數,它指示對齊方式設定為實現版本的預設值。

C99 新增下面三個標準的 pragma:
#pragma STDC FP_CONTRACT on_off_switch
#pragma STDC FENV_ACCESS on_off_switch
#pragma STDC CX_LIMITED_RANGE on_off_switch
on_off_switch 的值必須是 ON、OFF 或 DEFAULT。

_Pragma 運算子

無法通過宏展開建立一個 #pragma 命令(或任何其他命令)。當碰到需要這麼做的情況時,C99 新增了一個預處理運算子 _Pragma,它可以與宏配合使用。它的語法如下:
_Pragma (string_literal )

_Pragma 運算子的工作方式是這樣的。首先,該 string_literal 運算元會被“解字串化”(destringized),或被轉換為前處理器記號序列,該過程如下:刪除字串前後的雙引號;使用"替代 ";使用 替代 。然後,前處理器會翻譯前述結果序列的記號,類似於 #pragma 命令。

下面這一行程式碼定義了一個名為 STR 的宏,藉此可以使用 _Pragma 運算子重寫 #pragma 命令:
#define STR(s)   #s             // 這個#是“字串化”運算子

有了上述定義,下面兩行程式碼是等同的:
#pragma tokens
_Pragma ( STR(tokens) )

下面的例子是在宏中使用 _Pragma 運算子:
#define ALIGNMENT(n) _Pragma( STR(pack(n)) )
ALIGNMENT(2)

宏替換會把呼叫宏 ALIGNMENT(2)的過程重寫為如下形式:
_Pragma( "pack(2)" )

前處理器接著處理這行程式碼,實現方式與使用下面的命令一樣:
#pragma pack(2)