linux成長之路(gcc編譯器、靜態庫、動態庫)

LinJM-機器視覺發表於2015-04-15

Jeremy Lin

GCC簡介

GCC(GNU Complier Collection)是GNU推出的功能強大、效能優越的多平臺編譯器套件,它包括了C、C++、Objective-C、Fortran、Java、Ada和Go語言的前端,也包括了這些語言的庫,當前最新的版本是GCC 5.1。GCC可以在多種硬體平臺上編譯出可執行程式,其執行效率與一般的編譯器相比平均效率要高20%-30%。GCC編譯器能將C、C++語言源程式、匯程式程式和目標程式編譯、連線成可執行檔案,如果沒有給出可執行檔案的名字,GCC將生成一個名為 a.out 的檔案。在Linux系統中,可執行檔案沒有統一的字尾,系統從檔案的屬性來區分可執行檔案和不可執行檔案。而GCC通過字尾來區分輸入檔案的類別,下面我們來介紹GCC所遵循的部分約定規則:

.c的檔案                         C語言原始碼檔案;

.a的檔案                        由目標檔案構成的檔案庫檔案;

.C,.cc或.cxx 的檔案     C++原始碼檔案且必須要經過預處理;

.h的檔案                         程式所包含的標頭檔案;

.i 的檔案                         C原始碼檔案且不應該對其執行預處理;

.ii的檔案                         C++原始碼檔案且不應該對其執行預處理;

.m的檔案                        Objective-C原始碼檔案;

.mm的檔案                     Objective-C++原始碼檔案;

.o的檔案                         編譯後的目標檔案;

.s的檔案                         組合語言原始碼檔案;

.S的檔案                         經過預編譯的組合語言原始碼檔案。


GCC執行過程

雖然我們稱gcc是C語言的編譯器,但適用gcc由C語言原始碼生成可執行檔案的過程不僅僅是編譯的過程,而是要經歷4個相互關聯的步驟:

(1)預處理(Preprocessing)

(2)編譯(Compilation)

(3)彙編(Assembly)

(4)連線(Linking)


(1)預處理階段:輸入的是C語言的原始檔,通常為*.c。它們通常帶有.h之類的標頭檔案。這個階段主要是處理原始檔中的#ifdef、#include和#define命令。該階段會生成一箇中間檔案*.i,但實際工作中通常不會專門生成這種檔案,因為基本上用不到;若非要生成這種檔案,可以利用下面的命令:

gcc -E hello.c -o hello.i


hello.c的原始碼如下:

#include <stdio.h>
int main(int argc, char**argv)
{
	printf("Hello Linux\n");
	return 0;
}


生成的hello.i的結果如下(很長,所以只擷取一部分看看)


……

……



(2)編譯階段:輸入的是中間檔案*.i,編譯後生成組合語言檔案*.s。這個階段對應的GCC命令如下:

gcc -S hello.i -o hello.s

如下所示:



(3)彙編階段:輸入的是彙編檔案*.s,輸出的轉換生成的機器語言*.o。這個階段對應的命令如下:

gcc -c hello.s -o hello.o


(4)連線階段:將輸入的機器程式碼檔案*.s,彙整合一個可執行的二進位制檔案。命令如下:

gcc hello.o -o helloX




GCC基本用法與選項

在使用GCC編譯器的時候,我們必須給出一系列必要的呼叫引數和檔名稱。GCC編譯器的呼叫引數大約有100多個,其中多數引數我們可能根本就用不到,這裡只介紹其中最集中、最常用的引數。

GCC的基本用法:


gcc [options] [filenames]


其中options就是編譯器所需要的引數,filenames給出相關的檔名稱。

-c,  只編譯,不連線成為可執行檔案。編譯器只是由輸入的*.c等原始碼檔案生成*.o目標檔案,通常用於編譯不包含主程式的子程式檔案;

-o output_filename,確定輸出檔案的名稱為output_filename,同時這個名稱不能和原始檔同名。如果不給出這個選項,gcc給出預設的可執行檔案a.out;

-g,產生符號除錯工具(GNU的gdb)所必要的符號資訊,要想對原始碼進行除錯,我們必須加入這個選項;

-O,對程式進行優化編譯、連線,採用這個選項,整個原始碼會在編譯、連線過程中進行優化處理,這樣產生的可執行檔案的執行效率可以提高,但是,編譯、連線的速度相應的要慢一些;

-O2,比-O更好的優化編譯、連線,相應的速度會更慢一些;

-Wall,編譯警告選項,在編譯過程中如果gcc遇到一些它認為可能發生錯誤的地方就會提出一些相應的警告和提示訊息;

-I dirname,將dirname所指出的目錄加入到程式標頭檔案搜尋目錄列表中,是在預編譯過程中使用的引數;

-S,編譯指定的原始檔,但不進行彙編;

-E,預處理指定的原始檔,不進行編譯;


編譯靜態庫

靜態庫是一些目的碼的集合。Linux環境下的靜態庫目標檔案一般是以 .a 作為目標檔案的副檔名。

Linux 環境下使用 ar 命令建立一個靜態庫。靜態庫的優點在於使用簡單,編譯快速。靜態庫在應用程式生成時,已經編譯成為可重定位的目標檔案,因此可以不必要再編譯,節省編譯時間,以最短的時間生成可執行程式。而且,靜態庫在編譯的時候已經複製到可執行程式的程式碼中,不需要像使用動態庫那樣再程式載入時再次連結。在理論上,可執行程式使用靜態庫,要比使用動態庫速度快1%~5%。

