Linux下C/C++程序編譯鏈接加載過程中的常見問題及解決方法
1 頭文件包含的問題
報錯信息
該錯誤通常發生在編譯時,常見報錯信息如下:
run.cpp:2:10: fatal error: dlpack/dlpack.h: No such file or directory#include <dlpack/dlpack.h>^~~~~~~~~~~~~~~~~
compilation terminated.
頭文件包含的問題是新手常常會遇到的問題,在這里,我們要先搞懂 gcc 關于頭文件搜索的相關過程、參數選項和環境變量,之后頭文件包含的相關問題就迎刃而解了。
頭文件的搜索順序
我們先來看一下頭文件的搜索順序,我們知道,在 C/C++ 源文件中,包含頭文件有兩種形式,即分別是尖括號和雙引號來包含:#inlcude <xxx>
和 #include "xxx"
。
<>
對于尖括號包含的頭文件,通常搜索順序為:
- 搜索
-I
指定的目錄 - 搜索 gcc 環境變量
C_INCLUDE_PATH
(如果是C++程序,則為CPLUS_INCLUDE_PATH
) 中指定的目錄 - 搜索 gcc 的默認目錄:
/usr/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/7.5.0/include
“”
而對于雙引號包含的頭文件,我們很清楚,主要是為了當前工作目錄下去搜索,這種情況的詳細搜索順序是這樣的:
- 搜索當前目錄
- 搜索
-I
參數指定的目錄 - 搜索 gcc 環境變量
C_INCLUDE_PATH
(如果是C++程序,則為CPLUS_INCLUDE_PATH
) 中指定的目錄 - 搜索 gcc 的默認目錄:
/usr/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/7.5.0/include
可以看到,相對于 <> 的包含方式,"" 就是先多搜索了當前目錄。
另外,要指出,編譯器會按照上述順序搜索頭文件,一旦找到了就不會就緒往下進行了,也就是說,如果有同名的頭文件,順序靠后的搜索目錄中的頭文件會被靠前的屏蔽掉。
解決方法:編譯參數選項和環境變量
通過上面介紹的與包含頭文件相關的編譯參數選項和環境變量我們也不難發現,當面對找不到頭文件的錯誤時,有兩種方式可以告訴編譯器去找所需的頭文件的目錄:即 通過編譯參數選項或環境變量。
- 編譯參數選項
-I
參數指定編譯器會去搜索的頭文件包含目錄。 - 環境變量
C_INCLUDE_PATH
和CPLUS_INCULDE_PATH
分別指定C代碼和C++代碼需要去搜索的頭文件包含目錄。
注意這里不是由 PATH
環境變量指定的,PATH
目錄下都是一些可執行文件,他不是用來指定頭文件的搜索目錄的,
這里要說下 include 的默認目錄,它也不是由 $ATH
環境變量指定的(也就是說在所有頭文件包含的行為中,和 PATH
環境變量一毛錢關系沒有),而是由g++的配置prefix指定的,可以通過 gcc -v
來查看:
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/7/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 7.5.0-3ubuntu1~18.04' --with-bugurl=file:///usr/share/doc/gcc-7/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++ --prefix=/usr --with-gcc-major-version-only --program-suffix=-7 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-libmpx --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
2 編譯連接時的未定義引用的問題
報錯信息
常見報錯信息如下:
/tmp/ccWYumCZ.o: In function `main':
run.cpp:(.text+0x8f): undefined reference to `tvm::runtime::Module::LoadFromFile(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
collect2: error: ld returned 1 exit status
編譯鏈接時庫搜索順序
首先還是要明確在編譯連接時的庫的搜索順序:
- 搜索
-L
+-l
參數指定的 - 搜索環境變量
LIBRARY_PATH
+ 參數-l
指定的 - 搜索默認目錄中的
/lib
/usr/lib
/usr/local/lib
解決方法:編譯參數選項和環境變量
與上面頭文件包含問題類似,解決方法還是靠添加編譯參數選項和環境變量。這里需要注意的是:-L
參數選項和 LIBRARY_PATH
環境變量都可以負責指定庫的搜索目錄,而 -l
參數選項是來指定搜索目錄下的具體的庫文件的名字。也就是說, -L
和 LIBRARY_PATH
二選一,再加上 -l
參數,才行。
比如我們在 /home/song/tvm/build/
目錄下,有兩個庫文件 libtvm.so
和 libtvm_runtime.so
,我們要用它們,有以下兩種方法:
-
方法一:
export LIBRARY_PATH=/home/song/tvm/build/:$LIBRARY_PATH gcc main.cpp -ltvm -ltvm_runtime
-
方法二:
gcc main.cpp -L/home/song/tvm/build/ -ltvm -ltvm_runtime
可參考:gcc參數 -i, -L, -l, -include
3 運行時鏈接庫的問題
報錯信息
該問題發生在運行時,即我們已經得到可執行文件 a.out
了,但是在運行時卻報錯類似:
./a.out: error while loading shared libraries: libtvm.so: cannot open shared object file: No such file or directory
我們還可以借助工具 ldd
來查看 a.out
需要運行時鏈接的庫是否都可用:
ldd a.out
# 輸出:linux-vdso.so.1 (0x00007ffc0ebca000)libtvm.so => not foundlibstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f1b829b7000)libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f1b8279f000)libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1b823ae000)libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f1b82010000)/lib64/ld-linux-x86-64.so.2 (0x00007f1b82f45000)
可以看到,確實如同報錯信息提示的那樣,libtvm.so
它是找不到的。
運行時鏈接庫的搜索順序
我們還是先來看一下運行時鏈接庫的搜索順序:
-
編譯目標代碼時指定的動態庫搜索路徑
-
環境變量
LD_LIBRARY_PATH
指定的動態庫搜索路徑 -
配置文件
/etc/ld.so.conf
中指定的動態庫搜索路徑 -
默認的動態庫搜索路徑
-
/lib
-
/lib64
-
/usr/lib
-
應注意動態庫搜尋路徑并不包括當前文件夾,所以當即使可執行文件和其所需的so文件在同一文件夾,也會出現找不到so的問題,就像 #include <xxxx>
也不搜索當前目錄
解決方法:編譯選項參數、環境變量、配置文件、軟鏈接
這里編譯選項參數和環境變量的方法與上面類似,這里就不再贅述了,說說配置文件和軟鏈接的方法:
修改/etc/ld.so.conf文件
在 /etc/ld.so.conf
文件中添加運行時庫的路徑。然后執行 ldconfig
命令。
或者在 /etc/ld.so.conf.d
目錄下添加一個新建的 .conf
新文件,然后在文件中輸入新的路徑,然后再執行ldconfig
命令:
vim /etc/ld.so.conf.d/MyLibrary.conf # 加上自己需要的搜索路徑
sudo ldconfig
添加運行時庫的軟鏈接
可以用 ln
命令來創建運行時庫的軟鏈接,并把軟鏈接放在系統默認路徑下,然后程序鏈接時只需鏈接動態庫的軟鏈接就行。這樣做的好處是當動態庫升級時,只需替換掉原來的老軟鏈接就行,無需修改編譯命令。其實上面的問題2 也可以考慮用這種方法。
Ref:
http://blog.sina.com.cn/s/blog_7195909a0100zi7i.html
https://blog.csdn.net/Damocles_shi/article/details/104085803