用C寫一個web伺服器(三) Linux下用GCC進行專案編譯

枕邊書發表於2017-04-18

前言

離職前對做過的支付系統進行了一番#總結,繼續完善我的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原始檔的編譯一般步驟:

  1. 預處理(preprocess):主要是在程式碼層面的處理,包括檔案的引入,展開巨集定義,刪除註釋,新增行號等,生成的檔案以.i結尾。

    gcc -E test.c -o test.i

  2. 編譯(compilation):編譯是在程式碼語法層面的處理,生成對應的組合語言程式碼,生成以.s為字尾的組合語言檔案;

    gcc -S test.i -o test.s

  3. 彙編(Assembly):將組合語言程式碼生成可執行的機器碼,生成以.o為字尾的目標檔案。

    gcc -c test.s -o test.o

  4. 連結(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 // 連線靜態庫檔案生成可執行檔案

編譯連線時,靜態庫檔案搜尋目錄順序為:

  1. 編譯連線時 -L 引數指定的目錄;
  2. 環境變數目錄 LIBRARY_PATH
  3. 固定目錄 /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 // 連線當前資料夾下的動態庫檔案

編譯連線時,動態庫檔案搜尋目錄順序為:

  1. 編譯連線時 -L 引數指定目錄;
  2. 環境變數目錄 LD_LIBRARY_PATH
  3. 配置檔案/etc/ld.so.conf中配置的目錄
  4. 固定目錄 /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 新手的話,本文可以讓你大概瞭解一下編譯步驟等,不至於跟我一開始一樣一頭霧水。如果要深入學習的話,文章的關鍵詞和下面的參考檔案也能有些幫助。

如果您覺得本文對您有幫助,可以點選下面的 推薦 支援一下我。部落格一直在更新,歡迎 關注

參考檔案(精挑細選):

GCC工作過程以及動態庫靜態庫連結

Linux動態庫檔案搜尋路徑

cmake使用示例與整理總結

相關文章