Linux中gcc編譯工具
Linux中gcc編譯工具
一、用gcc生成靜態庫和動態庫
1.第1步:編輯生成例子程式hello.h、hello.c和main.c
(1)在Linux中建立一個資料夾來儲存本次練習。終端的程式碼如下:
#mkdir test1
#cd test1
(2)接下來用vim編輯器編寫下列三個程式:
程式1:hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif //HELLO_H
程式2:hello.c
#include <stdio.h>
void hello(const char *name)
{
printf("Hello %s!\n", name);
}
main.c
#include "hello.h"
int main()
{
hello("everyone");
return 0;
}
2.第2步:將hello.c編譯成.o檔案
在終端輸入如下命令:
gcc -c hello.c
然後我們輸入ls命令檢視是否得到了hello.o檔案
3.第3步:由.o檔案建立靜態庫檔案
靜態庫檔名的命名規範是以 lib 為字首,緊接著跟靜態庫名,副檔名為.a。例如:我們將 建立的靜態庫名為 myhello,則靜態庫檔名就是 libmyhello.a。在建立和使用靜態庫時, 需要注意這點。建立靜態庫用 ar 命令。在系統提示符下鍵入以下命令將建立靜態庫檔案 libmyhello.a,命令如下:
ar -crv libmyhello.a hello.o
執行 ls 命令檢視結果:
4.第4步:在程式中使用靜態庫
靜態庫製作完了,如何使用它內部的函式呢?只需要在使用到這些公用函式的源程式中包 含這些公用函式的原型宣告,然後在用 gcc 命令生成目標檔案時指明靜態庫名,gcc 將會從 靜態庫中將公用函式連線到目標檔案中。注意,gcc 會在靜態庫名前加上字首 lib,然後追 加副檔名.a 得到的靜態庫檔名來查詢靜態庫檔案。 在程式 3:main.c 中,我們包含了靜態庫的標頭檔案 hello.h,然後在主程式 main 中直接呼叫 公用函式 hello。下面先生成目標程式 hello,然後執行 hello 程式看看結果如何。
輸入如下程式碼:
gcc main.c libmyhello.a -o hello
輸入ls命令檢視可執行檔案hello:
執行可執行檔案hello,輸入命令:
./hello
得到結果:
5.第5步:由.o檔案建立動態庫檔案
動態庫檔名命名規範和靜態庫檔名命名規範類似,也是在動態庫名增加字首 lib,但其 副檔名為.so。例如:我們將建立的動態庫名為 myhello,則動態庫檔名就是 libmyh ello.so。用 gcc 來建立動態庫。 在系統提示符下鍵入以下命令得到動態庫檔案libmyhello.so。命令如下:
gcc -shared -fPIC -o libmyhello.so hello.o
輸入ls命令檢視動態檔案是否生成:
6.第6步:在程式中使用動態庫
在程式中使用動態庫和使用靜態庫完全一樣,也是在使用到這些公用函式的源程式中包含 這些公用函式的原型宣告,然後在用 gcc 命令生成目標檔案時指明動態庫名進行編譯。我 們先執行 gcc 命令生成目標檔案,再執行它看看結果。命令如下:
gcc main.c libmyhello.so -o hallo
用ls命令來檢視是否生成了hallo檔案:
用./hallo命令執行hallo:
出現了error.原因是因為找不到動態庫檔案 libmyhello.so。程式在執行時, 會在/usr/lib 和/lib 等目錄中查詢需要的動態庫檔案。若找到,則載入動態庫,否則將提 示類似上述錯誤而終止程式執行。我們將檔案 libmyhello.so 複製到目錄/usr/lib 中,再試試。
輸入以下命令:
出現許可權不夠的問題,在前面加入sudo.
輸入密碼後成功。我們再用./hallo執行一下hallo檔案:
二、靜態庫.a與.so庫檔案的生成與使用
1.建立一個檔案儲存本次練習
mkdir test2
cd test2
2.用vim編輯如下四個檔案A1.c、A2.c、A.h、test.c:
A1.c
#include <stdio.h>
void print1(int arg)
{
printf("A1 print arg:%d\n",arg);
}
A2.c
#include <stdio.h>
void print2(char *arg)
{
printf("A2 printf arg:%s\n", arg);
}
A.h
#ifndef A_H
#define A_H
void print1(int);
void print2(char *);
#endif
test.c
#include <stdlib.h>
#include "A.h"
int main()
{
print1(1);
print2("test"); e
xit(0);
}
3.靜態庫.a檔案的生成與使用
(1)生成目標檔案
使用命令如下:
gcc -c A1.c A2.c
得到的結果如下:
(2)生成靜態庫.a檔案
使用命令如下:
ar crv libafile.a A1.o A2.o
得到的結果如下:
(3)使用.a 庫檔案,建立可執行程式
(若採用此種方式,需保證生成的.a 檔案與.c 檔案保 存在同一目錄下,即都在當前目錄下)
輸入如下命令:
gcc -o test test.c libafile.a
./test
得到如下結果
4.共享庫.so檔案的生成與使用
(1)生成目標檔案(xxx.o() 此處生成.o 檔案必須新增"-fpic"(小模式,程式碼少),否則在生成.so 檔案時會出錯),命令如下:
gcc -c -fpic A1.c A2.c
(2)生成共享庫.so 檔案
命令如下:
gcc -shared *.o -o libsofile.so
(3)使用.so 庫檔案,建立可執行程式
命令如下:
gcc -o test test.c libsofile.so
4)我們執行,卻出現瞭如下的問題:
這是由於 linux 自身系統設定的相應的設定的原因,即其只在/lib and /usr/lib 下搜尋對應 的.so 檔案,故需將對應 so 檔案拷貝到對應路徑.
(5)我們將對應的.so檔案拷貝到對應路徑,命令:
sudo cp libsofile.so /usr/lib
再次執行:
成功。
同時可直接使用 gcc -o test test.c -L. -lname,來使用相應庫檔案 其中,-L.:表示在當前目錄下,可自行定義路徑 path,即使用-Lpath 即可。 -lname:name:即對應庫檔案的名字(除開 lib),即若使用 libafile.a,則 name 為 afile; 若要使用 libsofile.so,則 name 為 sofile)。
gcc的動態庫與靜態庫使用的例項
用vim建立主程式main1.c和子程式sub1.c
主程式main1.c
#include<.stdio.h>
#include"sub1.c"
void main()
{
int x=9,y=2;
float z=0;
z=x2x(x,y);
printf("%f\n",z);
}
子程式sub1.c
float x2x(int a,int b)
{
float s=0;
s=a/b;
return s;
}
1.新增x2y函式,功能為將兩個數相加並返回值
用vim編輯器編寫如下:
2.修改主函式
3.將這3個函式分別寫成單獨的3個 .c檔案,並用gcc分別編譯為3個.o 目標檔案:
4.將x2x、x2y目標檔案用 ar工具生成1個 .a 靜態庫檔案:
5.然後用 gcc將 main函式的目標檔案與此靜態庫檔案進行連結,生成最終的可執行程式:
6.此時的可執行檔案大小:
四、 Linux GCC常用的命令
1.一步到位的編譯指令
gcc test.c -o test
示例程式如下:
輸入上面的命令,我們執行試一下:
實質上,上述編譯過程是分為四個階段進行的,即預處理(也稱預編譯,Preprocessing)、編譯 (Compilation)、彙編 (Assembly)和連線(Linking)
2.預處理
命令如下:
gcc -E test.c -o test.i 或 gcc -E test.c
3.編譯為彙編程式碼
命令如下:
gcc -S test.i -o test.s
4.彙編
命令如下:
gcc -c test.s -o test.o
5.連線
命令如下:
gcc test.o -o test
五、GCC編譯器背後的故事
1.準備工作
由於 GCC 工具鏈主要是在 Linux 環境中進行使用,因此本文也將以 Linux 系統作 為工作環 境。為了能夠 演示編譯的整個 過程,先建立一 個工作目錄 test0,然後 用文字編輯器生成一個 C 語言編寫的簡單 Hello.c 程式為示例,其原始碼如下所 示:
#include <stdio.h>
int main(void)
{
printf("Hello World! \n");
return 0;
}
2.編譯過程
2…1預處理
預處理的過程主要包括以下過程:
(1) 將所有的#define 刪除,並且展開所有的巨集定義,並且處理所有的條件預編 譯指令,比如#if #ifdef #elif #else #endif 等。
(2) 處理#include 預編譯指令,將被包含的檔案插入到該預編譯指令的位置。
(3) 刪除所有註釋“//”和“/* */”。
(4) 新增行號和檔案標識,以便編譯時產生除錯用的行號及編譯錯誤警告行號。
(5) 保留所有的#pragma 編譯器指令,後續編譯過程需要使用它們。
使用 gcc 進行預處理的命令如下:
將原始檔 hello.c 檔案預處理生成 hello.i ;GCC 的選項-E 使 GCC 在進行完預處理後即停止
2.2編譯
編譯過程就是對預處理完的檔案進行一系列的詞法分析,語法分析,語義分析及 優化後生成相應的彙編程式碼。
使用 gcc 進行編譯的命令如下:
2.3彙編
彙編過程呼叫對彙編程式碼進行處理,生成處理器能識別的指令,儲存在字尾為.o 的目標檔案中。由於每一個彙編語句幾乎都對應一條處理器指令,因此,彙編相 對於編譯過程比較簡單,通過呼叫 Binutils 中的彙編器 as 根據彙編指令和處理 器指令的對照表一一翻譯即可。 當程式由多個原始碼檔案構成時,每個檔案都要先完成彙編工作,生成.o 目標 檔案後,才能進入下一步的連結工作。注意:目標檔案已經是最終程式的某一部 分了,但是在連結之前還不能執行。
使用 gcc 進行彙編的命令如下:
gcc -c hello.s -o hello.o
2.4連結
連結也分為靜態連結和動態連結,其要點如下:
(1) 靜態連結是指在編譯階段直接把靜態庫加入到可執行檔案中去,這樣可執行 檔案會比較大。連結器將函式的程式碼從其所在地(不同的目標檔案或靜態鏈 接庫中)拷貝到最終的可執行程式中。為建立可執行檔案,連結器必須要完 成的主要任務是:符號解析(把目標檔案中符號的定義和引用聯絡起來)和 重定位(把符號定義和記憶體地址對應起來然後修改所有對符號的引用)。
(2) 動態連結則是指連結階段僅僅只加入一些描述資訊,而程式執行時再從系統 中把相應動態庫載入到記憶體中去。
在 Linux 系 統中,gcc 編 譯鏈 接時 的動 態庫 搜尋 路徑 的 順序 通常 為:首 先從 gcc 命 令的 參 數-L 指 定的 路徑 尋找 ;再 從環 境變 量 LIBRARY_PATH 指 定的 路徑 定址;再 從默 認路 徑 /lib、/usr/lib、 /usr/local/lib 尋找 。
在 Linux 系 統中,執 行二 進位制 檔案 時的 動態 庫搜 索路 徑的 順序 通常 為:首 先搜 索編 譯目 標 程式碼 時指 定的 動態 庫搜 索路 徑;再 從環 境變 量 LD_LIBRARY_PATH 指 定的 路徑 定址;再 從 配置 檔案/etc/ld.so.conf 中 指定 的動 態庫 搜尋 路徑 ;再 從默 認路 徑/lib、/usr/lib 尋找 。
在 Linux 系統 中, 可以 用 ldd 命令 檢視 一個 可執 行程 序依 賴的 共享 庫。
由於連結動態庫和靜態庫的路徑可能有重合,所以如果在路徑中有同名的靜態庫檔案和動 態庫檔案,比如 libtest.a 和 libtest.so,gcc 連結時預設優先選擇動態庫,會連結 libtest.so,如果要讓 gcc 選擇連結 libtest.a 則可以指定 gcc 選項-static,該選項會強 制使用靜態庫進行連結。以 Hello World 為例: 如果使用命令“gcc hello.c -o hello”則會使用動態庫進行連結,生成的 ELF 可執行檔案的大小(使用 Binutils 的 size 命令檢視)和連結的動態庫 (使用 Binutils 的 ldd 命令檢視)如下所示
如 果 使 用 命 令 “ gcc -static hello.c -o hello”則 會 使 用 靜 態 庫 進 行 鏈 接 , 生成的 ELF 可執行檔案的大小(使用 Binutils 的 size 命令檢視)和連結的 動態庫(使用 Binutils 的 ldd 命令檢視)如下所示:
連結器連結後生成的最終檔案為 ELF 格式可執行檔案,一個 ELF 可執行檔案通常 被連結為不同的段,常見的段譬如.text、.data、.rodata、.bss 等段。
3.分析 ELF 檔案
3.1ELF 檔案的段
ELF 檔案格式,位於 ELF Header 和 Section Header Table 之間的都 是段(Section)。一個典型的 ELF 檔案包含下面幾個段:
.text:已編譯程式的指令程式碼段。 .
rodata:ro 代表 read only,即只讀資料(譬如常數 const)。 .
data:已初始化的 C 程式全域性變數和靜態區域性變數。
.bss:未初始化的 C 程式全域性變數和靜態區域性變數。 .
debug:除錯符號表,偵錯程式用此段的資訊幫助除錯。
可以使用 readelf -S 檢視其各個 section 的資訊如下:
3.2反彙編 ELF
由於 ELF 檔案無法被當做普通文字檔案開啟,如果希望直接檢視一個 ELF 檔案包 含的指令和資料,需要使用反彙編的方法。
使用 objdump -D 對其進行反彙編如下:
六、nasm編譯例項
as彙編編譯器針對的是AT&T彙編程式碼風格,Intel風格的彙編程式碼則可以用nasm彙編編譯器編譯生成執行程式,在Ubuntu在下載nasm。命令入下:
sudo apt install nasm
用gedit編輯一個hello.asm檔案,程式碼如下:
然後用nasm編輯.asm檔案,命令入下:
最後我們用ld連結器把hello.o生成可執行檔案hello:
然後我們執行一下:
結果正確,接著我們來看看一下此時可執行檔案的大小:
與之前gcc直接編譯的可執行檔案的大小相比較:
七、Linux 系統中終端程式最常用的游標庫(curses)的主要函式功能
1.從螢幕讀取
chtype inch(void); //返回游標位置字元
int instr(char *string); //讀取字元到string所指向的字串中
int innstr(char *string, int numbers);//讀取numbers個字元到string所指向的字串中
2.清除螢幕
int erase(void);//在螢幕的每個位置寫上空白字元
int clear(void);//使用一個終端命令來清除整個螢幕,相當於vi內的Ctrl+L
//內部呼叫了clearok來執行清屏操作,(在下次呼叫refresh時可以重現螢幕原文)
int clrtobot(void);//清除游標位置到螢幕結尾的內容
int clrtoeol(void);//清除游標位置到該行行尾的內容
3.視窗移動和更新螢幕
int mvwin(WINDOW *win, int new_y, int new_x); //移動視窗
int wrefresh(WINDOW *win);
int wclear(WINDOW *win);
int werase(WINDOW *win);
//類似於上面的refresh, clear, erase,但是此時針對特定視窗操作,而不是stdcur
int touchwin(WINDOW *win); //指定該視窗內容已改變、
//下次wrefresh時,需重繪視窗。利用該函式,安排要顯示的視窗
int scrollok(WINDOW *win, bool flag); //指定是否允許視窗卷屏
int scroll(WINDOW *win); //把視窗內容上卷一行
八、curses的標頭檔案的安裝目錄
首先我們用下面命令安裝:
sudo apt-get install libncurses5-dev
然後我們用 whereis 命令檢視curses.h安裝在哪裡
九、遊客身份體驗一下即將絕跡的遠古時代的 BBS
1.在 win10 系統中,“控制皮膚”–>“程式”—>“啟用或關閉Windows功能”
2.啟用 “telnet client” 和"適用於Linux的Windows子系統"(後面會使用)
3.然後開啟一個cmd命令列視窗,命令列輸入 telnet bbs.newsmth.net,即可體驗
十、Linux 環境下C語言編譯實現貪吃蛇遊戲
參考網站連結:http://www.linuxidc.com/Linux/2011-08/41375.htm
將上面的程式碼複製到linux的檔案中,並在終端輸入
gcc mysnake1.0.c -lcurses -o mysnake1.0,最後執行可執行檔案,就可以體驗一下貪吃蛇遊戲了。
十一、總結
通過本次實驗,小編我學習到了如何用gcc生成靜態庫和動態庫、靜態庫.a與.so庫檔案的生成與使用、
gcc編譯工具集中各軟體的用途、EFF檔案格式、
Linux GCC常用命令、nasm編輯器的使用方法等等
相關文章
- linux下的gcc編譯LinuxGC編譯
- gcc編譯GC編譯
- Linux gcc編譯器安裝LinuxGC編譯
- linux下gcc/g++編譯用法LinuxGC編譯
- FreeBSD中的GNU C編譯器--編譯器GCC(轉)編譯GC
- opensuse linux安裝gcc編譯環境LinuxGC編譯
- GCC編譯選項GC編譯
- GCC 編譯選項GC編譯
- gcc 編譯器與 clang 編譯器GC編譯
- Linux下如何用GCC編譯動態庫LinuxGC編譯
- gcc編譯cpp檔案GC編譯
- GCC編譯器的使用GC編譯
- Linux上安裝GCC編譯器過程(轉)LinuxGC編譯
- gcc 編譯多個原始檔GC編譯
- gcc最佳編譯引數(轉)GC編譯
- 安裝 GCC 編譯器(轉)GC編譯
- 探索gcc編譯最佳化細節 編譯器最佳化gcc -o3GC編譯
- GCC編譯過程(預處理->編譯->彙編->連結)GC編譯
- GCC 中的編譯器堆疊保護技術GC編譯
- 原創 【CentOS Linux 7】實驗4【gcc編譯器】CentOSLinuxGC編譯
- 開源編譯器 GCC 跨越 5.0,釋出 GCC 5.1編譯GC
- GCC 使用指南及gcc最佳編譯引數(轉)GC編譯
- GCC編譯和連結過程GC編譯
- GCC編譯器背後的故事GC編譯
- linux 改變GCC編譯器的位元組對齊方式LinuxGC編譯
- 記錄一次gcc的編譯GC編譯
- 編譯器GCC與Clang的異同編譯GC
- gcc編譯器小知識FAQ(轉)GC編譯
- linux成長之路(gcc編譯器、靜態庫、動態庫)LinuxGC編譯
- Linux環境下, 原始碼編譯安裝詳解 (編譯CMake 3.15 和 gcc 5.3.0 為例)Linux原始碼編譯GC
- gcc 從語言編譯全過程 預處理---->編譯---->彙編----->連結GC編譯
- 如何在64位Linux系統上用gcc編譯32位程式LinuxGC編譯
- 編譯linux kernel預裝工具list編譯Linux
- 32位支援:使用 GCC 交叉編譯GC編譯
- redhat 6.5 gcc編譯器初識之一RedhatGC編譯
- 記一次編譯GCC的經歷編譯GC
- gcc 和 g++ 的聯絡和區別,使用 gcc 編譯 c++GC編譯C++
- CentOS7編譯和安裝GCC7.5CentOS編譯GC