前言
離職前對做過的支付系統進行了一番#總結,繼續完善我的C服務器。
本想着接下來大概實現一下 CGI 協議,但是實現過程中被一個問題卡住了:
C進程與php進程的交互數據類型問題:
在 C 進程中我准備將服務器處理后的請求數據存儲在一個結構體內,然后將此結構體中的信息傳給 PHP,而 PHP 進程內也會有一個全局數組與之對應,可是眾所周之,結構體是 C 進程內的內存數據,是無法直接傳給 PHP 使用的。
這時候我們也需要一種“協議”來解決進程數據類型的異構性。當然這個解決方案確定起來還是很簡單的,無非是對C結構體進行序列化,使用xml,json,protobuf(沒用過)之一,花費時間多的地方在實現過程。 原來想自己造個輪子,實現一下json類型的編解碼,覺得有些偏離了主題了,於是考慮使用一個開源庫cJSON
;
可是自己沒有過 C 大型項目的開發經驗,寫的都是小 demo,gcc -o name source.c
足以解決問題了,沒有過編譯多個文件、組織項目的經驗,下載到源碼后一臉懵逼,搜索到的編譯資料都是一些較為零散的內容,不成體系,不過在自己的多次嘗試下終於成功地將 cJSON 引入到項目中了,這里稍做一下總結。
繞了好久,終於來到了本篇文章的主題:項目編譯,主要介紹一些用 GCC 在 linux 下項目編譯鏈接的步驟。
另外,我只是測試了方案可行,還沒動手改,對方案優劣情況的判斷還不足,望有過類似經驗的同學給點意見什么的。
編譯步驟
先說一下一個C源文件的編譯一般步驟:
-
預處理(preprocess):主要是在代碼層面的處理,包括文件的引入,展開宏定義,刪除注釋,添加行號等,生成的文件以
.i
結尾。gcc -E test.c -o test.i
-
編譯(compilation):編譯是在代碼語法層面的處理,生成對應的匯編語言代碼,生成以
.s
為后綴的匯編語言文件;gcc -S test.i -o test.s
-
匯編(Assembly):將匯編語言代碼生成可執行的機器碼,生成以
.o
為后綴的目標文件。gcc -c test.s -o test.o
-
鏈接(Linking):將各個
.o
目標文件連接起來,並解決庫依賴,生成無后綴的可直接執行文件。gcc -o test test.o
如果我們直接使用后面的命令,那么前面的步驟也會自動執行。如我們常使用的 gcc -o
實際上是一次性完成了所有的步驟的。
以上的中間文件,大家可以使用文本查看工具來查看其中內容來驗證其功能。
靜態庫和動態庫
庫文件有動態和靜態之分,他們的命名規范為 lib庫名.后綴
,在鏈接目標文件和庫時,使用 -l 庫名
(空格可省略)選項,也可以添加-L /path
來規定優先搜索庫文件的目錄。
例如:C中的數學函數庫math.h
的動態庫文件名為libm.so
,那么我們編譯連接文件時就需要添加-lm
的選項。如果要指定庫文件路徑為/usr/lib64/libm.so
,那么可添加-L /usr/lib64
來指定庫文件優先查找目錄。
另外靜態和動態庫文件搜索目錄順序不一樣,下面分別詳細介紹:
靜態庫
靜態庫文件一般是以.a
為后綴的庫文件,它在編譯連接時會將庫文件的內容全部添加到可執行文件中,在編譯連接完成后,靜態庫文件便不再影響可執行文件。
它的優點是簡單粗暴,但如果庫文件內部有改動的話需要重新對所有引用此庫文件的可執行文件重新編譯。
一般編譯步驟如下:
gcc -c static.c -o static.o // 編譯靜態庫文件的源文件
ar -r static.a static.o // 生成靜態庫文件
gcc -o main -lstatic // 連接靜態庫文件生成可執行文件
編譯連接時,靜態庫文件搜索目錄順序為:
- 編譯連接時 -L 參數指定的目錄;
- 環境變量目錄
LIBRARY_PATH
; - 固定目錄
/lib、/usr/lib、/usr/local/lib
等;
動態庫
動態庫文件一般以.so
結尾,它在編譯連接時只把動態庫的文件添加到可執行文件,只在程序運行時才加載庫文件。這種方式的優點是非常靈活,如果動態庫文件內部有變動,那么只需重要重新編譯庫文件即可。
它的一般編譯步驟如下:
gcc -c dynamic.c -fpic -o dynamic.o // 編譯動態庫文件的源文件 -fpic 表示編譯為位置獨立的代碼,使之可以被放在可執行文件內存中的任何地方
gcc -shared dynamic.o -o dynamic.so // 生成動態庫文件
gcc -o main -L . -ldynamic // 連接當前文件夾下的動態庫文件
編譯連接時,動態庫文件搜索目錄順序為:
- 編譯連接時 -L 參數指定目錄;
- 環境變量目錄
LD_LIBRARY_PATH
; - 配置文件
/etc/ld.so.conf
中配置的目錄 - 固定目錄
/lib、/usr/lib
等。
CMakeLists
寫到這里還不是結尾,我們要考慮如果文件非常多怎么辦,難道每一次都要輸入n多個源文件名嗎?如果軟件完成后,用戶使用時可不想記住這些復雜的命令和文件。
自動化才是目標,我們考慮使用自動化編譯工具 cmake,那么接下來我們就要編寫適合項目文件的編譯配置文件 CMakeLists。
CMakeLists 是一個 txt 文件,它就像是項目的編譯指南,是給用 cmake 工具用的。其語法類似於 shell,但內置了許多函數,這里我們介紹幾個簡單的語法,編寫一個簡單的 CMakeLists.txt
。
當前文件結構:
|__ CMakeLists.txt
|__ test.c
|__ cJSON.c
|__ include
| |__ cJSON.h
|__ lib
下面是一個動態庫的編譯CmakeList,將解釋放在注釋中。
PROJECT(test) # 項目名稱
cmake_minimum_required(VERSION 2.8) # 選擇一個cmake版本
SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) # 設定產生庫的目錄
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) # 設定產生的可執行文件的目錄
ADD_EXECUTABLE(test test.c) # 這里要先聲明產生的可執行文件,以便后面連接
SET(cJSON cJSON.c) # 設置文件變量
ADD_LIBRARY(cJSON SHARED ${cJSON}) # 此語句用文件變量生成一個動態鏈接庫
TARGET_LINK_LIBRARIES(test cJSON) # 連接可執行文件與動態鏈接庫
FIND_LIBRARY(MATH_LIB libm.so /usr/lib64) # 在/usr/lib64文件夾下找libm.so(cJSON需要)
IF(MATH_LIB)
TARGET_LINK_LIBRARIES(test ${MATH_LIB}) # 找到之后連接上
ENDIF()
MESSAGE("cmake complete, use make to compile!") # 在命令行輸出提示語句
搞了一個多小時,終於寫出來了一個能用的 CMakeLists 文件。運行 cmake . && make
完成項目的構建。
此時的目錄結構為(略過了 cmake 產生的臨時文件):
|__ CMakeLists.txt
|__ test.c
|__ cJSON.c
|__ include
| |__ cJSON.h
|__ lib
| |__ libcJSON.so
|__ bin
|__ test
小結
本文嚴重地說明了光會寫代碼沒什么卵用,環境的搭建/類庫的使用也是必備技能,畢竟不能每個輪子都自己造。
如果你也是 C 新手的話,本文可以讓你大概了解一下編譯步驟等,不至於跟我一開始一樣一頭霧水。如果要深入學習的話,文章的關鍵詞和下面的參考文件也能有些幫助。
如果您覺得本文對您有幫助,可以點擊下面的 推薦 支持一下我。博客一直在更新,歡迎 關注 。
參考文件(精挑細選):