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