1. 程式人生 > >uc筆記01---Unix,Linux,程式構建過程,gcc,標頭檔案,預處理,環境變數配置

uc筆記01---Unix,Linux,程式構建過程,gcc,標頭檔案,預處理,環境變數配置

1.    Unix 作業系統
    1)簡介
        美國 AT&T 公司貝爾實驗室,
        1971 年,
        肯.湯普遜、丹尼斯.裡奇。

        多使用者、多工、支援多種處理器架構。
        高安全性、高可靠性,高穩定性。

        既可構建大型關鍵業務系統的商業伺服器應用,
        也可構建面向移動終端、手持裝置等的嵌入式應用。

    2)三大派生版本
    a. System V
        AIX: IBM,銀行
        Solaris: SUN->Oracle,電信
        HP-UX
        IRIX
        
    b. Berkley
        FreeBSD
        NetBSD
        OpenBSD
        Mac OS X
        
    c. Hybrid
        Minix: 迷你版的類 Unix 作業系統。
        Linux: GPL,免費開源,商用伺服器(RedHat)、
        桌面(Ubuntu)、嵌入式(Android)。
        
    作業系統的基本分類:
    實時,分時和批處理;

3.    Linux 作業系統
    1)簡介
        類 Unix 作業系統,免費開源。
        不同發行版本使用相同核心。

        手機、平板電腦、路由器、視訊遊戲控制檯、臺式計算機、
        大型計算機、超級計算機。

        嚴格意義上的 Linux 僅指作業系統核心。
        隸屬於 GNU 工程。

        發明人 Linus Torvalds。

    2)相關知識
    a. Minix 作業系統
        荷蘭阿姆斯特丹 Vrije 大學,
        數學與計算機科學系,
        Andrew S. Tanenbaum,
        ACM 和 IEEE 的資深會員。

    b. GNU 工程
        Richard Stallman 發起於1984年,
        由自由軟體基金會 (FSF) 提供支援。

        GNU 的基本原則就是共享,
        其主旨在於發展一個有別於一切商業 Unix 系統的,
        免費且完整的類 Unix 系統——-GNU Not Unix。

    3)POSIX 標準
        Portable Operating System Interface for Computing Systems,
        統一的系統程式設計介面規範。
        由 IEEE 和 ISO/IEC 開發。

        保證應用程式原始碼級的可移植性。
        Linux 完全遵循 POSIX 標準。

    4)GPL 通用公共許可證
        允許對某成果及其派生成果的重用、修改和複製,
        對所有人都是自由的,但不能宣告做了原始工作,或宣告由他人所做。

    5)版本
        早期版本:0.01, 0.02, . . . , 0.99, 1.0

        舊計劃:介於 1.0 和 2.6 之間,A.B.C
            A: 主版本號,核心大幅更新。
            B: 次版本號,核心重大修改,奇數測試版,偶數穩定版。
            C: 補丁序號,核心輕微修訂。
        2003 年 12 月釋出 2.6.0 以後:縮短髮布週期,A.B.C-D.E

        D: 構建次數,反映極微小的更新。
        E: 描述資訊。
       rc/r - 候選版本,其後的數字表示第幾個候選版本,
                越大越接近正式版本
       smp  - 對稱多處理器
       pp   - Red Hat Linux 的測試版本
       EL   - Red Hat Linux 的企業版本
       mm   - 測試新技術或新功能
       fc   - Red Hat Linux 的 Fedora Core 版本

        用 cat /proc/version 命令檢視系統版本資訊:
        # cat /proc/version
        Linux version 3.6.11-4.fc16.i686

        # cat /proc/version
        Linux version 3.2.0-39-generic-pae

    6)特點
        遵循 GNU/GPL
        開放性
        多使用者
        多工
        裝置獨立性
        豐富的網路功能
        可靠的系統安全
        良好的可移植性

    7)發行版本
        大眾的 Ubuntu
        優雅的 Linux Mint
        銳意的 Fedora
        華麗的 openSUSE
        自由的 Debian
        簡潔的 Slackware
        老牌的 RedHat

