g++編譯過程和動態連接庫

簡介

gcc :GNU編譯器套件(GNU Compiler Collection)是到了4.7,才真正支持c11的。
gcc & g++如今是gnu中最主要和最流行的c & c++編譯器 。
g++是將默認語言設爲c++,連接時自動使用C++標準庫而不用c標準庫 。
C++標準庫:http://www.runoob.com/cplusplus/cpp-standard-library.htmlhtml

1、編譯過程

  1. 預編譯過程:處理宏定義和include,去除註釋,不會對語法進行檢查,生成.i文件:命令:g++ -E Test.cpp > Test.i
  2. 編譯過程:檢查語法,生成彙編指令.s文件;[編譯器egcs];命令:g++ -S Test.cpp -o Test.s
  3. 彙編過程:彙編變爲目標代碼(機器代碼)生成.o的文件 [彙編器as];命令:g++ -c Test.cpp
  4. 連接過程:生成可執行程序[連接器ld];命令:g++ Test.o -L F:\vs2008\VC\include\iostream

2、編譯器參數

  • -std=c++11 指定語言標準,只有編譯C或C++時纔有用,除了c++11,還有c90、c8九、c99等等

調試選項 -g和-pglinux

  • -g :生成能被GNU調試器使用的調試信息;用level指出須要多少信息,默認的level值是2
  • -pg:在編譯好的程序里加入額外的代碼。運行程序時, 產生gprof用的剖析信息以顯示你的程序的耗時狀況。

優化選項 -O(0~3) 優化編譯、連接,-O0表示沒有優化,-O1爲缺省值,-O3優化級別最高;ios

  • -O:進行跳轉和延遲退棧兩種優化
  • -O2:除了完成-O1,還進行額外的調整工做,如指令調整??
  • -O3:包括循環展開和其餘一些與處理特性相關的優化工做??
  • 可是一般狀況下,自動的東西都不是太聰明,太大的優化級別可能會使生成的文件產生一系列的bug。通常可選擇2;3會有必定風險。

目錄選項c++

  • -I:用來指定頭文件目錄;-Idirname:將dirname所指出的目錄加入到程序頭文件目錄列表中,是在預編譯過程當中使用的參數;/usr/include目錄通常是不用指定的,若是不加你會獲得一個"xxxx.h: No such file or directory"的錯誤,gcc xxx.c -lm(動態數學庫,庫名m) -lpthread(連接多線程庫) -ldl(連接dl庫)
    庫文件放在/lib和/usr/lib和/usr/local/lib裏的庫直接用-l參數就能連接,而放在其餘目錄,則要使用-L緊跟庫文件所在目錄;#include< > 引用的是編譯器的類庫路徑裏面的頭文件。 #include" " 引用的是你程序目錄的相對路徑中的頭文件。-l和-L:指定程序要連接的庫,緊接庫名;

錯誤與警告選項多線程

  • -v:列出全部編譯步驟
  • -w:關閉全部警告信息
  • -Wall:打印出gcc提供的警告信息
  • -Wextra:打印出更多的警告信息,比開啓 -Wall 打印的還多
  • -Werror:要求GCC將全部的警告當成錯誤進行處理,在警告發生時停止編譯過程。
  • -Wno-sign-compare: 關閉當有符號轉換爲無符號時,有符號和無符號值比較產生的錯誤警告。
  • -Wno-unused-local-typedefs:忽略本地未使用的類型定義警告。
  • -Wno-deprecated-declarations:關閉使用廢棄API的警告.

連接方式函數

  • -static:禁止使用動態庫。優勢:程序運行不依賴於其餘庫。缺點:可執行文件比較大。
  • -shared:此選項將盡可能使用動態庫,爲默認選項。優勢:生成文件比較小。缺點:運行時須要系統提供動態庫。 須要配合參數-fPIC使用
  • -symbolic :創建共享目標文件的時候,把引用綁定到全局符號上。對全部沒法解析的引用做出警告(除非用鏈接選項,'-Xlinker -z -Xlinker defs'取代)。只有部分系統支持該選項。
  • -Wl,-Bstatic:告訴連接器ld只連接靜態庫,若是隻存在動態連接庫,則連接器報錯。
  • -Wl,-Bdynamic:告訴連接器ld優先使用動態連接庫,若是隻存在靜態連接庫,則使用靜態連接庫。
  • -m32爲生成32位的動態連接庫。 -m64位生成64位的動態連接庫。

其餘選項工具

  • -fpic:編譯器就生成位置無關目標碼.適用於共享庫(shared library)
  • -fPIC:編譯器就輸出位置無關目標碼.適用於動態鏈接(dynamic linking)。
  • -pipe:使用管道代替編譯中臨時文件,在使用非gnu彙編工具的時候,可能有些問題
  • -msse:讓編譯器使用cpu的sse指令集,可使用mmx寄存器計算單精度浮點運算。
  • -msse2:讓編譯器使用奔騰cpu的指令集,可使用mmx寄存器計算雙精度浮點運算。
  • -mno-mmx-mno-sse-mno-sse2:不使用MMX,SSE,SSE2指令。
  • -fpermissive:把代碼的語法錯誤做爲警告,並繼續編譯。請謹慎使用該選項。
  • -rdynamic:用來通知連接器將全部符號添加到動態符號表中,程序動態調用動態庫中的函數,編譯時用到該選項,一般和-ldl一塊兒用。
  • -D name=def: 加入宏定義,若不指定def,則默認爲1
  • //一些進行緩衝區溢出實驗時可能須要的選項
  • -fstack-protector-fno-stack-protector:是否開啓堆棧保護,這裏的保護是在返回地址以前加入一個驗證值來確保返回地址不被破壞
  • -z execstack :啓用可執行棧,默認是禁用的

