C語言中文網 目錄
首頁 > 編程筆記 > C語言筆記 閱讀:2,415

C語言標準頭的使用

每個標準庫函數都會被聲明在一個或多個標準頭(standard header)中。這些標準頭也包括了 C 語言標準提供的所有宏和類型的定義。

每個標準頭都包含一組相關的函數聲明、宏和類型定義。例如,數學函數聲明在頭文件 math.h 中。標準頭也稱之為頭文件(header file),因為每個標準頭內容通常被存儲在一個文件中。然而,嚴格來說,C 語言標準并沒有強制要求將標準頭組織成文件。

C 標準定義了以下 29 個頭文件(其中有星號標識的是 C11 新增的):

assert.h inttypes.h signal.h stdint.h
threads.h* complex.h iso646.h stdalign.h*
stdio.h time.h ctype.h limits.h
stdarg.h stdlib.h uchar.h* errno.h
locale.h stdatomic.h* stdnoreturn.h* wchar.h
fenv.h math.h stdbool.h string.h
wctype.h float.h setjmp.h  stddef.h
gmath.h      

頭文件 complex.h、stdatomic.h 和 threads.h 是可選的。有一些 C11 實現版本可以定義一些標準宏,以指示該版本不包括前述可選的頭文件。這里,如果宏 __STDC_NO_COMPLEX__、宏 __STDC_NO_ATOMICS__,或宏 __STDC_NO_THREADS__ 被定義為值 1,則該實現版本就沒有包括這些宏所對應的可選頭文件。

通過 #include 命令,可以把一個標準頭文件內容插入到一個源代碼文件中。其中 #include 命令必須放在所有函數的外面。你可以以任意順序包含多個標準頭文件。然而,在針對某個頭文件使用 #include 命令之前,程序不可以定義任何與該頭文件內標識符相同的宏名稱。為了確保程序符合該條件,總是在源代碼開始的地方,首先包含所需的標準頭文件,然后再包含自己定義的頭文件。

運行環境

C 程序只會在兩種運行環境中執行:宿主(hosted)環境或獨立(freestanding)環境。大多數程序在宿主環境中執行:也就是說,在操作系統的控制和支持之下執行。在宿主環境中,可以使用標準庫的全部功能。而且,為宿主環境編譯的程序必須定義 main()函數,它是程序啟動后第一個執行的函數。

為獨立環境設計的程序,執行時不會獲得操作系統的支持。在獨立環境中,由所使用的實現版本自身決定程序啟動后第一個執行的函數是什么。獨立環境的程序無法使用復數浮點類型,并且只能使用下面的頭文件:

float.h stdalign.h stddef.h
iso646.h stdarg.h stdint.h
limits.h stdbool.h stdnoreturn.h

特定的實現版本可能還提供另外的標準庫資源。

函數和宏的調用

所有的標準函數都有外部鏈接。因此,你可以通過在自己的程序中聲明標準庫函數來使用它們,而不需要包含對應的頭文件。然而,如果標準函數需要頭文件中定義的某個類型,那么必須包含對應的頭文件。

標準庫的函數不一定確保可重入(reentrant)。也就是說,在一個進程中兩次調用同一個函數并行執行可能是不安全的行為。之所以制定該規則,其中一個原因是:部分標準函數會使用和修改同一個靜態變量或線程變量。

因此,你不能在信號處理進程(signal handling rountine)中調用標準庫函數。信號是異步的,也就是說,程序可能在任何時候收到信號,甚至是正在執行標準庫函數時。當發生這種情況時,如果信號處理器再調用的標準函數與正在執行的是同一個,那么該函數則必須是可重入的。由各個實現版本自行決定哪些函數可重入,或者是否需要對所有標準庫提供可重入版本。

大部分標準庫函數(除了一些顯式指定的函數)都是線程安全的(thread-safe),意思是它們可以“同時”被幾個線程安全地執行。換句話說,標準函數必須這樣實現:當多個線程調用它們時,所有它們使用的內部對象都不會造成數據競爭。尤其是它們在不能確保同步性的前提下,不得使用靜態對象。然而,作為程序員,需要協調好不同線程對函數參數直接或間接引用對象的訪問。

在執行操作前,每個流都具有一個相應的鎖,I/O 鏈接庫中的函數使用該鎖以獲得對這個流的獨占訪問權限。在這種方式下,當幾個線程訪問同一個給定的流時,標準庫函數防止了數據競爭。

