Ubuntu下編譯程序(動態鏈接,靜態鏈接,動態加載)

Ubuntu下編譯程序(動態鏈接,靜態鏈接,動態加載)

以下爲C++程序作爲示例:

Code.hCode.h

#ifndef CODE_H
#define CODE_H

int add(int, int);

#endif

Code.cppCode.cpp

#include "Code.h"

int add(int a, int b)
{
    return a + b;
}

Main.cppMain.cpp

#include <iostream>
#include "Code.h"

int main(void)
{
    std::cout << add(10, 20) << std::endl;
    
    return 0;
}



直接編譯

g++ Main.cpp Code.h Code.cpp

該指令將自動完成 “預處理”、“編譯”、“彙編”、“鏈接” 這四個步驟,將三個文件編譯成可執行文件,該可執行文件默認爲 a.outa.out



指定將要生成的可執行文件的名字

g++ Main.cpp Code.h Code.cpp -o [指定的名字]

示例:

g++ Main.cpp Code.h Code.cpp -o Demo

該指令直接將三個文件編譯成可執行文件,該可執行文件爲 DemoDemo



預處理

g++ -E Code.cpp

上面的命令會使得預處理後的結果顯示在計算機屏幕上。

如下指令將編譯成預處理文件:

g++ -o Code.i -E Code.cpp

該指令直接將Code.cppCode.cpp文件預編譯處理,生成爲 Code.iCode.i預編譯文件 。



編譯

g++ -S Code.cpp

上面的命令將Code.cpp文件翻譯成彙編語言,通常都是原本的文件名,但後綴爲ss,例如這個例子爲:Code.sCode.s



彙編

g++ -c Code.cpp

上面的命令將Code.cpp文件編譯成目標文件,或是將已經翻譯成了彙編的文件編譯成目標文件,通常都是原本的文件名,但後綴爲o,例如這個例子將生成文件:Code.oCode.o



生成靜態鏈接

我們這裏先將Code.hCode.hCode.cppCode.cpp生成靜態鏈接文件libCode.alibCode.a,然後與Main.cppMain.cpp文件一起編譯。指令如下:

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					// 添加環境變量後,可以直接編譯



生成動態鏈接

我們這裏先將Code.hCode.hCode.cppCode.cpp生成動態鏈接文件libCode.solibCode.so,然後與Main.cppMain.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 -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語言,則可以不用修改Code.hCode.h):

Code.hCode.h

#ifndef CODE_H
#define CODE_H

// 告訴編譯器這部分代碼按C語言(而不是C++)的方式進行編譯
extern "C" int add(int, int);

/* ————————————————————————————————————————————————————————
 *
 * 由於C++支持函數重載,因此編譯器編譯函數的過程中會將函數的
 * 參數類型也加到編譯後的代碼中,而不僅僅是函數名;而C語言並
 * 不支持函數重載,因此編譯C語言代碼的函數時不會帶上函數的參
 * 數類型,一般只包括函數名。
 * 
 * 
 * 如果是按照C++的方式編譯,則我們使用dlsym()函數根據"add"
 * 字符串尋找add函數的時候,可能會找不到add函數的地址。
 * 
 * ———————————————————————————————————————————————————————— */


#endif

Code.cppCode.cpp

#include "Code.h"

int add(int a, int b)
{
    return a + b;
}

Main.cppMain.cpp

#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;
}

接下來就是創建動態庫,我們先將Code.hCode.hCode.cppCode.cpp生成動態鏈接文件libCode.solibCode.so,然後編譯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							// 運行可執行文件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章