4.    構建過程 Build
    編輯 -> 預編譯 -> 編譯 -> 彙編 -> 連結

        1) 編輯:  vi hello.c                -> hello.c
        2) 預編譯:gcc -E hello.c -o hello.i -> hello.i ---+
        3) 編譯:  gcc -S hello.i            -> hello.s    |  GCC
        4) 彙編:  gcc -c hello.s            -> hello.o    |  工具鏈
        5) 連結:  gcc hello.o -o hello      -> hello   ---+

5.    GNU 編譯工具 GCC

    支援多種程式語言
        C、C++、Objective-C、Java、Fortran、Pascal、Ada
    支援多種平臺
        Unix、Linux、Windows

    編譯多個源程式
        gcc [選項引數] 檔案 1 檔案 2 . . .
        
    gcc [選項引數] 檔案
        -c        - 只編譯不連結
        -o        - 指定輸出檔案
        -E        - 預編譯
        -S        - 產生彙編檔案
        -pedantic - 對不符合ANSI/ISO C語言標準的
                       擴充套件語法產生警告
        -Wall     - 產生儘可能多的警告。            // Waring all
                         範例:gcc -Wall wall.c
        -Werror   - 將警告作為錯誤處理。            // Waring error
                         範例:gcc -Werror werror.c
        -x        - 指定原始碼的語言。
                         範例:gcc -x c++ cpp.c -lstdc++
        -g        - 生成除錯資訊
        -O1/O2/O3 - 優化等級                        // O1 最低,O3 最高;注意:是英文 o,不是零;
        
    檔案字尾
        .h  - C 語言原始碼標頭檔案
        .c  - 預處理前的 C 語言原始碼檔案
        .i  - 預處理後的 C 語言原始碼檔案
        .s  - 組合語言檔案
        .o  - 目標檔案
        .a  - 靜態庫檔案(後面講)
        .so - 共享庫(動態庫)檔案(後面講)
        
    可以用 nm 命令產看目標檔案:        
        nm hello.o
            000000    T mian        // T 程式碼區
                    U put        // U 標準庫
                    
6.    標頭檔案
    思考:標頭檔案的作用是什麼?
        1) 宣告外部變數、函式和類。
        2) 定義巨集、類型別名和自定義型別。
        3) 包含其它標頭檔案。
        4) 藉助標頭檔案衛士,防止因同一個標頭檔案被多次包含,而引發重定義錯。

    包含標頭檔案時需要注意:
        1)#include <...>
        先找 -I 指定的目錄,再找系統目錄。

        2)#include "..."
        先找 -I 指定的目錄,再找當前目錄,最後找系統目錄。

        3)gcc 的 -I 選項
        指定標頭檔案附加搜尋路徑。
        
        4)標頭檔案的系統目錄
            /usr/include
            /usr/local/include
            /usr/lib/gcc/i686-linux-gnu/4.6.3/include
            /usr/include/c++/4.6.3 (C++ 編譯器優先查詢此目錄)
    
    標頭檔案的三種定位方式:
        1) #include "目錄/xxx.h"            - 標頭檔案路徑發生變化,需要修改源程式;
        2) C_INCLUDE_PATH/CPATH=目錄        - 同時構建多個工程,可能引發衝突;(在 gcc 裡設定,詳見後面環境變數)
        3) gcc -I目錄                        - 既不用改程式,也不會有衝突
            