程序員要保障調用函數和類函數宏時,傳入有效的參數。錯誤的參數會造成嚴重的運行錯誤。需要避免的典型錯誤包括:

(1) 參數值超出函數的值域,如下例所示:
double x = -1.0, y = sqrt(x)

(2) 指針參數沒有指向一個對象或函數,相當于使用一個未經初始化的指針參數進行函數調用,如下例所示:
char *msg;  strcpy( msg, "eror" );

(3) 參數類型不符合可選參數函數的類型要求。在下面的示例中,轉換修飾符%f調用時需要一個浮點型指針參數,但 &x 是一個 double 指針:
double x;   canf( "%f", &x );

(4) 數組地址參數所指向的數組不夠大,不足以容納該函數所要寫入的數據。如下例所示:
char name[] = "Hi "; strcat( name, "Alice" );

標準庫中的宏充分利用了括號,所以可以像使用一般標識符一樣在表達式中使用這些標準宏。而且,標準庫中每個類函數宏都只會使用其參數一次。這意味著,可以像調用普通函數一樣調用這些宏,即便把具有副作用的表達式作為這些類函數宏的參數也可以。如下例所示:
int c = 'A';
while ( c <= 'Z' ) putchar( c++ );           // 輸出:'ABC… XYZ'

標準庫函數可能同時以宏和函數方式實現。如果這樣的話,對于一個給定的函數名,同一個頭文件中會包含一個函數原型和一個宏定義。因此,在包含該頭文件之后,每次使用該函數名都會調用宏。下面的例子調用宏或函數 toupper()把小寫字母轉換為大寫字母:
#include <ctype.h>
/* ... */
  c = toupper(c);                       // 調用宏toupper(),如果存在的話

然而,如果指定需要調用一個函數,而不是同名的宏,可以利用 #undef 命令取消宏定義:
#include <ctype.h>
#undef toupper                  // 移除任何同名的宏定義
/* ... */
  c = toupper(c)                        // 調用函數toupper()

把名稱放在括號內,也可以調用函數而非宏:
#include <ctype.h>
/* ... */
  c = (toupper)(c)                      // 調用函數toupper()

最后的一個做法,你可以忽略包含宏定義的頭文件,直接在源代碼文件中明確聲明該函數:
extern int toupper(int);
/* ... */
  c = toupper(c)                        // 調用函數toupper()

保留的標識符

在程序中選擇標識符使用時應特別注意,必須知道哪些標識符被保留給標準庫使用。保留的標識符包括:

(1) 所有以下劃線開始后面接著第二個下劃線或者大寫字母的標識符,均被保留。因此不能使用諸如 _x 或 _Max 形式的標識符,甚至不能作為局部變量或標簽。

(2) 以下劃線開始,但不符合上一點的所有其他標識符,都被保留為文件。因此,不能使用諸如 _a_ 形式標識符作為函數名或全局變量名,但是可以作為參數、局部變量和標簽名稱。結構成員和聯合成員也可以使用下劃線開頭的名稱作為標識符,但第二個字符不可以是下劃線或大寫字母。

(3) 在標準頭文件中被聲明為外部鏈接的標識符,被保留為外部鏈接標識符。這類標識符包括函數名以及全局變量名,例如 errno。雖然無法把這些外部鏈接標識符聲明為自己的函數或對象名稱,但是可以用于其他目的。例如,在一個沒有包含 string.h 的源文件內,可以定義一個名為 strcpy()的靜態函數。

(4) 在所包含的頭文件中定義的所有宏標識符,都是被保留的。

(5) 在標準頭文件中被聲明為文件的標識符,在它們自身命名空間范圍內,是被保留的。一旦在源文件中包含一個頭文件,在同一個命名空間中,不能將在該頭文件中聲明為文件的標識符用作其他目的,或作為宏名稱。

雖然這里所列出的一些條件有“漏洞”,允許在某些命名空間或配合靜態鏈接重復使用某些標識符,但是標識符過多重用很容易造成混淆,通常最安全的方式是完全規避標準頭文件中的標識符。

在下面的各節中,為了未來 C 標準的擴充,我們也會列出一些被保留的標識符。前面列表中的最后三點也適用于這些保留的標識符。

精美而實用的網站,提供C語言C++STLLinuxShellJavaGo語言等教程,以及socketGCCviSwing設計模式JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo