本文介紹 GNU/Linux 系統上最小的 C 語言編譯器 Tiny C 編譯器。Tiny C 編譯器不僅僅是一個常規意義上的 C 語言編譯器,它還使得用戶可以像使用腳本語言一樣使用 C 語言進行快捷的腳本編程。我們著重介紹用 C 語言進行腳本程序開發的魅力。這個系列將由三篇文章組成,這是第一篇,介紹;在第二篇中,我們將說明如何用標準 C 語言完成通常用 sed 和 awk 完成的字符串處理的工作;在第三篇中,我們將說明如何在自己的編譯器項目中使用 TCC 作為機器代碼生成器。
在下文中,我們說 Tiny C 編譯器、Tiny CC、或者 TCC 都是指的這個 Fabrice Bellard 發明的 GNU/Linux 環境下最小的 ANSI C 語言編譯器。TCC 的主頁在文后的參考資料中列出。在 Debian GNU/Linux 系統中,可以方便的用 apt-get install tcc 來從網絡上安裝 TCC 編譯器。TCC 的主頁上提供有給 Red Hat 系統上使用的 RPM 軟件包。在微軟 Windows 環境下,可以使用 Cygwin 的模擬 UNIX 的開發環境來編譯和使用 Tiny C 編譯器。TCC 是自由軟件,軟件許可證是 GNU LGPL,注意不是 GPL。
TCC 最有趣的特性是可以用 UNIX 系統上常見的 #!/usr/bin/tcc 的方式來執行 ANSI C 語言寫就的源程序,省略掉了在命令行上進行編譯和鏈接的步驟,而可以直接運行 C 語言寫就的源程序。這樣就能做到像任何一種其它的腳本語言比如 Perl 或者是 Python 一樣,顯著的加快開發步調??梢韵窬帉?Shell 腳本一樣的使用 C 語言,隨便想一想都覺得是一件奇妙的事情。但是 TCC 還有一些其它的特性呢!
下面我們用幾個例子來說明 TCC 帶給我們的方便。
這里是一個簡單的 C 語言的 Hello, world! 程序。我們利用這個經典的問候的機會來對 GCC 和 TCC 做一個簡單的比較。
|
在命令行上用 gcc -Wall -O2 -o hello hello.c 編譯后,再加以 strip hello 把可執行文件中的調試信息都刪掉。在 Debian GNU/Linux 上我們得到一個 2592 字節大小的可執行程序。作為比較,我們可以在命令行上用 tcc -run hello.c 直接運行這個經典的問候程序:
|
程序達到同樣的運行效果,可是所需要的相對應的硬盤空間卻只有 hello.c 這個文本文件所占用的 95 個字節。另一方面,我們也可以在命令行上用 tcc -o hello hello.c 像使用 gcc 一樣把 hello.c 編譯成 ELF 格式的可執行文件然后再運行。在命令行上使用 strip hello 把調試信息刪掉以后,我們在同樣的 Debian GNU/Linux 系統上得到了一個 1724 字節大小的可執行文件,和 GCC 得到的 2592 字節相比還是小了不少。
為了充分發揮 TCC 的書寫 C 語言腳本程序的能力,我們還可以在 hello.c 這個源文件的第一行上按照 UNIX 編寫腳本程序的傳統加入:
|
上面的 /usr/bin/tcc 是 Debian GNU/Linux 系統上 tcc 的安裝路徑,如果你是自己下載的 tcc 源代碼進行編譯安裝的話,你的系統上的 tcc 的安裝路徑肯定會有所不同,那么當然需要在這里作相應的改變。加入了這一行以后,我們在命令行上 chmod a+x hello.c 使得 hello.c 變成一個可執行文件。這樣我們就可以直接運行 hello.c 這個程序了。就像我們可以在命令行上直接運行 Shell 腳本或者是用 Perl 和 Python 寫就的腳本程序一樣:
|
在這里看出來,TCC 讓我們可以省略掉在用 C 語言編程序的時候麻煩的一遍遍的編譯和鏈接的步驟,這真的是非常方便。
在這一小節,讓我們來玩一個小小的多重嵌套 TCC 的游戲。
首先說明一下 TCC 是怎樣把命令行參數傳遞給應用程序的。在命令行 Shell 上輸入 tcc -run program.c 就可以不帶參數的運行 program.c 這個程序。如果我們需要從命令行上給 program.c 里面的 main(int argc, char **argv) 函數傳遞命令行參數的話,我們就需要在命令行上輸入 tcc -run program.c arg1 arg2 這樣的命令;這樣 arg1 和 arg2 就被 tcc 傳遞給了 program.c 程序中的 main() 函數。了解了這一點之后,我們來開始我們的小小游戲。
首先是直接在命令行 Shell 上運行 tcc -v 以輸出 tcc 的版本信息。并做一下系統運行時間的測試評估。這里的 tcc 命令是 Debian GNU/Linux 系統上安裝在 /usr/bin 目錄下面的 tcc 命令。關于 time 命令的細節,讀者朋友們可以參考相應的 Manual Page 頁面。
|
接下來讓 tcc 嵌套運行 tcc.c 這個源程序。這是 TCC 的 C 語言寫就的源程序的主文件,其中有 main() 函數這個 C 語言的入口函數。文件 tcc.c 位于下載自 TCC 主頁的 tcc 壓縮軟件包之中。相關鏈接在文后一并列出。把下載的 tcc 壓縮軟件包解開在相應的目錄下面,進入該目錄,就可以按照如下所示繼續我們的小小游戲了。很顯然,程序運行所需要的時間變長了。但是讓 tcc 自己運行自己,讀者朋友們不覺得很有趣嗎?下面我們還可以看到更加有趣的東西呢。
|
似乎上面讓 tcc 自己運行自己還不夠好玩,我們要讓這樣的嵌套更深入一層。首先說明一下 tcc 的 -B 選項設置 tcc 尋找函數庫文件的路徑;-I 選項設置 tcc 尋找 C 語言頭文件的路徑。在 Debian GNU/Linux 系統里面,相關的函數庫文件和 C 語言頭文件安裝在下面的命令中所顯示出來的路徑位置上。系統安裝好了的 tcc 自然可以自動找到這些個位置。但是從源文件運行的 tcc.c 則需要我們來告訴它這些文件的位置在哪里。
|
現在我們可以看到這樣的嵌套幾乎可以無休止的進行下去了。讓我們把這個嵌套再深入兩層,結束我們這個小小的游戲吧。
|
下面再來一層嵌套:
|
我想有一些“勤懇認真”的讀者朋友們不免要問上一句,上面這個小游戲到底有什么意思呢?我的回答是這樣的:上面這個小游戲意在說明小巧的東西是很靈活的。這個游戲一方面可以說明 TCC 的代碼質量;另一方面,我相信,也可以說服讀者朋友們同意,小巧靈活的東西往往可以有出人意料的精彩表演。
TCC 提供給我們用 C 語言進行腳本編程的能力,但是要最大限度的發揮出腳本編程的潛力來,我們需要在命令行 Shell 的環境中,讓 TCC 的腳本程序和其它的命令行 Shell 工具能夠緊密的合作才好。在 UNIX 的命令行 Shell 環境中讓若干個工具合作的方式就是通過我們熟知的 Shell 的管道機制。下面我們來看看 TCC 和 Shell 的管道機制如何配合。
TCC 和 Shell 管道的配合有兩個方面:一是 TCC 編譯器本身如何使用管道;二是用 TCC 編寫的 C 語言腳本程序如何使用管道。
我們先來看 TCC 編譯器本身如何使用 Shell 管道。在 GNU/Linux 系統上處理管道輸入的常見的辦法,是讓命令行程序可以處理特殊的減號(-)作為命令行參數。本來需要從某一個文件讀取輸入數據的命令行程序,在接收到這個減號作為命令行參數以后,就改為從標準輸入(stdin)讀取數據。這樣就可以和 Shell 的管道機制配合起來。但是在當前的 TCC 0.9.19 版本中還不能處理這個減號作為命令行參數。不過我們可以有一個替代的辦法,就是利用 GNU/Linux 系統上的 /dev/stdin 設備文件。
下面的測試是在 Debian GNU/Linux 系統上做出的。在 Debian GNU/Linux 系統上 /dev/stdin 其實是一個指向 /dev/fd/0 的符號鏈接;而后者又是一個指向 /dev/pts/0 的符號鏈接。如果你的 GNU/Linux 系統上沒有 /dev/stdin 的話,你還可以使用 /proc/self/fd/0 來代替。
|
上面的這個 hello.c 還是本文開始的時候列出的那個 hello.c 程序。這里我們看到了 TCC 如何利用 GNU/Linux 上的 /dev/stdin 文件來和 Shell 的管道機制協調運行。我們還是再來看一個嵌套運行 tcc.c 的例子。文件 tcc.c 有將近 300 K 字節大小,用 bzip2 壓縮以后就只剩下 50 K 字節多一點點。我們現在又知道了怎樣利用 Shell 管道,就可以像下面這樣運行 tcc 呢。這樣在存儲空間比較緊張的情況下,又可以節省不少空間了。
|
知道了 TCC 編譯器如何支持 Shell 的管道功能,我們就可以方便的為我們的 C 語言腳本程序做各式各樣所需要的預處理。這樣方便的支持對程序源文件進行預處理的功能,對于我們的 C 語言軟件開發是極為方便的。
上面講了 TCC 編譯器本身如何支持 Shell 管道,下面講用 TCC 編寫的 C 語言腳本程序如何支持 Shell 管道。
這個其實是很簡單的,只要在你的 C 語言腳本程序中恰到好處地使用標準輸入(stdin)和標準輸出(stdout)就可以了。我們來看一個簡單的例子。
|
這個程序很簡單,它把從標準輸入傳遞進來的每一個字符都重復一遍,然后再傳遞到標準輸出。我們看一下它的運行效果:
|
上面這個例子雖然簡單,卻提示我們可以用 TCC 來做一些簡單的字符串處理。這些字符串處理的工作通常都是用 awk 和 sed 這樣的工具來完成的?,F在有了 TCC 這個工具,我們也可以用 C 語言來完成了。在本系列的下一篇文章中,我們就來看看這方面的例子。
TCC 的小巧靈活的特性使得我們可以在諸如安裝軟盤、急救軟盤、以及微型 GNU/Linux 系統上使用 C 語言進行腳本程序編程工作。在中小型的 C 語言項目中,在開發階段使用 TCC 進行工作,可以免去編譯和鏈接的步驟,加快測試的速度。這兩點是 TCC 帶給我們的主要的好處。
此外,我想讀者朋友們也會同意用 C 語言進行腳本程序編程,這實在是一件非常有趣的事情。
感謝 Fabrice Bellard 給我們帶來 TCC 這樣一個美妙的工具。此外,作者還要感謝 Fabrice Bellard 和 Damian M Gryski 在 Tinycc-devel 郵件列表上提供給作者的幫助。