7.    預處理
    預處理又稱為預編譯,是做些程式碼文字的替換工作。
    處理 # 開頭的指令,比如拷貝 #include 包含的檔案程式碼,
    #define 巨集定義的替換,條件編譯等,就是為編譯做的預備工作的階段,
    主要處理 # 開始的預編譯指令,預編譯指令指示了在程式正式編譯前就由編譯器進行的操作,可以放在程式中的任何位置。
    
    c 編譯系統在對程式進行通常的編譯之前,先進行預處理。
    c 提供的預處理功能主要有以下三種:
    1)巨集定義
    2)檔案包含
    3)條件編譯
    
    以下情況需要使用預處理:
    1)總是使用不經常改動的大型程式碼體。
    2)程式由多個模組組成,所有模組都使用一組標準的包含檔案和相同的編譯選項。
    在這種情況下,可以將所有包含檔案預編譯為一個預編譯頭。
    
    常用的預處理指令:
        #include            // 將指定檔案的內容插至此指令處
        #include_next        // 與 #include 一樣,但從當前目錄之後的目錄查詢,極少用
        #define            // 定義巨集
        #undef                // 刪除巨集
        #if                // 判定
        #ifdef                // 判定巨集是否已定義
        #ifndef            // 判定巨集是否未定義
        #else                // 與 #if、#ifdef、#ifndef 結合使用
        #elif                // else if 多選分支
        #endif                // 結束判定
        ##                    // 連線巨集內兩個連續的字串
        #                    // 將巨集引數擴充套件成字串字面值

        #error                // 產生錯誤,結束預處理
        #warning            // 產生警告
    
8.    環境變數(參見後面的庫)
        C_INCLUDE_PATH     - C 標頭檔案的附加搜尋路徑,相當於 gcc 的 -I 選項
        CPATH              - 同 C_INCLUDE_PATH
        CPLUS_INCLUDE_PATH - C++ 標頭檔案的附加搜尋路徑
        LIBRARY_PATH       - 連結時查詢靜態庫/共享庫的路徑
        LD_LIBRARY_PATH    - 執行時查詢共享庫的路徑
    
    舉例:
        標頭檔案:cal.h
        #ifndef _CALC_H
        #define _CALC_H
        double add (double, double);
        #endif
        
        定義:add.c
        double add (double a, double b) {
            return a + b;
        }
        
        主函式:main.c
        #include <stdio.h>
        #include <cal.h>
        // #include "cal.h"
        int main () {
            printf ("%lf\n", add (10, 20));
            return 0;
        }
        
        這裡 #include <cal.h> 預設到系統目錄下找 cal.h 這個檔案,會編譯報錯;
        解決這個問題就需要自己定義搜尋路徑:
        
        方法 1:通過 gcc 的 -I 選項指定 C/C++ 標頭檔案的附加搜尋路徑:
            #gcc add.c main.c -I.            // 在當前目錄搜尋
        
        方法 2:通過環境變數 CPATH 增加編譯器搜尋路徑,這種方法只在當前 shell 有效;
            #export CPATH=$CPATH:.
            #echo $CPATH                    // 檢視當前環境變數
        
        方法 3:也可以在 ~/.bashrc 或 ~/.bash_profile,配置檔案中寫環境變數,持久有效:
            #ls -a
            #vi .bashrc                        // 進入 .bashrc
                或者:
                #vi .bash_profile
            在 bashrc / bash_profile 裡輸入下面語句:
                export CPATH=$CPATH:.
            退出 bashrc,執行下面語句:
                #source ~/.bashrc
                或者:
                #source ~/.bash_profile
            檢視結果:
                #echo $CPATH
                或者:
                #env | grep CPATH
                
    取消環境變數設定:
        # unset CPATH
        # unset LIBRARY_PATH            // 取消靜態庫和動態庫的環境變數
    
9.    VERSION 版本控制
    檔案:error.c
    #include <stdio.h>
    #if (VERSION < 2)
        #error "版本太低"
    #elif (VERSION > 3)
        #warning "版本太高"
    #endif
    int main () {
        printf("%d\n", VERSION);
        return 0;
    }
    測試結果如下:
    輸入:
        # gcc error.c -DVERSION=2
    輸出:
        error.c:4:3: error: #error "Version too low !"
        
    輸入:
        # gcc error.c -DVERSION=3
    輸出:
        (空行)
    
    輸入:
        # gcc error.c -DVERSION=4
    輸出:
        error.c:6:3: warning: #warning "Version too high !" [-Wcpp]
        
10    #line 指定行號:
        int main () {
            printf ("%d\n", __LINE__);    // 輸出當前行號
        #line 100
            printf ("%d\n", __LINE__);    // 輸出:100
            printf ("%d\n", __LINE__);    // 輸出:101
            return 0;
        }        
        
11    #pragma 提供額外資訊的標準方法,
    設定編譯器的狀態或者指示編譯器完成一些特定的動作;

    #pragma GCC dependency <檔案>        // 若 <檔案> 比當前檔案新,則產生警告
    #pragma GCC poison <標識>            // 若出現 <標識>,則產生錯誤
    #pragma pack(1/2/4/8)                // 按 1/2/4/8 位元組,對齊補齊
    
    例 1:pragma.c
        #include <stdio.h>
        #pragma GCC dependency "dep.c"
        // 如果 dep.c 比當前檔案 pragma.c 新,則發出警告;
        #pragma GCC poison goto int
        // 如果當前程式中出現 goto/int 關鍵字,則發出警告;
        int main () {
            goto escape;
            return 0;
        escape:
            printf ("hello");
            return 0;
        }
        tips:這裡有 goto 語句,所以執行到第一個 return 0; 的時候不會跳出程式,
        而是跳過 return,執行 escape 後面的語句;
    
    例 2:
        #include <stdio.h>
        int main () {
        #pragma pack(1)        // 按 1 位元組對齊補齊
            struct S1 {
                double d;
                int i;
                char c;
                short h;
            };
            // DDDDDDDDIIIICHH
            printf ("%u\n", sizeof (struct S1);        // 輸出:15
        #pragma pack()        // 按預設方式對齊,32 位系統為 4 位元組,64 位系統為 8 位元組
            struct S2 {
                double d;
                int i;
                char c;
                short h;
            };
            // DDDD DDDD IIII CXSS
            printf ("%u\n", sizeof (struct S2);        // 輸出:16
        #pragma pack(8)        // 按 8 位元組對齊,如果是 32 位系統,則只能還是按 4 位元組對齊;
            struct S3 {
                double d;
                int i;
                char c;
                short h;
            };
            printf ("%u\n", sizeof (struct S3);        // 輸出:16
            return 0;
        }
    
12.    預定義巨集
    例 1:
    檔案:print.h
    #ifndef _PRINT_H
    #define _PRINT_H
    #include <stdio.h>
    void print () {
        printf ("%s\n", __BASE_FILE__);        // 正在編譯的檔案:predef.c
        printf ("%s\n", __FILE__);            // 所在檔案:print.h
        printf ("%d\n", __LINE__);
        printf ("%s\n", __FUNCTION__);
        printf ("%s\n", __func__);
        printf ("%s\n", __DATE__);
        printf ("%s\n", __TIME__);
        printf ("%d\n", __INCLUDE_LEVEL__);    // 包含 include 層數:2
        #ifdef __cplusplus                    // 判斷是否是由 c++ 程式編譯;
        printf ("%d\n", __cplusplus);        // 當用 c++ 編譯此檔案時,會返回 1;否則什麼都不返回;
        #endif        // __cplusplus
    }
    #endif            // _PRINT_H
    -----------------------    
    檔案:predef.h    
    #ifndef _PREDEF_H
    #define _PREDEF_H
    #include "print.h"
    #endif            // _PREDEF_H
    -----------------------
    檔案:predef.c
    #include "predef.h"
    int main () {
        print ();
        return 0;
    }
        
13.    靜態庫
    連結靜態庫是將庫中的被呼叫程式碼複製到呼叫模組中,
    而連結共享庫則只是在呼叫模組中,
    嵌入被呼叫程式碼在庫中的(相對)地址。

    靜態庫佔用空間非常大,不易修改但執行效率高。
    共享庫佔用空間小,易於修改但執行效率略低。

    靜態庫的預設副檔名是 .a,共享庫的預設副檔名是 .so。
    編寫一個演算法程式,其中演算法位於 cal.c 檔案裡,列印位於 show.c 檔案裡;
    各自都包含有各自的標頭檔案,然後把整個檔案打包給客戶;
    客戶編寫 main.c 呼叫演算法程式;
    
    步驟:
    1)編寫 cal.h 和 cal.c;執行生成 cal.o 目標檔案;
    2)編寫 show.h 和 show.c;執行生成 show.o 目標檔案;
    3)編寫 math.h,用於包含 cal.h 和 show.h,這樣客戶就不用載入多個頭檔案了;
    4)把 cal.o 和 show.o 打包成靜態庫 libmath.a;
    5)把所有標頭檔案和靜態庫給客戶;(必須是所有標頭檔案都給客戶)
    6)客戶編寫測試程式,僅需要載入 math.h 標頭檔案就可以呼叫庫中函式;
    
    編寫靜態庫
    =====
    標頭檔案:cal.h
        #ifndef _CALC_H
        #define _CALC_H
        int add (int a, int b);
        int sub (int a, int b);
        #endif        // _CALC_H
    
    檔案:calc.c
        #include "calc.h"
        int add (int a, int b) {
            return a + b;
        }
        int sub (int a, int b) {
            return a - b;
        }
    生成目標檔案:
    gcc -c calc.c        --->    calc.o
    
    標頭檔案:show.h
        #ifndef _SHOW_H
        #define _SHOW_H
        void show (int a, char op, int b, int c);
        #endif        // _SHOW_H
        
    檔案:show.c
        #include <stdio.h>
        #include "show.h"
        void show (int a, char op, int b, int c) {
            printf ("%d%c%d=%d\n", a, op, b, c);
        }    
    生成目標檔案:
    gcc -c show.c        --->    show.o
    
    標頭檔案:math.h
        #ifndef _MATH_H
        #define _MATH_H
        #include "calc.h"
        #include "show.h"
        #endif        // _MATH_H
    
    壓縮成靜態庫
    ======
    ar -r libmath.a calc.o show.o    --->    libmath.a
    nm libmath.a        // 檢視靜態庫
    
        ar指令:ar [選項] 靜態庫檔名 目標檔案列表
        -r - 將目標檔案插入到靜態庫中,已存在則更新
        -q - 將目標檔案追加到靜態庫尾
        -d - 從靜態庫中刪除目標檔案
        -t - 列表顯示靜態庫中的目標檔案
        -x - 將靜態庫展開為目標檔案

        注意:提供靜態庫的同時也需要提供標頭檔案。
    
    呼叫靜態庫(客戶測試)
    ==========
    檔案:main.c
        #include "math.h"
        int main () {
            show (30, '+', 23, add (30, 23));
            return 0;
        }
    
    方法 1:直接編譯:
    gcc main.c libmath.a
    ./a.out
    
    方法 2:通過 -L 手動指定靜態庫連線地址:
    gcc main.c -lmath -L.                    // -l 會自動加上 lib 和 .a;        
    ./a.out
    
    方法 3:設定臨時環境變數:
    export LIBRAR_PATH=$LIBRART_PATH:.        // gcc 連結時查詢靜態庫/共享庫的路徑
    gcc main.c -lmath
    ./a.out
    
    方法 4:也可以在 ~/.bashrc 或 ~/.bash_profile,配置檔案中寫環境變數,持久有效:
    #ls -a
    #vi .bashrc                                // 進入 .bashrc
    或者:#vi .bash_profile
    在 bashrc 裡輸入下面語句:
        export CPATH=$CPATH:.
    退出 bashrc,執行下面語句:
        #source ~/.bashrc
    或者:#source ~/.bash_profile
    檢視結果:
        #echo $CPATH
        
    執行靜態庫
    =====
    # ./a.out

    在可執行程式的連結階段,已將所呼叫的函式的二進位制程式碼,
    複製到可執行程式中,因此執行時不需要依賴靜態庫。

