1. 程式人生 > >C語言靜態編譯和動態編譯

C語言靜態編譯和動態編譯

文章目錄

概述

在Linux系統中,應用程式表現為兩種檔案,一種是可執行檔案, 另一種是指令碼檔案

可執行檔案

可執行檔案是計算機可以直接執行的程式,與windows系統的.exe程式相似,它是由原始碼經過一定的手段翻譯成計算機能夠讀懂的二進位制編碼,由計算機直接去執行,這個翻譯的過程就稱之為編譯

指令碼檔案

指令碼檔案是一系列指令的組合,由另一個直譯器來執行,相當於windows系統的.bat檔案。
與windows系統不同之處在於,windows系統通常由字尾來決定該檔案是什麼檔案,而Linux系統則與字尾無關,而是由檔案的許可權來決定的。可以有後綴,也可以沒有後綴。
關於檔案的許可權相關,會在後續的文章中展開討論,此處就不多做說明了。

Linux應用程式目錄結構

一級目錄 二級目錄 三級目錄 說明
/bin 二進位制檔案目錄,用於存放啟動時用到的程式
/usr bin 使用者二進位制目錄,用於存放使用者使用的標準程式
/usr local bin 本地二進位制目錄,用於存放軟體安裝的程式
/usr sbin root使用者登入後的PATH管理目錄
/opt 第三方應用程式安裝目錄

編譯器初探

將原始碼轉換成計算機能夠識別的二進位制編碼的過程稱之為編譯,編譯使用的工具即為編譯器。
在POSIX相容的系統中,C語言編譯器被稱為c89,簡稱為cc。在Unix系統中,普遍使用的都是cc編譯器。而我們這裡討論的gcc編譯器是隨著Linux發行版一起提供的,全稱是GNU C編譯器。

普通程式的編譯

關於編譯器的使用,不妨通過一個簡單的例子來說明。
我們簡單的寫出一個C程式如下,該程式即程式設計者的入門程式hello.c

#include <stdio.h>

int main(void)
{
    printf("hello world\n");
    return 0;
}

該程式的編譯命令如下:

$ gcc -o hello hello.c

執行以上命令後,會在該目錄下生成一個名為hello的可執行檔案,使用以下命令即可執行該程式:

$ ./hello
hello world

下面來對該編譯命令做一下說明:

  • -o 緊跟著的是指定編譯後可執行檔案的名稱,上述命令中,-o hello
    即指定hello為可執行檔案。如果不指定-o, 則預設生成一個叫做a.out的可執行檔案。即:上述編譯命令直接寫成gcc hello.c也是沒有問題的,不過可執行檔案變成了a.out,執行該程式就是:
$ ./a.out
hello world
  • hello.c 是需要編譯的原始檔,該檔案是開發者寫好的程式碼,可以有多個,也可以只有一個。
  • 在執行應用程式中,./代表的是當前目錄,如果不加./,系統會預設到PATH環境變數中去尋找,如果找不到則會報錯,如果恰巧找到了一個同名的可執行程式,程式會執行,但得到的結果並不是我們所需要的,因為它執行的並不是我們編譯好的可執行程式。

連結標頭檔案

使用C語言進行程式設計時,需要連結標頭檔案進行系統及庫函式的呼叫,這時候就需要連結標頭檔案。Linux系統常用的系統標頭檔案都存放在/usr/include目錄下,該目錄能夠被gcc編譯器自行檢索到並主動連結到程式裡。
但對於有些使用者自定義的標頭檔案,編譯器並不能搜尋到其目錄,這時候就需要在編譯的時候去指定需要連結的標頭檔案的路徑。連結標頭檔案的命令是在編譯的時加上大寫的-I,後面緊跟標頭檔案的路徑。如下所示:

$ gcc -o fred -I/usr/openwin/include fred.c

注意:-I和標頭檔案路徑之間不要有空格。

庫檔案

庫是一組預先編譯好的函式組合,其特點是可重用性好,通常由一組相互關聯的函式組成,用以執行某項任務。
系統的標準庫函式存放在/lib或者/usr/lib目錄下,與標頭檔案必須以.h作為字尾一樣,庫函式同樣也需要遵循一些規範。
庫函式必須以lib作為開頭,以.a或者.so作為結尾。其中,.a代表靜態函式庫,.so代表動態函式庫。比較典型的比如:libc.alibm.a即代表標準C函式庫和標準數學函式庫。

靜態編譯

由於歷史原因,編譯器僅能識別標準c函式庫,部分系統庫函式,即使已經放在/usr/lib目錄下,編譯器仍然不能夠識別,這時候就需要在編譯的時候告訴編譯器使用的是哪個庫函式,編譯時可以通過給出完整的靜態庫絕對路徑的方式,或使用-l標誌來告訴編譯器你所使用的靜態庫函式。如:

