cpp编译时搜索路径问题

 

静态编译时,会去哪里找头文件/库文件呢? 动态运行时,会去哪里找库文件呢?

cpp编译时搜索路径问题

本文所描述的编译工具链为GNU GCC

在使用cmake或者其他的构建系统编译 C++ 代码时,总是会碰见找不到头文件或者库文件的情况。本文就详细描述了寻找头/库文件的过程。

ps:这其实是因为一系列构建系统太完善了,导致大家对gcc或者其他的编译器不熟悉,由于“构建系统”这一“中间层”的存在,使得我们不熟悉底层提供“服务”的编译工具链。

如果感兴趣的话,完全可以直接去GNU GCC官网读手册

g++/gcc 是什么

二者其实都是 GNU 编译工具链的“驱动”,负责在不同的编译(广义)阶段调用不同的程序。如,汇编、编译(狭义)、链接。二者的不同1有如下几点:

  1. gcc 会将 *.c*.cpp 分别当做 c 和 c++ 文件来编译,g++将二者都当作 c++ 文件

  2. g++ 在链接阶段会去找标准的 c++ 库,而 gcc 不会。

  3. gcc 编译 *.c 文件只有很少的预定义的宏

  4. gcc 编译 *.cpp 文件 或者 g++ 编译 *.c/*.cpp 文件 有一些额外的宏,如:

    #define __GXX_WEAK__ 1
    #define __cplusplus 1
    #define __DEPRECATED 1
    #define __GNUG__ 4
    #define __EXCEPTIONS 1
    #define __private_extern__ extern
    

头文件搜索路径

知道了上面的小知识之后,自然而然就需要思考一个问题,实际上执行搜索头文件的程序到底是什么?

头文件的处理在预处理阶段,也就是预处理程序是什么2cpp(C Preprocessor)

使用gcc/g++提供的命令行选项找到其实际调用的预处理程序:

$ `gcc -print-prog-name=cpp`
# 或者
$ `g++ --print-prog-name=cpp`

查看详细信息,gcc/g++调用的cpp都是同一个,只列举一个:

$ `gcc --print-prog-name=cpp` -v
>>> log: 
# 为了突然重点删除一些log,保留如下信息:
ignoring nonexistent directory "/usr/local/include/x86_64-linux-gnu"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/include-fixed"
ignoring nonexistent directory "/usr/lib/gcc/x86_64-linux-gnu/9/../../../../x86_64-linux-gnu/include"
#include "..." search starts here:
#include <...> search starts here:
 /usr/lib/gcc/x86_64-linux-gnu/9/include
 /usr/local/include
 /usr/include/x86_64-linux-gnu
 /usr/include
End of search list.

从命令行的输出就可以看出来预处理程序会去哪些目录下寻找头文件,增加头文件搜索路径不在本文讨论范围。

题外话:#include “file” 和 #include <file> 的区别是什么

4.9.4版本的官网doc3有述:

GCC looks for headers requested with #include "file" first in the directory containing the current file, then in the directories as specified by -iquote options, then in the same places it would have looked for a header requested with angle brackets. For example, if /usr/include/sys/stat.h contains #include "types.h", GCC looks for types.h first in /usr/include/sys, then in its usual search path.

意思为:

  1. 对于 “file”: 首先在 ·include此文件· 的 ·文件· 所属的 ·目录· 寻找
  2. 之后在使用-iquote选项传入的目录路径下寻找(-iquote选项的作用新版本文档中有简单描述)
  3. 最后在和<file>一样的目录下寻找

库文件搜索路径

库文件搜索需要分两种情况,运行时编译时

编译时

执行库文件搜索+链接的程序为 ld。由上面讨论头文件的经验和前人4的经验,使用此命令打印搜索路径:

$ `g++ --print-prog-name=ld` --verbose | grep SEARCH_DIR | tr -s ' ;' \\012
>>> log:
SEARCH_DIR("=/usr/local/lib/x86_64-linux-gnu")
SEARCH_DIR("=/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu")
SEARCH_DIR("=/usr/lib/x86_64-linux-gnu64")
SEARCH_DIR("=/usr/local/lib64")
SEARCH_DIR("=/lib64")
SEARCH_DIR("=/usr/lib64")
SEARCH_DIR("=/usr/local/lib")
SEARCH_DIR("=/lib")
SEARCH_DIR("=/usr/lib")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib64")
SEARCH_DIR("=/usr/x86_64-linux-gnu/lib")

运行时

资料4中的这句话提示了我要区分编译时和运行时,然后感叹零几年的这种基础问答确实很让人豁然开朗,也自觉基础薄弱:

The answers suggesting to use ld.so.conf and ldconfig are not correct because they refer to the paths searched by the runtime dynamic linker (i.e. whenever a program is executed), which is not the same as the path searched by ld (i.e. whenever a program is linked).

也就是说:

  1. 链接时是由 ld 来完成库文件的搜索
  2. 运行时是由 ld.so 来完成库文件的搜索
# 同样来自资料4的回答,打印动态链接库的搜索路径:
$ ldconfig -v 2>/dev/null | grep -v ^$'\t'
/usr/lib/x86_64-linux-gnu/libfakeroot:
/usr/local/lib:
/lib/x86_64-linux-gnu:
/lib:

笔者通过file来查看一个可执行文件的信息,然后得到了这些信息:<exectuable file>:ELF 64-bit LSB shared object, ARM aarch64, version 1 (GNU/Linux), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=f50a07fc0aabb5d4425d40c117ef8b2511effa5b, for GNU/Linux 3.7.0, not stripped

dynamically linked, interpreter /lib/ld-linux-aarch64.so.1 分析可知,该可执行文件是动态链接的,它依赖于其他共享库,并且在运行时需要 /lib/ld-linux-aarch64.so.1 作为解释器,负责加载和执行程序所需的共享库。

动态库链接器ld.so搜索库文件的顺序

根据资料5所述:

  1. LD_LIBRARY_PATH 包含的目录
  2. /etc/ld.so.conf 里包含的目录
  3. /lib
  4. /usr/lib

另外也可以直接参考ld.so的man文档6

If a shared object dependency does not contain a slash, then it is searched for in the following order:

  1. 由 DT_RPATH 标记(ELF中的一个标记)指定的查找共享库的搜索目录,它是在静态编译时嵌入到可执行文件里面的。比如通过 -Wl, -rpath 来修改 DT_RPATH 的值。不推荐使用这种方法。
  2. LD_LIBRARY_PATH 所含的目录,不过在 secure-execution mode 下会被忽略。
  3. /etc/ld.so.cache 缓存的文件
  4. /lib(/lib64)
  5. /usr/lib (/usr/lib64)

参考资料

  1. What is the difference between g++ and gcc?
  2. Where does gcc look for C and C++ header files?
  3. Search Path
  4. How to print the ld(linker) search path
  5. What is the order that Linux’s dynamic linker searches paths in?(https://unix.stackexchange.com/questions/367600/what-is-the-order-that-linuxs-dynamic-linker-searches-paths-in)
  6. man-ld.so