(1)建立靜態庫

假設有一原始碼 static_lib.c:

int add( int a, int b )
{
	return a + b;
}
int sub( int a, int b )
{
	return a - b;
}
int mul( int a, int b )
{
	return a * b;
}
int div( int a, int b )
{
	return a / b;
}
現在要將其編譯成為靜態庫檔案——static_lib.a,則可使用如下的命令:

gcc -c static_lib.c
ar rcs static_lib.a static_lib.c
其中 ar 可以將目標檔案加入到一個已經存在的靜態庫中(若不存在則建立),rcs 引數分別表示:把列表中的目標檔案加入到靜態庫中(引數r),若指定靜態庫不存在,則建立該靜態庫檔案(引數c),最後更新靜態庫檔案的索引,使之包含新加入的目標檔案的內容(引數s)。




(2)使用靜態庫

靜態庫建立成功後,需要連結到應用程式中使用。應用程式可以引用該庫中的函式或者全域性變數。為了使應用程式可以正確引用該庫中的全域性符號,需要製作一個包含該靜態庫中全域性符號宣告的檔案。這個標頭檔案可以包含在應用程式的標頭檔案中,這樣就可以引用靜態庫中的全域性符號了。

標頭檔案static_lib.h

extern int add( int a, int b);
extern int sub( int a, int b );
extern int mul( int a, int b );
extern int div( int a, int b );
應用程式main.c:

#include <stdio.h>
#include "static_lib.h"

int main(void)
{
	int a, b;

	printf("please input a and b \n");
	scanf( "%d%d", &a, &b );
	printf("the add:  %d", add(a,b));
	printf("the sub:  %d", sub(a,b));
	printf("the mul:  %d", mul(a,b));
	printf("the div:  %d", div(a,b));
	return 0;
}
我們使用 gcc 的 -l 選項來指定靜態庫,或者使用 -L 引數來指定庫檔案的搜尋路徑。-l 和 -L 之後都直接帶引數而不跟空格。因而,對於本例,編譯命令如下:

gcc main.c -lstatic_lib.a -o app
或者使用 “ . ” 作為靜態庫的搜尋路徑,表示在當前目錄下搜尋需要的靜態庫檔案。

gcc -L. main.c -o app
注意:-l 是連結器選項,一定要放在被編譯的原始檔的檔名之後。

gcc 也支援使用-static 選項對靜態庫進行連結。

gcc main.c -static ./static_lib.a -o app



編譯動態庫

動態庫又稱為共享庫或者動態連結庫,現代程式中大量地使用動態連結庫。例如,Windows 環境中的dll檔案和Linux環境下的 so檔案。動態庫是在程式啟動時被裝載的。當一個程式裝載了一個動態庫後,其他應用程式仍然可以裝載同一個動態庫。

(1)建立動態庫

假設有一份需要轉化成動態庫的程式碼share_lib.c

void insert_sort( int *array, int length)
{
	int i, j;
	for (i = 1; i < length; ++i)
	{
		int temp;
		j = i;
		temp = array[j];

		while(j > 0){
			if (temp < array[j - 1])
			{
				array[j] = array[j - 1];
				j--;
			}else
			break;
		}
		array[j] = temp;
	}
}


int binary_search( int *array, int item, int length)
{
	int high, low, mid;
	high = length - 1;
	low = 0;
	mid = (high + low) / 2;

	while(low <= high)
	{
		if (array[mid] > item)
		{
			high = mid;
			mid = (high + low) / 2;
		}else if ( array[mid] < item)
		{
			low = mid;
			mid = (high + low)/2;
		}
		else
			return mid;
	}
return -1;
}

Linux使用gcc建立一個動態庫。由於動態庫可以被多個程式共享載入,所以需要生成位置無關的目標檔案。這時需要使用gcc編譯器的-fPIC選項,該選項用於生成位置無關的程式碼。除了需要使用-fPIC選項外,還需要使用-shared 選項,該選項將位置無關的程式碼製作為動態庫。

gcc -shared -fPIC share_lib.so share_lib.c


(2)使用動態庫

動態庫建立完後,應用程式可以引用該庫中的函式或全域性變數。為了使應用程式可以正確引用該庫中的全域性符號,需要製作一個包含該動態庫中全域性符號宣告的標頭檔案。

如share_lib.h

extern void insert_sort( int *array, int length);
extern int binary_search( int *array, int item, int length);
使用動態庫的main.c

#include <stdio.h>
#include "share_lib.h"

 int main(int argc, char const *argv[])
{
	int array[5] = {5,4,2,3,1};
	int item;
	int pos;

	insert_sort( array, 5);

	printf("please input a number\n");
	scanf("%d", &item);
	pos = binary_search( array, item, 5);

	if (pos == -1)
	{
	        printf("can't find\n");
	}else
	{
                      printf("the position is %d\n", pos+1);
	}

	return 0;
}

使用如下命令建立程式:

gcc main.c ./share_lib.so -o app





本文地址:http://blog.csdn.net/linj_m/article/details/45039347

更多資源請關注 部落格:LinJM-機器視覺  微博:林建民-機器視覺



相關文章