Linux环境下C/C++开发和可执行程序运行路径


开发C++程序, 路径设置一直是一个容易出错的关键点。很多人入门C++很久,对于程序开发和运行相关路径还是云里雾里。这里做一个简单的梳理。
因为现在C++开发一般都是CMake了,所以本文都以CMake举例。

C++开发中需要关注的路径

  1. 头文件路径: 头文件路径是编译器(如: cc, cc1)需要使用的路径。
  2. 依赖库文件路径: 依赖库文件路径是链接器(如: ld)需要使用的路径。
  3. 运行时路径: 程序运行时通常需要加载一些动态库,仅依赖当前系统环境,与开发设置无关,所以需要在系统环境中设置运行时路径。

GCC链接选项-L,-rpath-link,-rpath

  • -L: 指定通过-l连接的动态库的搜索目录,所有的-L对所有的-l有效。
  • -rpath: 设置运行时库搜索路径,会写入ELF可执行文件中。
  • -rpath-link: 指定链接时间接依赖库搜索路径, 运行时无效。

LD_LIBRARY_PATH

在程序链接或运行期间,如果设置了LD_LIBRARY_PATH, 链接器/运行时链接器都会搜索该路径查找动态库。
有趣的两点:

  1. LD_LIBRARY_PATH 中可以配置相对路径,比如.和..
  2. LD_LIBRARY_PATH 中若存在空路径,则相当于包含当前工作路径 (所以LD_LIBRARY_PATH中存在一个多余的冒号与否,有较大区别)

可执行程序运行时搜索库路径的几个点:

  • 默认路径: /lib, /usr/lib
  • 环境变量: LD_LIBRARY_PATH
  • ELF中的RPATH
  • ELF中的RUNPATH

其优先级关系如下:

|ELF中的RPATH|ELF中的RUNPATH|LD_LIBRARY_PATH环境变量|尝试加载目录的数序|
|:=:|:=:|:=:|:=:|
|未设置|未设置|未设置|/lib==>/usr/lib|
|未设置|未设置|设置|${LD_LIBRARY_PATH}==>/lib==>/usr/lib|
|设置|未设置|未设置|${RPATH}==>/lib==>/usr/lib|
|设置|未设置|设置|${RPATH}==>${LD_LIBRARY_PATH}==>/lib==>/usr/lib|
|设置或未设置|设置|设置|${LD_LIBRARY_PATH}==>${RUN_PATH}==>/lib==>/usr/lib|
|设置或未设置|设置|未设置|${RUN_PATH}==>/lib==>/usr/lib|

pkg-config和PKG_CONFIG_PATH

许多程序和第三方库发布包提供一个*.pc的配置文件,里面也包含了头文件和依赖库等信息,用户通过设置PKG_CONFIG_PATH,就可以利用pkg-config工具解析并输出编译选项,将其用于gcc等编译器的编译链接过程中。如:
$gcc main.c pkg-config --cflags --libs opencv -o main

参考: https://www.cnblogs.com/sddai/p/10266624.html

CMake中设置头文件,依赖库路径和运行时路径

1
2
3
4
5
6
7
8
9
10
11
include_directories: 设置当前Project全局头文件路径
target_include_directories: 设置单个target头文件路径

link_directories: 设置当前Project全局库文件路径
target_link_directories: 设置单个target库文件路径

link_libraries: 设置当前Project全局依赖的库文件
target_link_libraries: 设置单个target依赖的库文件

// 设置运行时库路径RPATH
set_target_properties(${APP_NAME} PROPERTIES INSTALL_RPATH "$ORIGIN/lib:/usr/local/lib")

可能遇到的问题

  1. 设置了rpath, 但是运行时找不到so.
    1
    2
    3
    4
    5
    # 检查elf FLAGS:
    $readelf -d myelf | grep FLAGS
    0x000000000000001e (FLAGS) ORIGIN
    0x000000006ffffffb (FLAGS_1) Flags: ORIGIN
    #如果看到FLAGS不是上面的ORIGIN,可能需要通过`-z origin`链接选项开启。