$ gcc -o fred fred.c /usr/lib/libm.a
$ gcc -o fred fred.c -lm

以上兩個命令達到的效果是一樣的,可以通過ls /usr/lib命令來檢視系統的庫函式。
除此之外,使用者也可以自己定義庫函式,連結非標準位置的自定義庫函式可以通過大寫的-L標誌來實現,命令如下:

$ gcc -o xllfred  -L/usr/openwin/lib xllfred.c -lxll  

建立靜態庫

使用ar命令(archive)可以很容易地建立屬於自己的靜態庫。ar命令一般對.o的目標檔案進行操作,目標檔案可以由gcc -c命令得到。
下面就以一個具體的例子來說明一下。
首先,我們有如下兩個源程式檔案:

$ ls
main.c  print.c test.h
$ pg print.c
#include <stdio.h>

int print()
{
    printf("Hello world\n");
    return 0;
}

$ pg main.c                                                        
#include "test.h"

int main()
{
    print();
    return 0;
}

$ pg test.h
int print();

先通過gcc -c命令將其編譯成.o檔案:

$ gcc -c *.c                                                        
$ ls 
main.c  main.o  print.c  print.o  test.h

我們可以看到兩個.o的目標檔案已經成功生成。
這時候,如果我們使用以下命令,是可以直接編譯成功的:

$ gcc -o test *.o
$ ls
main.c  main.o  print.c  print.o  test.h test
$ ./test
Hello world

但是這裡由於我們是要建立靜態庫,所以可以使用ar命令來建立一個歸檔檔案:

$ ar crv libtest.a print.o
a - test2.o
$ ls
libtest.a  main.c  main.o  print.c  print.o  test  test.h

可以看到,靜態庫libtest.a已經成功建立,這時,還需要使用ranlib命令來生成一個內容表,這一步在Unix系統中必不可少,但在Linux中,當使用的是GUN開發工具時,這一步可以省略。以上步驟完成後,即可以使用下面的命令來編譯程式:

$ ranlib libtest.a
$ gcc -o testa main.o -L./ -ltest
$ ls
libtest.a  main.c  main.o  print.c  print.o  test  testa  test.h
$ ./testa
Hello world

通過以上案例,可以發現得到的效果其實是一樣的。
當然,也可以使用以下命令,得到相同的效果(這裡因為沒有連結標頭檔案,會報一個錯,但是結果沒有影響):

$ gcc -o testb main.c -L./ -ltest  
$ ls
libtest.a  main.c  main.o  print.c  print.o  test  testa  testb  test.h
$ ./testb
Hello world

動態編譯

靜態庫有一個侷限性,當同時執行多個程式,並且這些程式都去呼叫同一個靜態庫函式時,就會出現多個函式庫副本,會佔用大量的虛擬記憶體和磁碟空間,這時候,動態庫(又叫共享庫)可以解決這個問題。
當程式使用動態庫時,其連結方式是這樣的:程式本身不包含程式碼,而是引用執行時可訪問的共享程式碼。即,只有在必要的時候,才會載入到記憶體中。
即,可以簡單的理解如下:靜態庫在編譯時就已經把記憶體給分配好了,每一次呼叫,都會分配一份記憶體。而動態庫是在執行時才會去訪問共享程式碼分配的記憶體,永遠只會存在一份,因此不會出現記憶體浪費。
動態庫的格式是以.so結尾,比如典型的有/lib/libm.so
裝載和解析動態庫的工具是ld.so,如果需要搜尋標準位置以外的動態庫,則需要在/etc/ld.so.conf中進行配置,然後執行ldconfig來處理它。
可以使用ldd命令來檢視程式所需要的動態庫檔案,如:

$ ldd test
        linux-vdso.so.1 =>  (0x00007ffc0e4a8000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd26176b000)
        /lib64/ld-linux-x86-64.so.2 (0x000056170c352000)

靜態庫與動態庫的一個明顯的區別在於:使用靜態庫編譯完成後,靜態庫被刪除,不會影響可執行檔案的執行,但是如果刪除了動態庫,可執行檔案執行就會報錯。

建立動態庫

仍然是看上面這個程式:

$ ls
libtest.a  main.c  main.o  print.c  print.o  test  testa  testb  test.h

動態編譯的命令是針對共享程式碼進行操作的,命令如下:

$ gcc -shared -fPIC -o libtest.so print.c
$ ls
libtest.a  libtest.so  main.c  main.o  print.c  print.o  test  testa  testb  test.h

這樣libtest.so就已經生成了,編譯時連結如下:

$ gcc -o testc main.c ./libtest.so
$ ls
libtest.a  libtest.so  main.c  main.o  print.c  print.o  test  testa  testb  testc  test.h
$ ./testc
Hello world

程式的編譯過程

C程式的編譯共有四個步驟組成,分別為:

  • 預處理
  • 編譯
  • 彙編
  • 連結
    編譯過程

為了可以直觀的說明這個過程,我們不妨使用一個案例演示一下:
假設有兩個檔案,標頭檔案hello.h 和原始檔 hello.c:

$ ls
hello.c  hello.h
$ pg hello.h
# include<stdio.h>
$ pg hello.c
#include "hello.h"

int main()
{
    printf("Hello world\n");
    return 0;
}

預處理

預處理主要完成的工作是去註釋、標頭檔案包含和巨集替換,該步驟並不會檢查語法。預處理命令為:

$ gcc -E -I./ hello.c -o hello.i                
$ ls
hello.c  hello.h  hello.i

預處理完成後,會由.c檔案生成一個.i檔案。

編譯

編譯步驟完成的功能是將預處理之後的程式轉換為組合語言程式碼。編譯命令如下:

$ gcc -S  hello.i -o hello.s 
$ ls
hello.c  hello.h  hello.i  hello.s

這一步會將.i檔案生成為.s格式的組合語言程式碼。在該步驟中會檢查語法,如果語法有錯誤,會在這一步報出來。

彙編

彙編就是將組合語言程式處理成二進位制目標檔案。其命令如下:

$ gcc -c  hello.s -o hello.o   
$ ls
hello.c  hello.h  hello.i  hello.o  hello.s

或者使用以下命令:

$ as  hello.s -o hello.o       
$ ls
hello.c  hello.h  hello.i  hello.o  hello.s

這兩個命令實際是等價的。

連結

連結是最後一步,即將多個目標檔案,或者靜態庫檔案(.a)以及動態庫檔案(.so)連結成最後的可執行程式的過程。其命令如下:

$ gcc -o hello hello.o  
$ ls
hello  hello.c  hello.h  hello.i  hello.o  hello.s
$ ./hello
Hello world

如果使用了動態庫,可能使用到ld連結命令。

通過以上分析發現,看似簡單的一條編譯命令,其內部完成的步驟是相當複雜的,能夠理解編譯器編譯的原理,對程式設計是有相當大的幫助的。當然實際開發過程中,只需要使用以下編譯命令一步到位,它完成了以上所有四步命令的所有過程:

$ gcc -o hello hello.c
$ ./hello
hello world

結語

關於gcc編譯命令還有很多,這裡就不多做介紹了,如果有需要,可以通過man gcc或者info gcc來獲取幫助。

相關推薦

c語言------靜態動態庫的建立使用

一.  靜態庫       1. 靜態庫的建立           gcc  -c  原始檔 . c                                 //為了得到原始檔的 . o 檔

C語言靜態編譯動態編譯

文章目錄概述可執行檔案指令碼檔案編譯器初探庫檔案靜態編譯建立靜態庫動態編譯建立動態庫預處理編譯彙編連結結語 概述 在Linux系統中,應用程式表現為兩種檔案,一種是可執行檔案, 另一種是指令碼檔案。 可執行檔案 可執行檔案是計算機可以直接執行的程式,與windo

靜態編譯動態編譯(libdll)

weibo docs p s 獎章 com 動態編譯 lan doc sin u2瓢剮JZP匪媳51http://www.docin.com/app/user/userinfo?userid=179185213 0宰9U拔7853E5噸渭3http://www.docin

Linux驅動靜態編譯動態編譯方法詳解

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

C語言檔案操作 編譯命令

//read檔案 int main(){ char *path = "D:\\friends.txt"; FILE *fp = fopen(path, "r"); char buff[500]; while (fgets(buff,50,fp)){ printf("%s\n", b

apache靜態編譯動態編譯

Apache擁有4層結構,從核心到外層的module。而外層的module可以用通過靜態和動態兩種方式與Apache共同工作。這也就引入下文的“動態”和“靜態”兩種編譯安裝方式: 靜態編譯:  編譯的時候,所有的模組自己編譯進 httpd 這個檔案中 ,啟動Apache的時

靜態編譯動態編譯區別

靜態函式庫 一般副檔名為(.a),這類的函式庫通常副檔名為libxxx.a 。 這類函式庫在編譯的時候會直接整合到程式中,所以利用靜態函式庫編譯成的檔案會比較大,這類函式庫最大的優點就是編譯成功的可執行檔案可以獨立執行,而不再需要向外部要求讀取函式哭的內容;但是從升級難易

一、C語言靜態變數靜態函式