14.    動態庫
    1)建立動態庫:
    =======
    把上面所有的標頭檔案和目標檔案都拷貝一份,作如下操作:
    gcc -c -fpic calc.c    -->    calc.o
    gcc -c -fpic show.c    -->    show.o                                // 編譯器
    gcc -shared calc.o show.o -o libmath.so    -->    libmath.so        // 連結器
    或一次完成編譯和連結:
    # gcc -shared -fpic calc.c show.c -o libmath.so
    
    PIC (Position Independent Code):位置無關程式碼。
    可執行程式載入它們時,可將其對映到其地址空間的任何位置。
    
    -fPIC : 大模式,生成程式碼比較大,執行速度比較慢,所有平臺都支援。
    -fpic : 小模式,生成程式碼比較小,執行速度比較快,僅部分平臺支援。

    注意:提供共享庫的同時也需要提供標頭檔案。

    2)呼叫動態庫
    =======
    方法 1:
        # gcc main.c libmath.so (直接法)
    方法 2:
    通過 LIBRARY_PATH 環境變數指定庫路徑:
        # export LIBRARY_PATH=$LIBRARY_PATH:.
        # gcc main.c -lmath (環境法)
    方法 3:
    通過 gcc 的 -L 選項指定庫路徑:
        # unset LIBRARY_PATH                    // 取消方法 2 中設定的環境變數
        # gcc main.c -lmath -L. (引數法)
    方法 4:也可以在 ~/.bashrc 或 ~/.bash_profile,配置檔案中寫環境變數,
        方法同上;
    方法 5:
    一般化的方法:
        # gcc .c/.o -l<庫名> -L<庫路徑>
        
    3)執行動態庫
    =======
    執行時需要保證 LD_LIBRARY_PATH 環境變數中包含共享庫所在的路徑:
        # export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
        # ./a.out

    在可執行程式的連結階段,並不將所呼叫函式的二進位制程式碼複製到可執行程式中,
    而只是將該函式在共享庫中的地址嵌入到可執行程式中,因此執行時需要依賴共享庫。

