Linux中gcc編譯工具

m0_45269126發表於2020-10-17

一、用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編輯器的使用方法等等

相關文章