static C語言        C語言程式可以看成由一系列外部物件構成,這些外部物件可能是變數或函式。而內部變數是指定義在函式內部的函式引數及變數。外部變數定義在函式之外,因此可以在許多函式中使用。由於C語言不允許在一個函式中定義其

C++】靜態分配動態分配 堆棧(詳解)

但是,在使用陣列的時候,總有一個問題困擾著我們:陣列應該有多大?在很多的情況下,你並不能確定要使用多大的陣列,比如上例,你可能並不知道我們要定義的這個陣列到底有多大,那麼你就要把陣列定義得足夠大。這樣,你的程式在執行時就申請了固定大小的你認為足夠大的記憶體空間。即使你知道你想利用的空間大小,但是如果因為某種特

c++呼叫靜態動態

呼叫靜態庫 第一步把動態庫放到檔案裡 第二部連線上靜態庫 #pragma comment(lib,"靜態庫.lib") 呼叫動態庫 第一步 typedef int( *getMaxNum)(i

c++中靜態動態庫的建立與連結

2、動態庫      在使用動態庫的時候,往往提供兩個檔案:一個引入庫(.lib)和一個DLL(.dll)檔案。雖然引入庫的字尾也是.lib ,但是動態庫的引入庫檔案和靜態庫檔案有著本質的區別,對一個DLL來說,其引入庫檔案(.lib)包含DLL匯出的函式和變數的符號名,而.dll檔案包含該DLL實際的函式

Windows下用DEV C++建立靜態動態

如何在DEV下建立屬於自己的靜態和動態庫呢?(2018.6.6)一、新建專案: 二、在庫裡面加入你的函式:(編譯) 三、編譯後到工程目錄下檢視是否產生了字尾位.a的檔案(與你的專案名是一致的) 四、使用這個靜態連結庫:1.首先新建一個.h檔案到你的專案裡面,.h主要是宣告你剛

g++ 編譯連結C++程式碼, 生成與使用靜態動態

例如我有A.cpp、A.h、main.cpp 三個檔案 編譯連結C++程式碼: 第一步:g++ -c A.cpp main.cpp 這樣就可以編譯A.cpp和main.cpp的程式碼生成A.o和main.o檔案【因為A.cpp包含了A.h的標頭檔案,所以一般編譯時

[C/C++] VS2017編譯libcurl靜態動態

開啟 VS2013 x86 本機命令工具提示,進入libcurl目錄winbuild cd D:\MyWork\cpp\third\curl-7.61.1\winbuild 執行命令 // r

ffmpeg的ubuntu的編譯過程(編譯靜態動態庫)

ffmpeg第一步源碼下載通過git下載git clone https://git.ffmpeg.org/ffmpeg.git ffmpeg或者直接下載wget http://ffmpeg.org/releases/ffmpeg-3.3.tar.bz2如果是下載的ffmpeg-3.3.tar.bz2 需要進

linux+vs2013編譯靜態動態

cal 控制 文件 urn 運行時 names c++ spec using Linux下創建與使用靜態庫 Linux靜態庫命名規則 Linux靜態庫命名規範,必須是"lib[your_library_name].a":lib為前綴,中間是靜態庫名,擴展名為.a。 創建靜態

Qt靜態編譯發布動態編譯發布

打包 mage 5.6 動態編譯 完成 工具 開始 選擇 ref 靜態編譯發布 你寫了一個小型Qt程序,發布的時候不想要一大堆dll文件,就只想打包成一個exe文件,那麽就需要用到靜態編譯。 下面的教程就是Qt靜態編譯環境配置 Qt5.6靜態編譯包下載地址 1.下載Qt

CMakelist編譯靜態動態

從程式碼模組化的角度,往往需要將一個系統工程拆分成很多小的模組,編譯成庫函式也是很好的方法。 編譯庫函式的時候,可以選擇編譯成靜態庫或者動態庫。靜態庫對應.a檔案,動態庫對應.so檔案。 還是以氣泡排序為例加以說明。 編寫StaticBubble.h標頭檔

C語言函式篇(五)靜態動態庫的建立使用

使用庫函式是原始碼的一種保護?我猜的. 庫函式其實不是新鮮的東西,我們一直都在用,比如C庫. 我們執行pringf() 這個函式的時候,就是呼叫C庫的函式.   下面記錄靜態庫和動態庫的生成和使用.   靜態庫:libxxx.a 動態庫:libxxx.so  

Linux靜態動態庫的命名規則編譯連結

1、Linux靜態庫和動態庫的命名規則 靜態函式庫 靜態庫的名字一般是libxxx.a,利用靜態庫編譯生成的檔案比較大,因為整個靜態庫所有的資料都會被整合進目的碼中。 a)優點 編譯後,可執行檔案不需要外部支援; b)缺點 生成的可執行程式大;靜態庫改變了,就