#pragma
預處理指示供編譯器實現一些非標準的特性,C標準沒有規定#pragma
後面應該寫什麼以及起什麼作用,由編譯器自己規定。有的編譯器用#pragma
定義一些特殊功能寄存器名,有的編譯器用#pragma
定位連結地址,本書不做深入討論。如果編譯器在代碼中碰到不認識的#pragma
指示則忽略它,例如gcc
的#pragma
指示都是#pragma GCC ...
這種形式,用別的編譯器編譯則忽略這些指示。
C標準規定了幾個特殊的宏,在不同的地方使用可以自動展開成不同的值,常用的有__FILE__
和__LINE__
,__FILE__
展開為當前源檔案的檔案名,是一個字元串,__LINE__
展開為當前代碼行的行號,是一個整數。這兩個宏在原始碼中不同的位置使用會自動取不同的值,顯然不是用#define
能定義得出來的,它們是編譯器內建的特殊的宏。在打印調試信息時打印這兩個宏可以給開發者非常有用的提示,例如在第 6 節 “折半查找”我們看到assert
函數打印的錯誤信息就有__FILE__
和__LINE__
的值。現在我們自己實現這個assert
函數,以理解它的原理。這個實現出自[Standard C Library]:
例 21.3. assert.h的一種實現
/* assert.h standard header */ #undef assert /* remove existing definition */ #ifdef NDEBUG #define assert(test) ((void)0) #else /* NDEBUG not defined */ void _Assert(char *); /* macros */ #define _STR(x) _VAL(x) #define _VAL(x) #x #define assert(test) ((test) ? (void)0 \ : _Assert(__FILE__ ":" _STR(__LINE__) " " #test)) #endif
通過這個例子可以全面複習本章所講的知識。C標準規定assert
應該實現為宏定義而不是一個真正的函數,並且assert(test)
這個表達式的值應該是void
類型的。首先用#undef assert
確保取消前面對assert
的定義,然後分兩種情況:如果定義了NDEBUG
,那麼assert(test)
直接定義成一個void
類型的值,什麼也不做;如果沒有定義NDEBUG
,則要判斷測試條件test
是否成立,如果條件成立就什麼也不做,如果不成立則調用_Assert
函數。假設在main.c
檔案的第33
行調用assert(is_sorted())
,那麼__FILE__
是字元串"main.c"
,__LINE__
是整數33
,#test
是字元串"is_sorted()"
。注意_STR(__LINE__)
的展開過程:首先展開成_VAL(33)
,然後進一步展開成字元串"33"
。這樣,最後_Assert
調用的形式是_Assert("main.c" ":" "33" " " "is_sorted()")
,傳給_Assert
函數的字元串是"main.c:33 is_sorted()"
。_Assert
函數是我們自己定義的,在另一個源檔案中:
/* xassert.c _Assert function */ #include <stdio.h> #include <stdlib.h> void _Assert(char *mesg) { /* print assertion message and abort */ fputs(mesg, stderr); fputs(" -- assertion failed\n", stderr); abort(); }
注意,在標頭檔assert.h
中自己定義的內部使用的標識符都以_
綫開頭,例如_STR
,_VAL
,_Assert
,因為我們在模擬C標準庫的實現,在第 3 節 “變數”講過,以_
綫開頭的標識符通常由編譯器和C語言庫使用,在/usr/include
下的標頭檔中你可以看到大量_
綫開頭的標識符。另外一個問題,為什麼我們不直接在assert
的宏定義中調用fputs
和abort
呢?因為調用這兩個函數需要包含stdio.h
和stdlib.h
,C標準庫的標頭檔應該是相互獨立的,一個程序只要包含assert.h
就應該能使用assert
,而不應該再依賴于別的標頭檔。_Assert
中的fputs
向標准錯誤輸出打印錯誤信息,abort
異常終止當前進程,這些函數以後再詳細討論。
現在測試一下我們的assert
實現,把assert.h
和xassert.c
和測試代碼main.c
放在同一個目錄下。
/* main.c */ #include "assert.h" int main(void) { assert(2>3); return 0; }
注意#include "assert.h"
要用"
引號而不要用<>
括號,以保證包含的是我們自己寫的assert.h
而非C標準庫的標頭檔。然後編譯運行:
$ gcc main.c xassert.c $ ./a.out main.c:6 2>3 -- assertion failed Aborted
在打印調試信息時除了檔案名和行號之外還可以打印出當前函數名,C99引入一個特殊的標識符__func__
支持這一功能。這個標識符應該是一個變數名而不是宏定義,不屬於預處理的範疇,但它的作用和__FILE__
、__LINE__
類似,所以放在一起講。例如:
例 21.4. 特殊標識符__func__
#include <stdio.h> void myfunc(void) { printf("%s\n", __func__); } int main(void) { myfunc(); printf("%s\n", __func__); return 0; }
$ gcc main.c $ ./a.out myfunc main