庫鏈接的問題

編譯:

編譯過程是以每個.cpp文件爲獨立的編譯單位的,生成一個個.obj
編譯過程,將引用文件在.cpp文件中展開,並檢查是否有正確的聲明。如果該函數沒有定義,編譯器認爲在連接過程可以在其他.obj文件中找到。

頭文件路徑是通過編譯器默認設置以及用戶通過-I選項添加的,默認的include路徑,可以通過指令來查看

echo | g++ -v -x c++ -E -

連接過程,將上面沒有定義的函數,在其他.obj中查找定義,如果沒有找到,則會報undefine reference的問題。
而查找鏈接庫時所用的路徑是,通過變量LIBRARY_PATH設置,但是變量LIBRARY_PATH只是在編譯期間使用,作爲查找動態鏈接庫時指定查找共享庫的路徑。

執行

在可執行文件執行時,會通過LD_LIBRARY_PATH查找動態鏈接庫的。
LD_LIBRARY_PATH環境變量用於在程序加載運行期間查找動態鏈接庫時指定除了系統默認路徑之外的其他路徑。

注意

LD_LIBRARY_PATH中指定多個的路徑,查找動態庫的時候會按序查找
舉個例子,如果我們make了opencv的debug版本,和opencv的release版本,這兩個版本在程序中都會用到,所以需要將兩個版本的lib的路徑,都添加到環境變量LD_LIBRARY_PATH中,假如我們將debug版本的路徑放在release版本的前面,那麼程序執行運行時,如果需要用到opencv庫,那麼都會先查找debug的路徑,即都執行的是debug的lib,無論用戶本意如何。

那麼問題來了,這時就涉及爲什麼要設置兩次library path?LIBRARY_LIB設置的路徑在運行時不能使用麼?
答:開發時,設置LIBRARY_PATH,以便gcc能夠找到編譯時需要的動態鏈接庫。
發佈時(執行時),設置LD_LIBRARY_PATH,以便程序加載運行時能夠自動找到需要的動態鏈接庫,如果更改LD_LIBRARY_PATH,不需要重新編譯也可以完成更新。

即LIBRARY_PATH只是爲了編譯時能夠確定是否存在連接函數的定義,即obj;而,當編譯結束後,可執行文件具執行的時候,是通過LD_LIBRARY_PATH來查找obj的)
所以,是的,LIBRARY_PATH設置的路徑不能再運行時使用。

但是我還有個問題
因爲是在運行的時候連接,會不會影響速度?因爲得查找所有的 動態鏈接庫文件,而編譯的時候就可以執行lib目錄下的那些庫文件

靜態鏈接庫,連接時注意事項:
使用靜態連接庫時,在鏈接命令中給出所依賴的庫時,需要注意庫之間的依賴順序,依賴其他庫的庫一定要放到被依賴庫的前面,這樣才能真正避免undefined reference的錯誤,完成編譯鏈接。
動態鏈接庫不存在這個問題。

動態鏈接庫和靜態鏈接庫的區別
靜態連接庫就是把(lib)文件中用到的函數代碼直接鏈接進目標程序,程序運行的時候不再需要其它的庫文件;動態鏈接就是把調用的函數所在文件模塊(DLL)和調用函數在文件中的位置等信息鏈接進目標程序,程序運行的時候再從DLL中尋找相應函數代碼,因此需要相應DLL文件的支持。

靜態鏈接庫與動態鏈接庫都是共享代碼的方式,如果採用靜態鏈接庫,則無論你願不願意,lib 中的指令都全部被直接包含在最終生成的 EXE 文件中了。但是若使用 DLL,該 DLL 不必被包含在最終 EXE 文件中,EXE 文件執行時可以“動態”地引用和卸載這個與 EXE 獨立的 DLL 文件。靜態鏈接庫和動態鏈接庫的另外一個區別在於靜態鏈接庫中不能再包含其他的動態鏈接庫或者靜態庫,而在動態鏈接庫中還可以再包含其他的動態或靜態鏈接庫。

關於OpenCV庫鏈接,如果已經make以後,但是沒有安裝,改變opencv-3.0.0(版本)的位置,然後再重新改一下相應的路徑,OpenCV仍能正常使用

我們遇到的問題是:

1.
從別的機器上拷貝過來的工程代碼,更新庫路徑。
但是運行的時候出現段錯誤。
這是系統安裝的opencv庫的問題。

2.
我們重新安裝了一個debug版本的opencv,並想用這個庫。
具體操作如下,make之後(並沒有install),將-I,-L路徑都連接到信的目錄,但是編譯的時候出錯,錯誤如下:

cpp問價中報未定義的引用的錯

這是因爲,我們的debug庫是未安裝的,include下頭文件不全,所以報這樣的錯,然後我們將-I路徑換爲系統的opencv的include,就編譯通過了。

3.
但是然後很奇怪的事情發生了,如果直接登錄我的賬號,執行,會出現段錯誤,如果李同學從他的賬戶su到我的賬戶,執行,就正常。

這是因爲我們的~/.bashrc文件不同

他在~/.bashrc中直接將LD_LIBRARY_PATH中的路徑賦值爲$USER_OPENCV_HOME/lib,而他這個USER_OPENCV_HOME是自己安裝的lib,

而我是這樣添加的
export LD_LIBRARY_PATH = $LD_LIBRARY_PATH: $USER_OPENCV_LIB/lib
這樣,查找動態連接庫時,會直接先查找usr/local下的opencv庫,而這個庫有問題,所以就出現了上述情況。

改爲這樣就可以了
export LD_LIBRARY_PATH = $USER_OPENCV_LIB/lib: $LD_LIBRARY_PATH

另外,我的.bashrc文件只能通過新開一個會話,纔能有更新,用source不行,不知道爲什麼
另外,調用cv::getBuildInformation()函數,可查看可執行文件當前使用的庫的信息,庫信息,跟所調用的庫有關,即跟LD_LIBRARY_PATH有關。


補充

linux靜態鏈接庫與動態鏈接庫的區別及動態庫的創建(轉)
一、引言

通常情況下,對函數庫的鏈接是放在編譯時期(compile time)完成的。所有相關的對象文件(object file)與牽涉到的函數庫(library)被鏈接合成一個可執行文件(executable file)。程序在運行時,與函數庫再無瓜葛,因爲所有需要的函數已拷貝到自己門下。所以這些函數庫被成爲靜態庫(static libaray),通常文件名爲“libxxx.a”的形式。

其實,我們也可以把對一些庫函數的鏈接載入推遲到程序運行的時期(runtime)。這就是如雷貫耳的動態鏈接庫(dynamic link library)技術。

二、動態鏈接庫的特點與優勢

首先讓我們來看一下,把庫函數推遲到程序運行時期載入的好處:

  1. 可以實現進程之間的資源共享。
    什麼概念呢?就是說,某個程序的在運行中要調用某個動態鏈接庫函數的時候,操作系統首先會查看所有正在運行的程序,看在內存裏是否已有此庫函數的拷貝了。如果有,則讓其共享那一個拷貝;只有沒有才鏈接載入。這樣的模式雖然會帶來一些“動態鏈接”額外的開銷,卻大大的節省了系統的內存資源。C的標準庫就是動態鏈接庫,也就是說系統中所有運行的程序共享着同一個C標準庫的代碼段。

  2. 將一些程序升級變得簡單。用戶只需要升級動態鏈接庫,而無需重新編譯鏈接其他原有的代碼就可以完成整個程序的升級。Windows 就是一個很好的例子。

  3. 甚至可以真正坐到鏈接載入完全由程序員在程序代碼中控制。
    程序員在編寫程序的時候,可以明確的指明什麼時候或者什麼情況下,鏈接載入哪個動態鏈接庫函數。你可以有一個相當大的軟件,但每次運行的時候,由於不同的操作需求,只有一小部分程序被載入內存。所有的函數本着“有需求才調入”的原則,於是大大節省了系統資源。比如現在的軟件通常都能打開若干種不同類型的文件,這些讀寫操作通常都用動態鏈接庫來實現。在一次運行當中,一般只有一種類型的文件將會被打開。所以直到程序知道文件的類型以後再載入相應的讀寫函數,而不是一開始就將所有的讀寫函數都載入,然後才發覺在整個程序中根本沒有用到它們。

