項目開發日報表
項目名稱 | 【蘇嵌實訓-嵌入式 linux C 第4天】 | |
今日進度以及任務 |
|
|
本日任務完成情況 |
1、編譯器三級優化分別優化了哪些? ①、第一級:代碼調整 ②、第二級:新的視角 ③、第三級:表驅動狀態機
見附錄1
見附錄2 |
|
本日開發中出現的問題彙總 |
無 |
|
本日未解決的問題 | 無 | |
本日開發收穫 | 瞭解了gcc、gdb工具的使用,學會創建靜態庫、動態庫;學會使用cmake | |
其他 | 無 |
附錄1(靜態、動態庫的創建與使用)
靜態庫:
- 假設當前有一個 C 語言項目,其目錄結構如下所示:
demo項目
├─ headers
│ └─ test.h
└─ sources
├─ add.c
├─ sub.c
├─ div.c
└─ main.c
可以看到,該項目中包含 1 個頭文件( .h ),4 個源文件( .c ),它們各自包含的代碼如下所示:
整個項目的邏輯很簡單,其中 add.c、sub.c 和 div.c 這 3 個文件中各包含一個函數,分別實現將兩個整數做相加、相減和除法操作,而 test.h 僅包含這 3 個函數的聲明部分,main.c 是主程序文件,其通過引入 test.h 頭文件調用了 3 個函數,從而分別完成了對用戶輸入的 2 個整數做相加、相減以及除法操作。
對於編譯、運行 demo 項目,我們可以直接使用 gcc 命令完成:
注意,由於在程序預處理階段,GCC 編譯器會自行處理各個 .c 文件內部引入的 .h 頭文件(將 .h 文件中的代碼直接拷貝到當前 .c 源文件中),因此編譯運行 demo 項目時,我們只需要提供所有的源文件即可,不需要處理頭文件。
注意,add.c、sub.c 和 div.c 這 3 個文件,其包含的都是一些功能模塊(實現具體功能的函數),對於這樣的源文件,只要我們願意共享,每個人都可以直接用到自己的項目中。這就產生一個問題,如果僅希望別人使用我們實現的功能,但又不想它看到具體實現的源碼,該怎麼辦呢?很簡單,就是將它們加工成一個靜態鏈接庫。
靜態鏈接庫的創建
通過前面的學習我們知道,靜態鏈接庫其實就相當於壓縮包,其內部可以包含多個源文件。但需要注意的是,並非任何一個源文件都可以被加工成靜態鏈接庫,其至少需要滿足以下 2 個條件:
- 源文件中只提供可以重複使用的代碼,例如函數、設計好的類等,不能包含 main 主函數;
- 源文件在實現具備模塊功能的同時,還要提供訪問它的接口,也就是包含各個功能模塊聲明部分的頭文件。
顯然對於 demo 項目中的 add.c、sub.c 以及 div.c 這 3 個源文件來說,以上 2 個條件都符合,因此都可以被加工成靜態鏈接庫。並且根據實際需要,我們可以將它們集體壓縮到一個靜態鏈接庫中,也可以各自壓縮成一個靜態鏈接庫。
將源文件打包爲靜態鏈接庫的過程很簡單,只需經歷以下 2 個步驟:
1) 將所有指定的源文件,都編譯成相應的目標文件:
2) 然後使用 ar 壓縮指令,將生成的目標文件打包成靜態鏈接庫,其基本格式如下:
ar rcs 靜態鏈接庫名稱 目標文件1 目標文件2 ...
有關 ar 打包壓縮指令,以及 rcs 各選項的含義和功能,感興趣的讀者可自行查找相關資料瞭解。這裏需要重點說明的是,靜態鏈接庫的不能隨意起名,需遵循如下的命名規則:
libxxx.a
Linux 系統下,靜態鏈接庫的後綴名爲 .a;Windows 系統下,靜態鏈接庫的後綴名爲 .lib。
其中,xxx 代指我們爲該庫起的名字,比如 Linux 系統自帶的一些靜態鏈接庫名稱爲 libc.a、libgcc.a、libm.a,它們的名稱分別爲 c、gcc 和 m。
下面,我們嘗試將 add.o、sub.o 和 div.o 打包到一個靜態鏈接庫中:
其中,libmymath.a 就是 add.o、sub.o 和 div.o 一起打包生成的靜態鏈接庫,mymath 是我們自定義的庫名。
通過以上 2 步操作,我們就成功創建出了 libmymath.a 靜態鏈接庫。那麼,該如何使用它呢?
靜態鏈接庫的使用
靜態鏈接庫的使用很簡單,就是在程序的鏈接階段,將靜態鏈接庫和其他目標文件一起執行鏈接操作,從而生成可執行文件。
以 demo 項目爲例,首先我們將 main.c 文件編譯爲目標文件:
在此基礎上,我們可以直接執行如下命令,即可完成鏈接操作:
[root@localhost demo]# gcc -static main.o libmymath.a
[root@localhost demo]# ls
add.c a.out div.o main.c sub.c test.h
add.o div.c libmymath.a main.o sub.o
其中,-static 選項強制 GCC 編譯器使用靜態鏈接庫。
注意,如果 GCC 編譯器提示無法找到 libmymath.a,還可以使用如下方式完成鏈接操作:
其中,-L(大寫的 L)選項用於向 GCC 編譯器指明靜態鏈接庫的存儲位置(可以藉助 pwd 指令查看具體的存儲位置); -l(小寫的 L)選項用於指明所需靜態鏈接庫的名稱,注意這裏的名稱指的是 xxx 部分,且建議將 -l 和 xxx 直接連用(即 -lxxx),中間不需有空格。
注意 需要提前安裝靜態庫(若是centos系統 命令:yum install glibc-static)
由此,就生成了 a.out 可執行文件:
動態庫:
demo1 項目(一個 C 語言多文件項目)與上述demo源碼相同
動態鏈接庫的創建
總的來說,動態鏈接庫的創建方式有 2 種。
1) 直接使用源文件創建動態鏈接庫,採用 gcc 命令實現的基本格式如下:
gcc -fpic -shared 源文件名... -o 動態鏈接庫名
其中,-shared 選項用於生成動態鏈接庫;-fpic(還可寫成 -fPIC)選項的功能是,令 GCC 編譯器生成動態鏈接庫(多個目標文件的壓縮包)時,表示各目標文件中函數、類等功能模塊的地址使用相對地址,而非絕對地址。這樣,無論將來鏈接庫被加載到內存的什麼位置,都可以正常使用。
例如,由 demo 項目中的 add.c、sub.c 和 div.c 這 3 個源文件生成一個動態鏈接庫,執行命令爲:
注意,動態鏈接庫的命令規則和靜態鏈接庫完全相同,只不過在 Linux 發行版系統中,其後綴名用 .so 表示;Windows 系統中,後綴名爲 .dll。
2) 先使用 gcc -c 指令將指定源文件編譯爲目標文件。仍以 demo 項目中的 add.c、sub.c 和 div.c 爲例,先執行如下命令:
注意,爲了後續生成動態鏈接庫並能正常使用,將源文件編譯爲目標文件時,也需要使用 -fpic 選項。
在此基礎上,接下來利用上一步生成的目標文件,生成動態鏈接庫:
以上 2 種操作,生成的動態鏈接庫是完全一樣的,任選一種即可。
動態鏈接庫的使用
通過前面章節的學習我們知道,動態鏈接庫的使用場景就是和項目中其它源文件或目標文件一起參與鏈接。以 demo 項目爲例,前面我們將 add.c、sub.c 和 div.c 打包到了 libmymath.so 動態鏈接庫中,此時該項目中僅剩 main.c 源程序文件,因此執行 demo 項目也就演變成了將 main.c 和 libmymath.so 進行鏈接,進而生成可執行文件。
注意,test.h 頭文件並不直接參與編譯,因爲在程序的預處理階段,已經對項目中需要用到的頭文件做了處理。
執行如下指令,即可藉助動態鏈接庫成功生成可執行文件:
注意,生成的 main 通常無法直接執行,例如:
可以看到,執行過程中無法找到 libmymath.so 動態鏈接庫。通過執行ldd main
指令,可以查看當前文件在執行時需要用到的所有動態鏈接庫,以及各個庫文件的存儲位置:
可以看到,main文件的執行需要 4 個動態鏈接庫的支持,其中就包括 libmymath.so,但該文件無法找到,因此 main執行會失敗。
運行由動態鏈接庫生成的可執行文件時,必須確保程序在運行時可以找到這個動態鏈接庫。常用的解決方案有如下幾種:
- 將鏈接庫文件移動到標準庫目錄下(例如 /usr/lib、/usr/lib64、/lib、/lib64);
- 在終端輸入
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
,其中 xxx 爲動態鏈接庫文件的絕對存儲路徑(此方式僅在當前終端有效,關閉終端後無效); - 修改~/.bashrc 或~/.bash_profile 文件,即在文件最後一行添加
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:xxx
(xxx 爲動態庫文件的絕對存儲路徑)。保存之後,執行source .bashrc
指令(此方式僅對當前登陸用戶有效)。
本操作系統(CentOS 6.5 64 位)中,只需要將 libmymath.so 庫文件移動 /usr/lib64 或者 /lib64 目錄下,即可使 main.exe 成功執行:
main即可運行
附錄2
CMake 使用
查看本機是否安裝cmake
安裝cmake(以centos6.5舉例)
首先讓我們從最簡單的代碼入手,先來體驗下cmake是如何操作的。編寫main.c,如下
然後在main.c相同目錄下編寫CMakeLists.txt,內容如下,
第一行意思是表示cmake的最低版本要求是2.8,我們安裝的是2.8.12;第二行是表示本工程信息,也就是工程名叫demo;第三行比較關鍵,表示最終要生成的elf文件的名字叫main,使用的源文件是main.c
在終端下切到main.c所在的目錄下,然後輸入以下命令運行
cmake .
再來看看目錄下的文件,
可以看到成功生成了Makefile,還有一些cmake運行時自動生成的文件。
然後在終端下輸入make並回車,
可以看到執行cmake生成的Makefile可以顯示進度,並帶顏色。再看下目錄下的文件,
可以看到我們需要的elf文件main也成功生成了,然後運行main,
運行成功!
注: 如果想重新生成main,輸入make clean就可以刪除main這個elf文件。
接下來進入稍微複雜的例子:在同一個目錄下有多個源文件。文件及源碼如下
修改CMakeLists.txt,在add_executable的參數裏把其他源文件加進來
然後重新執行cmake .生成Makefile
運行make,
然後運行重新生成的elf文件main
運行成功!
可以類推,如果在同一目錄下有多個源文件,那麼只要在add_executable裏把所有源文件都添加進去就可以了。但是如果有一百個源文件,再這樣做就有點坑了,無法體現cmake的優越性,cmake提供了一個命令可以把指定目錄下所有的源文件存儲在一個變量中,這個命令就是 aux_source_directory(dir var)。
修改CMakeLists.txt如下即可
cmake_minimum_required (VERSION 2.8)
project (demo)
aux_source_directory(. SRC_LIST)
add_executable(main ${SRC_LIST})