C前處理器和C庫

而遠之發表於2014-03-30

編譯程式之前,先由前處理器檢查程式。根據程式中使用的前處理器指令,前處理器用符號縮略語所代表的內容替換程式中的縮略語。

1.編譯器的處理步驟:

  • 第一步:翻譯處理:工作內容(1)把原始碼中出現的字元對映到源字符集。

                                             (2)編譯器查詢反斜線後面緊跟換行符的例項並刪除這些例項。將多個物理行替換為一個邏輯行。

                (3)編譯器將文字劃分為預處理的語言符號(token)序列和空白字元及註釋序列(編譯器會將註釋序列替換為一個空格字元。所以註釋序列可以出現在原始碼的任何地方)。

  • 第二步:預處理:   
    • 預處理的格式:ANSI標準允許#符合前後有空格或製表符,還允許在#和指令的其餘部分之間有空格。但是舊版本均不允許這些空格出現。
    • 預處理範圍:預處理指令從#開始,到其後第一個換行符為止。所以預處理指令不允許換行。所以可以使用反斜線和換行符組合使預處理指令跨越多行
    • 系統把巨集的主體定義為語言符號(Token)型別字串。而不是字元型字串。Token是指由空格符隔開的單詞。“2*3”是一個Token,“2  *  3”是三個Token組成的字串。
  • 第三步:把原始碼翻譯為機器語言。第一步和第二步只是對原始碼檔案進行文字處理,進行字串方面的替換。真正的根據語義翻譯為機器語言是在這步。

2.前處理器(preprocessor)種類

前處理器可以使用反斜線和換行符組合可以橫跨多個物理行,但是其最終只佔用一個邏輯行。

第一部分:#define定義常量

  • #define :格式:#define macro body
    • macro是被稱為巨集的縮略語,其是一個根據變數命名規則的命名的單詞。
    • body被稱為實體。可以是任何字串。前處理器將程式中巨集例項替換為實體。
  • #define使用引數:#define macro body 其中marco的格式類似函式.由name(args)組成。但是name和(之間不能有空格。括號內部和body一樣,可以有空格)
    • 注意在body中的引數和body都要用括號括起來。如

      #define ADD(X, Y) X + Y //compiled rightly, but not safe enough #define ADD(X, Y) ((X) + (Y)) //good #define ADD (X, Y) ((X) + (Y)) //error! No spaces between ADD and (X, Y)

  • #運算子:常量字串中和引數相同的字元子串不會被替換為引數的字串形式。如果想在常量字串中使用引數字串,那麼ANSI C允許在body中使用#符號。如果x為引數名,那麼#x就是相應的“x”。
  • ##運算子:#是獲取引數的字串形式,而##是將兩個Token拼接起來得到一個新的Token.

                  如 #define NAME(N)   x   N  中x   N就是空格符隔開的兩個token。但是如果#define NAME(N) x ## N 就是將NAME(N)替換為xN.

    • 總之,#用來拼接字元型字串,##用來拼接語言符號型符號串。
  • 可變巨集:…和__VA_ARGS__
    • 函式巨集中引數列表的最後一個引數為省略號(…)。預定義巨集__VA_ARGS__用在body中代表省略號代表什麼。在巨集擴充中,將__VA_ARGS__替換為…位置實際上的引數。注意,省略號只能替代最後的巨集引數。
  • 總結:
    • 1,巨集的名字中macro名字部分不能有空格,但是在body替代字串中可以使用空格。在引數列表中可以使用空格。
    • 2,在body部分,用圓括號括住每個引數,並括住巨集的整體定義。

第二部分:檔案包含 #include

  • #include <XX.h> 優先搜尋系統目錄。 #include “XX.h”優先搜尋當前目錄
  • 標頭檔案中可以包含:
      • 1.明顯常量,如stdio.h中的EOF,NULL和BUFFSIZE    2.巨集函式  3.函式宣告 
      • 4.結構模板定義。如stdio.h中的FILE結構定義。5.型別定義,使用#define或者typedef。
        #ifndef _FILE_DEFINED
        #define	_FILE_DEFINED
        typedef struct _iobuf
        {
        	char*	_ptr;
        	int	_cnt;
        	char*	_base;
        	int	_flag;
        	int	_file;
        	int	_charbuf;
        	int	_bufsiz;
        	char*	_tmpfname;
        } FILE;
        #endif	/* Not _FILE_DEFINED */
      • 6,如果某個變數要多個檔案共享。則可以在標頭檔案中進行引用宣告。

        extern int status; 	//標頭檔案中
        
        int status;		//原始檔中
      • 7,如果某個const常量需要多個檔案共享。則可採用在標頭檔案中宣告static const int MAX = 100;

      • 8,行內函數,inline function。因為編譯器優化時,必須知道函式定義的內容。所以原始檔呼叫的行內函數的定義,必須在此原始檔中。inline 函式具有內部連結,可以將inline function定義放在標頭檔案中。但是普通函式具有外部連結,則只能有一次定義。

第三部分:其它指令。因為系統變數多用巨集定義。所以它們多用於更改巨集定義。改變編譯時編譯環境,方便移植系統。

  • #undef指令:取消定義一個給定的#define,即使該巨集之前未被定義。可用於重定義某個巨集。

  • 條件編譯指令:#if、#ifdef、#ifndef、#else、#elif、#endif

    • #ifdef、#ifndef後面跟上識別符號

      ,判斷該識別符號是否已經被#define或者是否還未被#define定義。#ifndef多用於防止多次包含同一標頭檔案。#else作用類似if-else語句中的else,選擇不同塊。

    • #if後面跟的是常量整數表示式和邏輯表示式。

      如果表示式非零或者為真,則為真。#if 可以和defined運算子結合。defined(X),如果識別符號X已經定義,返回1,否則返回0.#if defined優點為可以和#elif結合。

    • #if、#ifndef、#ifdef後面必須有對應的#endif。這樣相當於if-else語句中的{},劃分出指令的作用範圍。

  • #line :指令用於重置由__LINE__和__FILE__巨集報告的行號和檔名 。#error:指令使前處理器發出一條錯誤訊息,該訊息包含指令中的文字。#warning:指令是前處理器發出一條警告訊息,該訊息包含指令中的文字。

  • #line 100         //將__LINE__設定為100
    #line 100 "new.c"     //將__LINE__設定為100, 檔名設定為"new.c"
    #line "new.c"        //錯誤,#line引數只能為上述兩種形式
    
    #error This is a example.//編譯器編譯到這句會報錯。
    #warning This is a warning//編譯器編譯到這句,會發出警告

第四部分:重要的C庫

  • 通用工具庫:#include <stdlib.h>

    • 快排函式qsort() 函式原型:void qsort(void *base, size_t nmemb, size_t size, int (*cmpar)(const void *, const void *));

      • ANSI C支援將任何資料型別指標轉換為void型別指標。第一個引數為要排序的陣列頭部的指標,第二個引數為參與排序的專案數量。因為第一個引數為void *,所以第三個引數為資料物件的大小。第三個為排序函式,返回值分別為正,零,負,代表大,相等,小於。

相關文章