C語言的本質(21)——預處理之三:其它預處理特性及總結
C標準規定了幾個特殊的巨集,在不同的地方使用可以自動展開成不同的值,預編譯程式對於在源程式中出現的這些串將用合適的值進行替換。這些巨集有下面這些:
__FILE__ 展開為當前原始檔的檔名,是一個字串
__LINE__ 展開為當前程式碼行的行號,是一個整數
__DATE__ 展開為包含當前日期的字串
__STDC__ 如果編譯器遵循ANSIC標準,它就是個非零值
__TIME__ 展開為包含當前時間的字串
注意:是雙下劃線,而不是單下劃線。
常用的有__FILE__和__LINE__這兩個巨集在原始碼中不同的位置使用會自動取不同的值,顯然不是用#define能定義得出來的,它們是編譯器內建的特殊的巨集。在列印除錯資訊時列印這兩個巨集可以給開發者非常有用的提示。
#include<stdio.h>
int main(void)
{
printf("HelloWorld!\n");
printf("%s\n",__FILE__);
printf("%d\n",__LINE__);
return 0;
}
下面我們自己實現斷言assert函式,以理解它的原理。
/* assert.h standard header */
#undef assert /* remove existing definition */
#ifdef NDEBUG
#defineassert(test) ((void)0)
#else /*NDEBUG not defined */
void_Assert(char *);
/*macros */
#define_STR(x) _VAL(x)
#define_VAL(x) #x
#defineassert(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:33is_sorted()"。_Assert函式是我們自己定義的,在另一個原始檔中:
/* xassert.c _Assert function */
#include <assert.h>
#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標準庫的實現,以_線開頭的識別符號通常由編譯器和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);
return0;
}
注意#include "assert.h"要用"引號而不要用<>括號,以保證包含的是我們自己寫的assert.h而非C標準庫的標頭檔案。然後編譯執行:
$ gcc main.c xassert.c
$ ./a.out
main.c:6 2>3 -- assertion failed
Aborted
#error指令將使編譯器顯示一條錯誤資訊,然後停止編譯。
#line指令改變_LINE_與_FILE_的內容,它們是在編譯程式中預先定義的識別符號。
#line舉例:
#line 100 //初始化行計數器
#include<stdio.h> //行號100
int main(void)
{
printf("HelloWorld!\n");
printf("%d",__LINE__);
return 0;
}
輸出104
#pragma指令沒有正式的定義。它預處理指示供編譯器實現一些非標準的特性,C標準沒有規定#pragma後面應該寫什麼以及起什麼作用,由編譯器自己規定。典型的用法是禁止或允許某些煩人的警告資訊。有的編譯器用#pragma定義一些特殊功能暫存器名,有的編譯器用#pragma定位連結地址。如果編譯器在程式碼中碰到不認識的#pragma指示則忽略它,例如gcc的#pragma指示都是#pragma GCC ...這種形式,用別的編譯器編譯則忽略這些指示。
預處理指令總結:
預處理指令是以#號開頭的程式碼行。#號必須是該行除了任何空白字元外的第一個字元。#後是指令關鍵字,在關鍵字和#號之間允許存在任意個數的空白字元。整行語句構成了一條預處理指令,該指令將在編譯器進行編譯之前對原始碼做某些轉換。
預處理功能是C語言特有的功能,它是在對源程式正式編譯前由預處理程式完成的。程式設計師在程式中用預處理命令來呼叫這些功能。
巨集定義可以帶有引數,巨集呼叫時是以實參代換形參。而不是“值傳送”。
為了避免巨集代換時發生錯誤,巨集定義中的字串應加括號,字串中出現的形式引數兩邊也應加括號。
檔案包含是預處理的一個重要功能,它可用來把多個原始檔連線成一個原始檔進行編譯,結果將生成一個目標檔案。
條件編譯允許只編譯源程式中滿足條件的程式段,使生成的目標程式較短,從而減少了記憶體的開銷並提高了程式的效率。
使用預處理功能便於程式的修改、閱讀、移植和除錯,也便於實現模組化程式設計。
相關文章
- C語言程式設計——9,預處理命令C語言程式設計
- Go 語言操作 MySQL 之 預處理GoMySql
- 自然語言處理中的語言模型預訓練方法自然語言處理模型
- 如何系統學習C 語言(下)之 預處理命令篇
- 影像預處理
- 用c語言處理檔案C語言
- 資料預處理方法彙總
- 預約直播 | 基於預訓練模型的自然語言處理及EasyNLP演算法框架模型自然語言處理演算法框架
- 自然語言處理中的分詞問題總結自然語言處理分詞
- C語言零基礎教程之預處理和巨集定義篇C語言
- 影像預處理方法
- 資料預處理
- C語言細節 前處理器C語言
- 【C++】 63_C語言異常處理C++C語言
- c語言是如何處理函式呼叫的?C語言函式
- 資料預處理 demo
- PHP中的PDO操作學習(二)預處理語句及事務PHP
- 【scikit-learn基礎】--『預處理』之 缺失值處理
- .net 預處理指令符的使用
- 自然語言處理NLP(四)自然語言處理
- 自然語言處理(NLP)概述自然語言處理
- HanLP 自然語言處理 for nodejsHanLP自然語言處理NodeJS
- Go 語言異常處理Go
- 文字檢測預處理地址
- 特徵工程之特徵預處理特徵工程
- 預處理技術文獻
- nlp 中文資料預處理
- TANet資料預處理流程
- 程式環境和預處理
- split用法與影像預處理
- shell字串處理總結字串
- SPM12之fMRI批次預處理——NII檔案處理
- 《Python自然語言處理實戰》連結表Python自然語言處理
- 自然語言處理的最佳實踐自然語言處理
- 編譯warp,d語言寫的c/c++前處理器.編譯C++
- 語音訊號預處理——數字濾波器音訊
- Python文字預處理:步驟、使用工具及示例Python
- Go語言的 序列處理 和 並行處理 有什麼區別 ?Go並行
- [譯] 自然語言處理真是有趣!自然語言處理