Ubuntu下編譯程序(動態鏈接,靜態鏈接,動態加載)
以下爲C++程序作爲示例:
#ifndef CODE_H
#define CODE_H
int add(int, int);
#endif
#include "Code.h"
int add(int a, int b)
{
return a + b;
}
#include <iostream>
#include "Code.h"
int main(void)
{
std::cout << add(10, 20) << std::endl;
return 0;
}
直接編譯:
g++ Main.cpp Code.h Code.cpp
該指令將自動完成 “預處理”、“編譯”、“彙編”、“鏈接” 這四個步驟,將三個文件編譯成可執行文件,該可執行文件默認爲 。
指定將要生成的可執行文件的名字
g++ Main.cpp Code.h Code.cpp -o [指定的名字]
示例:
g++ Main.cpp Code.h Code.cpp -o Demo
該指令直接將三個文件編譯成可執行文件,該可執行文件爲 。
預處理
g++ -E Code.cpp
上面的命令會使得預處理後的結果顯示在計算機屏幕上。
如下指令將編譯成預處理文件:
g++ -o Code.i -E Code.cpp
該指令直接將文件預編譯處理,生成爲 預編譯文件 。
編譯
g++ -S Code.cpp
上面的命令將Code.cpp文件翻譯成彙編語言,通常都是原本的文件名,但後綴爲,例如這個例子爲:。
彙編
g++ -c Code.cpp
上面的命令將Code.cpp文件編譯成目標文件,或是將已經翻譯成了彙編的文件編譯成目標文件,通常都是原本的文件名,但後綴爲o,例如這個例子將生成文件:
生成靜態鏈接
我們這裏先將和生成靜態鏈接文件,然後與文件一起編譯。指令如下:
g++ -c Code.h Code.cpp // 將文件Code.h Code.cpp編譯成默認的目標文件Code.o(當然,也可以自主指定名字)
ls // 查看當前路徑下是否有Code.o文件
ar -rcs libCode.a Code.o // 該指令將Code.o打包稱爲庫文件,其靜態鏈接的後綴爲a,文件名爲libCode
ls // 查看當前路徑下是否有libCode.a靜態庫文件
g++ Main.cpp -L./ libCode.a // 將Main.cpp文件libCode.a進行編譯,並生成可執行文件。
// -L./ 是顯示指定庫文件在當前目錄下
ls // 查看當前路徑下是否有a.out可執行文件
./a.out // 運行可執行文件
在第五步,我們也可以顯示的去直接寫路徑:
g++ Main.cpp /home/villageclerk/libCode.a // /home/villageclerk/ 爲我存儲libCode.a的路徑
或是將當前路徑加到環境變量:
export My_Temporary_PATH=/home/villageclerk/ // 意爲添加環境變量My_Temporary_PATH,將/home/villageclerk/放入環境變量My_Temporary_PATH中
// export // 查看環境變量,export不僅可以添加環境變量還可以查看環境變量
// export -n My_Temporary_PATH // 刪除環境變量My_Temporary_PATH
g++ Main.cpp libCode.a // 添加環境變量後,可以直接編譯
生成動態鏈接
我們這裏先將和生成動態鏈接文件,然後與文件一起編譯。指令如下:
g++ -c -fPIC Code.h Code.cpp // 將文件Code.h Code.cpp編譯成默認的目標文件Code.o(當然,也可以自主指定名字), 一定要加 -fPIC
ls // 查看當前路徑下是否有Code.o文件
g++ -shared -fPIC Code.o -o libCode.so // 該指令將Code.o打包稱爲庫文件,其動態鏈接的後綴爲so,文件名爲libCode
ls // 查看當前路徑下面是否有libCode.so動態庫文件
g++ Main.cpp -L./ libCode.so // 將Main.cpp文件libCode.so進行編譯,並生成可執行文件。
ls // 查看當前路徑下是否有a.out可執行文件
./a.out // 運行可執行文件
在第五步,我們也可以顯示的去直接寫路徑:
g++ Main.cpp /home/villageclerk/libCode.so // /home/villageclerk/ 爲我存儲libCode.so的路徑
g++ Main.cpp ./libCode.so // 或是這樣
或是將當前路徑加到環境變量:
export My_Temporary_PATH=/home/villageclerk/ // 意爲添加環境變量My_Temporary_PATH,將/home/villageclerk/放入環境變量My_Temporary_PATH中
// export // 查看環境變量,export不僅可以添加環境變量還可以查看環境變量
// export -n My_Temporary_PATH // 刪除環境變量My_Temporary_PATH
g++ Main.cpp libCode.so // 添加環境變量後,可以直接編譯
動態加載
先來了解幾個動態加載庫的函數:
/* ————————————————————————————————————————————————————————
* dlopen()函數
*
* 頭文件:
* <dlfcn.h>
*
* 原型:
* void * dlopen(const char * filename, int flag);
*
* 參數:
* filename 爲所要打開的動態庫文件(包含其路徑)
* flag 打開方式,一般爲RTLD_LAZY
*
* 作用:
* 以指定模式打開指定的動態鏈接庫文件,並返回一個句柄給dlsym()
* 的調用進程,或是用dlclose卸載打開的庫。如果打開錯誤,則返回
* NULL
*
* ———————————————————————————————————————————————————————— */
/* ————————————————————————————————————————————————————————
* dlerror()函數
*
* 頭文件:
* <dlfcn.h>
*
* 原型:
* char * dlerror(void);
*
* 參數:
* 無
*
* 作用:
* 動態鏈接庫的一種命令,可以排查顯示出錯的信息。當動態鏈接庫
* 操作函數執行失敗時,dlerror可以返回出錯信息,返回值爲 NULL
* 時表示操作函數執行成功。
*
* ———————————————————————————————————————————————————————— */
/* ————————————————————————————————————————————————————————
* dlsym()函數
*
* 頭文件:
* <dlfcn.h>
*
* 原型:
* void * dlsym(void * handle, char * symbol);
*
* 參數:
* handle 爲dlopen()函數返回的句柄
* symbol 所要獲取的函數或變量的名稱
*
* 作用:
* 根據動態鏈接庫操作句柄與符號,返回符號對應的地址
*
* ———————————————————————————————————————————————————————— */
/* ————————————————————————————————————————————————————————
* dlclose()函數
*
* 頭文件:
* <dlfcn.h>
*
* 原型:
* int dlclose(void * handle);
*
* 參數:
* handle 爲dlopen()函數返回的句柄
*
* 作用:
* 關閉指定句柄的動態鏈接庫,如果卸載成功,則返回 0
*
* ———————————————————————————————————————————————————————— */
在進行動態加載前,我們需要對C++源文件進行一些修改(如果是C語言,則可以不用修改):
#ifndef CODE_H
#define CODE_H
// 告訴編譯器這部分代碼按C語言(而不是C++)的方式進行編譯
extern "C" int add(int, int);
/* ————————————————————————————————————————————————————————
*
* 由於C++支持函數重載,因此編譯器編譯函數的過程中會將函數的
* 參數類型也加到編譯後的代碼中,而不僅僅是函數名;而C語言並
* 不支持函數重載,因此編譯C語言代碼的函數時不會帶上函數的參
* 數類型,一般只包括函數名。
*
*
* 如果是按照C++的方式編譯,則我們使用dlsym()函數根據"add"
* 字符串尋找add函數的時候,可能會找不到add函數的地址。
*
* ———————————————————————————————————————————————————————— */
#endif
#include "Code.h"
int add(int a, int b)
{
return a + b;
}
#include <iostream>
#include <dlfcn.h> // 動態加載庫庫頭文件
#include <cstdlib>
int main(void)
{
char *error;
void *handle = dlopen("./libCode.so", RTLD_LAZY); // 打開動態加載庫,./ 表示在當前目錄下
if (!handle) // 判斷動態加載庫是否打開
{
std::cerr << dlerror() << std::endl; // 輸出錯誤信息
std::exit(1);
}
int(*p)(int, int) = (int(*)(int, int))dlsym(handle, "add"); // 獲取函數add的地址
if ((error = dlerror()) != NULL) // 判斷函數是否找到
{
std::cerr << error << std::endl; // 輸出錯誤信息
std::exit(1);
}
std::cout << p(10, 20) << std::endl;
dlclose(handle); // 關閉動態加載庫
return 0;
}
接下來就是創建動態庫,我們先將和生成動態鏈接文件,然後編譯Main.cpp函數就行了。
g++ -c -fPIC Code.h Code.cpp // 將文件Code.h Code.cpp編譯成默認的目標文件Code.o(當然,也可以自主指定名字), 一定要加 -fPIC
ls // 查看當前路徑下是否有Code.o文件
g++ -shared -fPIC Code.o -o libCode.so // 該指令將Code.o打包稱爲庫文件,其動態鏈接的後綴爲so,文件名爲libCode
ls // 查看當前路徑下面是否有libCode.so動態庫文件
g++ Main.cpp -ldl // 將Main.cpp文件libCode.so進行編譯,並生成可執行文件。
ls // 查看當前路徑下是否有a.out可執行文件
./a.out // 運行可執行文件