3、生成動態連接庫

在實際開發過程當中,各個模塊之間會涉及到一些通用的功能,好比讀寫文件,查找、排序。爲了減小代碼的冗餘,提升代碼的質量,能夠將這些通用的部分提取出來,作出公共的模塊庫。經過動態連接庫能夠實現多個模塊之間共享公共的函數。優化

建立動態庫,經過shared和fPIC編譯參數生產so動態連接庫文件。程序在調用庫函數時,只須要鏈接上這個庫便可。spa

/*caculate.h*/
#ifndef CACULATE_HEAD
#define CACULATE_HEAD
int add(int a, int b);
int sub(int a, int b);
int div(int a, int b);
int mul(int a, int b);
#endif

/*caculate.c文件*/
#include "caculate.h"
extern "C"{
int add(int a, int b)
{ 
  return (a + b);
}
int sub(int a, int b)
{
  return (a - b);
}
int div(int a, int b)
{ 
  return (int)(a / b);
} 
int mul(int a, int b)
{ 
  return (a * b); 
}
}

編譯生產libcac.so文件以下插件

g++ -fPIC -shared -o libxxx.so xx1.cpp xx2.cpp xx3.cpp  //多個文件成成一個動態鏈接庫

g++ -fPIC -shared caculate.cpp -o libcac.so

4、調用動態庫

#include <stdio.h>
#include "caculate.h"
int main(){ 
  int a = 20; 
  int b = 10; 
  printf("%d + %d = %d\n", a, b, add(a, b));
  printf("%d - %d = %d\n", a, b, sub(a, b));
  printf("%d / %d = %d\n", a, b, div(a, b));
  printf("%d * %d = %d\n", a, b, mul(a, b)); 
  return 0;
}

編譯生產可執行文件main以下: g++ main.c -o main -L./ -lcac

5、動態獲取動態連接庫的函數

linux提供dlopen、dlsym、dlerror和dlcolose函數獲取動態連接庫的函數。
經過這個四個函數能夠實現一個插件程序,方便程序的擴展和維護。

#include <dlfcn.h> 以指定模式打開指定的動態連接庫文件,並返回一個句柄給 dlsym()的調用進程

void *dlopen(const char *filename, int flag);

char *dlerror(void);

void *dlsym(void *handle, const char *symbol);

int dlclose(void *handle);//使用dlclose()來卸載打開的庫。 Link with -ldl.

  • dlopen 該函數將打開一個新庫,並把它裝入內存。該函數主要用來加載庫中的符號,這些符號在編譯的時候是不知道的。

flag在linux 下有三種解析方式 RTLD_LAZY 暫緩決定,等有須要時再解出符號  RTLD_NOW 當即決定,返回前解除全部未決定的符號。

做用範圍,可與解析方式經過「|」組合使用。
RTLD_GLOBAL:動態庫中定義的符號可被其後打開的其它庫解析。
RTLD_LOCAL: 與RTLD_GLOBAL做用相反,動態庫中定義的符號不能被其後打開的其它庫重定位。若是沒有指明是RTLD_GLOBAL仍是RTLD_LOCAL,則缺省爲RTLD_LOCAL。

handle = dlopen(full_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);

  • dlsym 根據動態連接庫操做句柄與符號,返回符號對應的地址,不但能夠獲取函數地址,也能夠獲取變量地址。

void _dlsym(void_handle,const char*symbol)

handle:由dlopen打開動態連接庫後返回的指針;

symbol:要求獲取的函數或全局變量的名稱。

  • dlclose函數負責關閉指定句柄的動態庫,當該庫的使用計數爲0時,就會被卸載
#include <stdio.h> 
#include <dlfcn.h>

#define DLL_FILE_NAME "libcac.so" 
typedef int (CAC_FUNC)(int, int); 
//using function = void()(int, int);
//c++11 
int main() { 
void *handle; 
CAC_FUNC func = NULL; 
char *error; 
int a = 30; 
int b = 5;
handle = dlopen(DLL_FILE_NAME, RTLD_NOW);
if(handle == NULL)
{
    fprintf(stderr, "Failed to open libaray %s error:%s\n", DLL_FILE_NAME, dlerror());
    return -1;
}
*(void **) (&func) = dlsym(handle, "add");
if(func == NULL){
    printf("ERROR:%s:dlsym\n", dlerror());
    return -1;
}
printf("%d + %d = %d\n", a, b, func(a, b));

func = (CAC_FUNC)dlsym(handle, "sub");
printf("%d + %d = %d\n", a, b, func(a, b));

func= (CAC_FUNC)dlsym(handle, "div");
printf("%d + %d = %d\n", a, b, func(a, b));

func = (CAC_FUNC)dlsym(handle, "mul");
printf("%d + %d = %d\n", a, b, func(a, b));

dlclose(handle);
return 0;
}

輸出:g++ maindl.cpp -o maindl -ldl

30 + 5 = 35 30 + 5 = 25 30 + 5 = 6 30 + 5 = 150

void *dlsym(void _handle, const char _symbol); 返回值爲void (void )&(func)是將函數指針的地址強制轉換void類型,而後使用取值,獲取dlsym的返回值 實際這個地方沒有必要這樣,函數指針原本就是地址,能夠直接用 func = dlsym(handle, "add");

  • void 和 void*

一、函數的返回值若是是 void 類型的,則表示這個函數的執行結果是沒有返回值的; 二、函數的返回值若是是 void* 類型的,則表示返回一個內存地址,這個內存空間存放的數據類型是void類型的,即無類型的,也能夠說是萬能型的 三、在實際開發中,void* 使用時最終都要強制轉換成某種明確的數據類型

相關文章
相關標籤/搜索