1. 程式人生 > >《UNIX環境高階程式設計》原始碼編譯方法

《UNIX環境高階程式設計》原始碼編譯方法

 最近在學習《UNIX環境高階程式設計》(Advanced Programming in the UNIX Environment, 簡稱APUE,以下使用簡稱)。該書的作者是W.Richard.Stevens,國際知名的UNIX和網路專家。我看的是該書的第一版,尤晉元翻譯的。網上有的評論該書翻譯的比較差勁,有的說還行。我個人覺得,這本書的翻譯還可以。並且,如果本身就有一定的UNIX/Linux基礎的話,讀起來應該不是很吃力。同時我也下了該書的第二版(英文版),有什麼看不懂的地方,再對照英文版看一下,輔助理解。
   這裡要談到的一個問題就是該書中的原始碼編譯的問題。此書中差不多每個歷程中,都會有這樣一行原始碼:

#include "ourhdr.h"

在第二版中改為:
#include "apue.h"

    這個標頭檔案是作者把把每個例程中常用的標準標頭檔案,一些常用的出錯處理函式(err_**()之類的函式)和一些常用的巨集定義給整理在一個頭檔案中。這個可以省去在每個例程中錄入較多的重複程式碼,這樣可以減少每個例程的長度。但是,這樣就給讀者帶來了不少麻煩。因為我們還要去搞明白如和把這個標頭檔案編譯,然後做成庫檔案,新增到我們的系統中。特別讀於初學者,本來滿懷信心的,結果在編譯第一個程式的時候就出現了問題。我也沒有搞明白如何把 "ourhdr.h"靜態的編譯到系統中。

    不過,不明白如何使用"ourhdr.h"這個標頭檔案,並不會影響我們學習APUE,也不會影響我們編譯和執行每一個例程。其實,簡單的想一下,如果一個 C程式要能順利的編譯和執行,除了我們要語法正確等方面外,最根本的是要保證我們程式中所呼叫的函式以及巨集等等都要有完整的來源,也就是必須包含所有呼叫函式和巨集所在的標頭檔案。對於一個具體的源程式,如果我們正確的包含了標頭檔案,那麼剩下的就是程式本生語法方面應該注意的事項。


    如何確定系統呼叫函式包含在那個標頭檔案中呢?這在Unix/Linux系統下並非一件難事。Unix/Linux下命令man可以幫助我們找到。man命令不僅可以幫助我們查詢一般命令的用法,同時提供不同層次的幫助諸如系統呼叫或者管理員級別的命令等等(譬如FreeBSD6.1中,man 1是使用者專用手冊,man 2是系統呼叫,man 3是庫函式查詢等等)。

    下面我們就以APUE書中程式1-1 (實現ls命令部分功能)為例,來說明如何將書中的程式改編成全部使用標準標頭檔案的程式。其中,作業系統用的是FreeBSD6.1,經過相應的修改可以在書中所說的幾個Unix系統及Linux系統中執行,我也曾在Debian Linux下成功編譯和執行該程式。書中1-1.c的原始程式碼如下:


#include <sys/types.h>
#include <dirent.h>
#include "ourhdr.h"

int
main(int argc, char *argv[])
{
    DIR                *dp;
    struct dirent    *dirp;

    if (argc != 2)
        err_quit("usage: ls directory_name");

    if ((dp = opendir(argv[1])) == NULL)
        err_sys("can't open %s", argv[1]);
    while ((dirp = readdir(dp)) != NULL)
        printf("%s/n", dirp->d_name);

    closedir(dp);
    exit(0);
}

    從書後面的附錄中可以看到"ourhdr.h"的內容比較多,包含了比較多的常用標頭檔案,一些巨集定義和一些常用函式和出錯函式的定義。其實,對於每一個具體的程式,我們只需要找到該程式中用到的標頭檔案即可。

    該1-1.c中所用到的系統函式呼叫有:opnedir(),readdir(),printf(),closedir()和exit()。
其中,對於常用的函式prinft()和exit(),它們所在的標頭檔案一般都知道,分別是<stdio.h>和<stdlib.h>。而對於
opnedir (),readdir()和closedir(),我們可以通過man opendir,man readdir,man closedir得到這三個關於目錄操作的函式所在的標頭檔案都是:<sys/types.h>和<dirent.h>。這兩個標頭檔案在源程式中也已經列出。

    其次,1-1.c中還用到了作者自定義的兩個函式:err_quit()和err_sys()。這兩個函式主要使用來進行出錯處理的。當然,使用這兩個函式對錯誤資訊的處理是比較完善的。但是,作為我們學習來講,瞭解程式的核心功能是首要的,我們可以將出錯處理簡化一點,即當遇到錯誤的時候,我們只簡單的使用printf()函式來提示一下有錯誤發生。當然,用printf()來進行出錯處理並不是一種很合理的方法,而且往往我們看不到更關鍵的錯誤資訊,但對於我們僅僅作為學習來用還是可以接受的。畢竟我們要理解的核心部分是程式的功能實現,出錯處理在於其次。

   通過以上的說明,我們可以將1-1.c修改為如下內容:

#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
    DIR *dp;
    struct dirent *dirp;

    if(argc != 2)
    {
        printf("You need input the directory name./n");
        exit(1);  
    }

    if((dp = opendir(argv[1])) == NULL)
    {
        printf("cannot open %s/n", argv[1]);
        exit(1);   

    }

    while ((dirp = readdir(dp)) != NULL)
        printf("%s/n", dirp->d_name);


    closedir(dp);

    exit(0);
}

    這樣修改後的程式已經與作者的標頭檔案"ourhdr.h"沒有關係,可以單獨的進行編譯。我使用的是root使用者,執行命令:

# gcc 1-1.c  //生成目標檔案a.out
或者
# gcc -o 1-1 1-1.c  //生成目標檔案1-1

    沒有任何錯誤和警告,說明編譯成功。這時我們執行生成的目標檔案:

# ./a.out /home
或者
# ./1-1 /home

    則會列出/home路徑下的所有檔案,包括目錄(.)和(..)。

    通過這樣的方法,基本上我們可以將該書中所有的例程修改成不包含"ourhdr.h"的程式。這樣,我們就可以單獨的編譯每一個例程,而不用顧及作者所給的雜湊的標頭檔案。同時這種比較笨的方法,反而有利於幫助我們瞭解不同系統呼叫所對應的標頭檔案,對於學習來說,這應該是一件好事。

    現在,我也才學到APUE的第四章了。前四章的程式,我都是採用這種方法進行編譯和執行。如果也有在學習APUE的朋友,我們可以一起交流。