15.    動態載入共享庫/動態庫
    #include <dlfcn.h>
    1)載入共享庫
        void* dlopen (const char* filename, int flag);
    成功返回共享庫控制代碼,失敗返回 NULL;
    
    filename 為共享庫路徑,若只給檔名,則根據 LD_LIBRARY_PATH 搜尋;

    flag取值:
        RTLD_LAZY - 延遲載入,使用共享庫中的符號(如呼叫函式)時才載入。
        RTLD_NOW  - 立即載入。

    2)獲取函式地址
        void* dlsym (void* handle, const char* symbol);
        成功返回函式地址,失敗返回 NULL;
        
        handle 為共享庫控制代碼,symbol 為函式名;

    3)解除安裝共享庫
        int dlclose (void* handle);
        成功返回 0,失敗返回非零。

    4)獲取錯誤資訊
        char* dlerror (void);
        有錯誤發生則返回錯誤資訊字串指標,否則返回 NULL。

    範例:load.c
    #include <stdio.h>
    #include <dlfcn.h>
    typedef int (*PFUN_CALC) (int, int);    // 定義函式指標型別
    typedef void (*PFUNC_SHOW) (int, char, int, int);
    int main () {
        // 建立控制代碼
        void* handle = dlopen ("shared/libmath.so", RTLD_NOW);
        if (! handle) {
            fprintf (stderr, "dlopen:%s\n", dlerror ());
            return -1;
        }
        // 取出函式
        PFUNC_CALC add = (PFUNC_CALC)dlsym (handle, "add");
        if (! add) {
            fprintf (stderr, "dlsym:%s\n", dlerror ());
            return -1;
        }
        PFUNC_CALC sub = (PFUNC_CALC)dlsym (handle, "sub");
        if (! sub) {
            fprintf (stderr, "dlsym:%s\n", dlerror ());
            return -1;
        }
        PFUNC_SHOW show = (PFUNC_CALC)dlsym (handle, "show");
        if (! show) {
            fprintf (stderr, "dlsym:%s\n", dlerror ());
            return -1;
        }
        // 呼叫
        show (30, '+', 20, add (30, 20));
        show (30, '+', 20, sub (30, 20));
        // 關閉
        if (dlclose (handle)) {
            fprintf (stderr, "dlclose:%s\n", dlerror ());
            return -1;
        }
        return 0;
    }
    # gcc load.c -ldl
    注意:連結時不再需要 -lmath,但需要 -ldl(連線動態庫)。
    
16.    輔助工具
    nm: 檢視目標檔案、可執行檔案、靜態庫、共享庫中的符號列表。
    ldd: 檢視可執行檔案和共享庫的動態依賴。
    ldconfig: 共享庫管理。

    事先將共享庫的路徑資訊寫入 /etc/ld.so.conf 配置檔案中,
    ldconfig 根據該配置檔案生成 /etc/ld.so.cache 緩衝檔案,
    並將該緩衝檔案載入記憶體,藉以提高共享庫的載入效率。

    系統啟動時自動執行 ldconfig,但若修改了共享庫配置,則需要手動執行該程式。

    strip: 減肥。去除目標檔案、可執行檔案、靜態庫和共享庫中的符號列表、除錯資訊等。

    objdump: 顯示二進位制模組的反彙編資訊。
        # objdump -S a.out

    指令地址      機器指令                  彙編指令
    --------    --------------------    ---------------------
    8048514:    55                      push %ebp
    8048515:    89 e5                   mov  %esp,%ebp
    8048517:    83 e4 f0                and  $0xfffffff0,%esp
    804851a:    83 ec 20                sub  $0x20,%esp
    804851d:    c7 44 24 04 02 00 00    movl $0x2,0x4(%esp)

17.    作業:編寫一個函式 diamond(),列印一個菱形,其高度、
    寬度、實心或者空心以及圖案字元,均可通過引數設定。
    分別封裝為靜態庫 libdiamond_static.a 和動態庫 libdiamond_shared.so,並呼叫之。
    
    標頭檔案:diamond.h
        #ifndef _DIAMOND_H
        #define _DIAMOND_H
        void diamond (int h, int d, int s, char c);
        #endif // _DIAMOND_H
    檔案:diamond.c
        #include <stdio.h>
        #include "diamond.h"
        void diamond (int h, int d, int s, char c) {
            int i, j;
            for (i = 0; i <= h; i++) {
                printf ("%*s", (h - i) * d, "");
                for (j = 0; j < i * d * 2; j++)
                    printf ("%c", j ? (s ? c : ' ') : c);
                printf ("%c\n", c);
            }
            for (i = 1; i <= h; i++) {
                printf ("%*s", i * d, "");
                for (j = 0; j < (h - i) * d * 2; j++)
                    printf ("%c", j ? (s ? c : ' ') : c);
                printf ("%c\n", c);
            }
        }
    主函式:main.c
        #include <stdio.h>
        #include "diamond.h"
        int main (int argc, char* argv[]) {
            if (argc < 5) {
                fprintf (stderr, "Usage: %s <H> <D> <S> <C>\n", argv[0]);
                return -1;
            }
            diamond (atoi (argv[1]), atoi (argv[2]), atoi (argv[3]), *argv[4]);
            return 0;
        }