目前,在許多資訊與電子相關科系中,都是以 C 語言做為第一個程式語言,但是,C 語言絕對不是簡單的語言。相反的,C語言當中有很多陷阱,會令初學者百思不得其解。即便是C語言的專家,在使用這個語言時,都要敬畏三分。因為,C語言是以效率為首要考量,而非學習的容易程度。雖然,筆者已經學過 Pascal, C, VB, Foxpro, Java, C#, LISP, Prolog, Ruby, Python, ASP 等語言,但是,C語言仍是我所學習過的語言當中最難學,也是最容易出錯的一個語言。
舉例而言,C語言中的指標經常會造成錯誤百出的狀況,但由於指標是C語言強而有力的武器,在低階輸出入、效能表現、與靈活度上都有很強的用途,因此,指標可以說是一把鋒利的雙面刃,若不砍死敵人就會砍傷自己。
C語言中的指標
在C語言當中,宣告指標的情況很常見,C語言中的各種型態都可以加上星號 (*) 後就成為指標型態,而且,指標可以指向任何的記憶體區塊,舉例而言,下列程式可以將 'a' 字元塞入到記憶體位址 100 當中,這種方式常被用來存取記譯體映射輸出入的暫存器。
char *ptr=(char*) 100;
*ptr = 'a'
函數指標是C語言中相當常用到的功能之一,但是這個主題卻有相當的難度。利用函式指標可以進行反向呼叫 (Call Back Function),舉例而言,在標準函式庫中的 qsort 函數,就使用了 int (*compare)(const void *, const void *) 這樣一個函數指標。
為了效率的考量,C語言的函式庫通常採用靜態的配置方式,盡可能避免動態配置記憶體,因此像 strcpy(), sscanf() 等函數都不會動態配置記憶體,這樣可以讓函數使用者自行決定是否要自行配置方式。但是,當使用者決定以靜態的方式宣告陣列或字串大小,而不採用動態配置時,通常會很難決定陣列或字串所需的空間大小。因此,撰寫 C 語言的程式設計人員對記憶體的掌握度較高,但是也相對必須花費許多程式碼在記憶體管理上。
C語言由於設計上非常注重效率問題,而且與組合語言之間的連接相當良好,因此,很早就成為了系統程式的主流語言。但是,由於C語言的歷史淵遠流長,而且早已被廣泛使用在各個作業系統的撰寫上,這也使得C語言的函式庫相當的不統一。
雖然,ISO組織定義了 C 語言的標準函式庫,但是,這些函式庫有時會不足以應付許多實務上的應用,因此,各家廠商經常會自行發展其C語言函式庫,舉例而言,微軟平台的C語言函式庫與GNU的C語言函式庫就相當不同。更嚴重的是,由於各家作業系統幾乎都是以 C/C++所開發的,因此,各自擁有不同的系統函式庫,這對C語言的開發人員而言,造成了一些困擾,因為,要學習作業系統上的程式設計,就必須分別學習各個作業系統不同的函式庫。
ISO組織定義的C語言函式庫,是以函數為主的定義方式,但是,卻缺乏標準的資料結構,像是 HashTable, Binary Tree 等,這使得C語言的開發者常常被迫要重寫這些結構的相關函數,還好,在開放原始碼領域,GNU已經為我們製作了一些廣泛被使用的函式庫,稱為 GNU C library (glibc) 。
除了包含 ISO C 函式庫之外,glibc 還包含 POSIX (Portable Operating System Interface for Unix) 所定義的函數,POSIX 是 UNIX作業系統家族的標準介面,UNIX, Linux, Cygwin 等環境都支援 POSIX 標準,因此,glibc 的函數大部分都可以在這些平台上執行。
除此之外,glibc 還包含了一些其他平台中常見的好用函數,像是 Berkley Unix (BSD and SunOS) 的函數,UNIX System V Interface Description (SVID) 的函數,與 The X/Open Portability Guide 中的函數等等。表格 7.5顯示了 glibc 的標頭檔列表,僅供讀者參考。
表格 7.5 glibc函式庫的標頭檔分類表
ISO標準 |
說明 POSIX |
標準 |
說明 |
其他檔頭 |
說明 |
assert.h |
錯誤偵測 |
cpio.h |
壓縮格式 |
argz.h |
參數取得 |
complex.h |
數學複數 |
dirent.h |
目錄操作 |
envz.h |
參數取得 |
ctype.h |
字元處理 |
fcntl.h |
檔案操作 |
execinfo.h |
除錯堆疊 |
errno.h |
錯誤處理 |
grp.h |
群組管理 |
fnmatch.h |
字串比對 |
fenv.h |
浮點環境 |
pwd.h |
帳號密碼 |
gconv.h |
國際轉換 |
float.h |
浮點數 |
sys/ipc.h |
行程通訊 |
langinfo.h |
格式轉換 |
inttypes.h |
整數轉換 |
sys/msg.h |
訊息佇列 |
mcheck.h |
記憶體檢查 |
iso646.h |
運算詞彙 |
sys/sem.h |
號誌 |
ulimit.h |
資源限制 |
limits.h |
數值範圍 |
sys/stat.h |
檔案資訊 |
utmp.h |
使用者帳號 |
locale.h |
國際化 |
sys/time.h |
日期時間 |
obstack.h |
物件堆疊 |
math.h |
數學函數 |
sys/types.h |
型態定義 |
printf.h |
參數剖析 |
setjmp.h |
遠程跳躍 |
sys/utsname.h |
平台型號 |
regex.h |
正規表示式 |
signal.h |
引發錯誤 |
sys/wait.h |
行程等待 |
search.h |
排序搜尋 |
stdarg.h |
變動參數 |
tar.h |
壓縮格式 |
sys/param.h |
系統參數 |
stdbool.h |
布林型態 |
termios.h |
終端機 |
sys/resource.h |
系統資源 |
stddef.h |
通用定義 |
unistd.h |
UNIX |
sys/stat.h |
系統設定 |
stdint.h |
整數型態 |
utime.h |
檔案時間 |
sys/time.h |
系統時間 |
stdio.h |
輸出入 |
|
|
sys/types.h |
系統型態 |
stdlib.h |
一般函數 |
|
|
sys/utsname.h |
系統名稱 |
string.h |
字串處理 |
|
|
sys/vlimit.h |
系統限制 |
tgmath.h |
數學函數 |
|
|
sys/vtimes.h |
系統時間 |
time.h |
日期時間 |
|
|
sys/wait.h |
行程等待 |
wchar.h |
Unicode |
|
|
sys/socket.h |
網路函數 |
wctype.h |
Unicode |
|
|
netdb.h |
網路資料 |
|
|
|
|
netinet/in.h |
網路輸入 |
|
|
|
|
sys/un.h |
網址 |
在glibc當中,補充了標準函式庫的一些不足之處,這包含二元樹 (Binary Tree)、赫序函數 (HashTable)、Unicode 字元處理函數、正規表示式、網路函式庫(Socket)、以及許多作業系統的相關函式庫等等,在本節中,我們將介紹glibc函式庫的一些常見用法,這些背景知識對於我們實作系統軟體時會有所幫助。
glibc 的二元樹與HashTable之使用方式
檔案:search.c // 說明
#include <search.h> // 引用檔案 search.h
#include <stdlib.h>
#include <stdio.h>
char *names[] = { "George", "Mary", // 宣告名單
"Bob", "Snoopy", "Mickey", "John", "Mike" };
typedef struct { // 宣告結構 person
char *name;
int id;
} person;
#define person_print(p) \ // 定義輸出函數
printf("id=%d name=%s\n", p->id, p->name)
int compare(const void *pa, const void *pb) { // 定義比較函數
person *p1=(person*)pa, *p2=(person*)pb;
return strcmp(p1->name, p2->name);
}
void action(const void *nodep, const VISIT which, // 定義節點訪問動作
const int depth) {
person *p;
switch(which) {
case preorder:
break;
case postorder:
p = *(person**)nodep;
person_print(p);
break;
case endorder:
break;
case leaf:
p = *(person**)nodep;
person_print(p);
break;
}
}
void binaryTreeTest() { // 二元樹測試程式
void *v, *root=NULL; // 宣告根節點
int i;
for (i = 0; i < 7; i++) { // 將名單加入樹中
person *p = (person*) malloc(sizeof(person)); // 分配空間
p->name = strdup(names[i]); // 複製名稱
p->id = i; // 設定編號
v = tsearch((void *)p, &root, compare); // 新增p記錄
}
twalk(root, action); // 列印整顆樹
}
void hashTableTest() { // HashTable 測試程式
void *v;
int i;
ENTRY e, *ep;
hcreate(30); // 建立 hashTable
for (i = 0; i < 5; i++) { // 將名單放入表格
person *p = (person*) malloc(sizeof(person)); // 分配空間
p->name = strdup(names[i]); // 複製名稱
p->id = i; // 設定編號
e.key = p->name; // 設定e=(key, data)
e.data = p;
v = hsearch(e, ENTER); // 新增e記錄
}
for (i=0; i<7; i++) { // 從表格中取出名單
e.key = names[i]; // 設定搜尋的 key
ep = hsearch(e, FIND); // 開始搜尋
if (ep != NULL) { // 如果有找到
person *p = ep->data; // 取得 person
person_print(p); // 列印 person
} else // 否則
printf("%s not found !\n", e.key); // 印出找不到
}
hdestroy(); // 刪除 HashTable
}
int main() { // 主程式
printf("=====binaryTreeTest()=======\n");
binaryTreeTest(); // 測試二元樹
printf("=====hashTableTest()=======\n");
hashTableTest(); // 測試 HashTable
return 0;
}
執行過程與結果:
$ gcc search.c -o search
$ ./search
=====binaryTreeTest()=======
id=2 name=Bob
id=0 name=George
id=5 name=John
id=1 name=Mary
id=4 name=Mickey
id=6 name=Mike
id=3 name=Snoopy
=====hashTableTest()=======
id=0 name=George
id=1 name=Mary
id=2 name=Bob
id=3 name=Snoopy
id=4 name=Mickey
John not found !
Mike not found !
Facebook
Post preview:
Close preview