三、動態鏈接庫的創建

由於動態鏈接庫函數的共享特性,它們不會被拷貝到可執行文件中。在編譯的時候,編譯器只會做一些函數名之類的檢查。在程序運行的時候,被調用的動態鏈接庫 函數被安置在內存的某個地方,所有調用它的程序將指向這個代碼段。因此,這些代碼必須實用相對地址,而不是絕對地址。在編譯的時候,我們需要告訴編譯器, 這些對象文件是用來做動態鏈接庫的,所以要用地址不無關代碼(Position Independent Code (PIC))。

對gcc編譯器,只需添加上 -fPIC 標籤,如:

gcc -fPIC -c file1.c
gcc -fPIC -c file2.c
gcc -shared libxxx.so file1.o file2.o

注意到最後一行,-shared 標籤告訴編譯器這是要建立動態鏈接庫。這與靜態鏈接庫的建立很不一樣,後者用的是 ar 命令。也注意到,動態鏈接庫的名字形式爲 “libxxx.so” 後綴名爲 “.so”

2、動態庫的鏈接

在1、中,我們已經成功生成了一個自己的動態鏈接庫libtest.so,下面我們通過一個程序來調用這個庫裏的函數。程序的源文件爲:test.c。

test.c:

#include “so_test.h”
int main()
{
test_a();
test_b();
test_c();
return 0;

}

l 將test.c與動態庫libtest.so鏈接生成執行文件test:

$ gcc test.c -L. -ltest -o test

l 測試是否動態連接,如果列出libtest.so,那麼應該是連接正常了

$ ldd test

l 執行test,可以看到它是如何調用動態庫中的函數的。

3、編譯參數解析
最主要的是GCC命令行的一個選項:
-shared 該選項指定生成動態連接庫(讓連接器生成T類型的導出符號表,有時候也生成弱連接W類型的導出符號),不用該標誌外部程序無法連接。相當於一個可執行文件

l -fPIC:表示編譯爲位置獨立的代碼,不用此選項的話編譯後的代碼是位置相關的所以動態載入時是通過代碼拷貝的方式來滿足不同進程的需要,而不能達到真正代碼段共享的目的。

l -L.:表示要連接的庫在當前目錄中

l -ltest:編譯器查找動態連接庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來確定庫的名稱

l LD_LIBRARY_PATH:這個環境變量指示動態連接器可以裝載動態庫的路徑。

l 當然如果有root權限的話,可以修改/etc/ld.so.conf文件,然後調用 /sbin/ldconfig來達到同樣的目的,不過如果沒有root權限,那麼只能採用輸出LD_LIBRARY_PATH的方法了。


undefined reference問題總結(轉)
http://ticktick.blog.51cto.com/823160/431329
1. 鏈接時缺失了相關目標文件(.o)
測試代碼如下

//main.c
int main()
{
test();
}
//test.c
#include <stdio.h>
void test()
{
printf("test!\n");
}

然後編譯

gcc -c test.c  
gcc -c main.c

得到兩個 .o 文件,一個是 main.o,一個是 test.o ,然後我們鏈接 .o 得到可執行程序:

gcc -o main main.o

這時,你會發現,報錯了:
這裏寫圖片描述
這就是最典型的undefined reference錯誤,因爲在鏈接時發現找不到某個函數的實現文件,本例中test.o文件中包含了test()函數的實現,所以如果按下面這種方式鏈接就沒事了。

gcc -o main main.o test.o

2….

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