Post

gcc/g++链接选项

链接顺序/符号冲突/显示指定动态还是静态链接

gcc/g++链接选项

珍惜生命,远离链接顺序导致的问题

  • 链接问题一般不会给你带来什么收益,但是出现问题,查错有时候是困难的,所以要了解一下。
  • 无需深入研究,遇到问题再来看一下解决办法即可。
  • 知道有这回事,当你设计一个库的时候,尽可能保证库的垂直关系,不要循环依赖。
  • 尽量避免同名,C++的 namespace,以及 C 中按模块添加前缀 XXX_func(),都是为了避免同名而存在的。

测试代码

b.cpp

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include "b.h"
int say_hi(void)
{
    std::cout << "hi!" << std::endl;
}
void same_func()
{
    std::cout << "i am b!" << std::endl;
}

b.h

1
2
3
#pragma once
int say_hi(void);
void same_func();

a.cpp

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include "a.h"
#include "b.h"
int say_hello(void)
{
    std::cout << "hello world!" << std::endl;
    say_hi();//来自libb.so,
}
void same_func()
{
    std::cout << "i am b!" << std::endl;
}

a.h

1
2
3
#pragma once
int say_hello(void);
void same_func();

test2.cpp

1
2
3
4
5
6
7
#include <iostream>
#include "a.h"
int main(void)
{
    say_hello();//来自liba.so,函数内又调用了libb.so的内容
    same_func();
}

以上代码接口按顺序编译,先是 libb.so,然后 liba.so,最后编译 test2.cpp


链接顺序–依赖

在 ld -v 大于 2.2 时,ld 自带了-as-needed 参数,于其对应的参数是–no-as-needed

  • -Wl,-as-needed,该参数表示 ld 将会检查每个指定动态库,当需要才链接,并认为仅依赖后面(右边)的库,不依赖前面的(左边)
  • -Wl,–no-as-needed,链接所有列举出来的库,并且没有顺序依赖关系的判断

第一种情况

先忽略 same_func 相关的内容

libb.so 没有疑问

g++ -shared -fPIC b.cpp -o libb.so

liba.so 没有真实链接到 b

这里编译器默认开启了-as-need 选项,所以认为 a.cpp 不需要 libb.so,使用 ldd 命令查看,并没有 libb.so

1
2
3
4
5
6
7
8
$ g++ -shared -fPIC -L. -lb a.cpp -o liba.so
$ ldd liba.so
linux-vdso.so.1 (0x00007fffca820000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007ff3a03b0000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff39ffb0000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007ff39fc10000)
/lib64/ld-linux-x86-64.so.2 (0x00007ff3a0a00000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007ff39f9f0000)

PS:动态库的编译方式导致了这里可以通过编译,如果编译可执行文件,将遇到-lb a.cpp 的顺序问题。

test2.cpp 编译失败

失败原因:在第二步的时候,-lb 写在 a.cpp 左边,导致 liba.so 实际上没有链接 libb.so

1
2
3
$ g++ -o test test2.cpp -L. -la
./liba.so: undefined reference to `say_hi()'
collect2: error: ld returned 1 exit status
  • 这两种方法都没有问题,但涉及符号冲突时,以下两行命令导致不同的结果,下文会说到
1
2
g++ -o test -Wl,--no-as-need -L. -lb -la test2.cpp
g++ -o test test2.cpp -L. -la -lb

第二种情况

先忽略 same_func 相关的内容

libb.so

g++ -shared -fPIC b.cpp -o libb.so

liba.so,真正链接 libb.so,注意对比第一种情况的第 2 步命令

1
2
3
4
5
6
7
8
9
$ g++ -shared -fPIC a.cpp -L. -lb -o liba.so
$ ldd liba.so
    linux-vdso.so.1 (0x00007fffdfa42000)
    libb.so => ./libb.so (0x00007f686bfb0000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f686bc20000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f686b820000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f686b480000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f686c400000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f686b260000)

test2.cpp

由于 liba.so 已经指明了依赖于 libb.so,所以这里只需要链接 liba.so g++ -o test test2.cpp -L. -la


额外细节

按照《第二种情况》的方法编译,最后加上 libpthread.so 的库 -Wl,–no-as-need 会链接上代码完全没有使用到的 pthread 库

1
2
3
4
5
6
7
8
9
10
11
$ g++ -o test  -Wl,--no-as-need  test2.cpp -L. -la -lpthread
$ ldd test
    linux-vdso.so.1 (0x00007fffe3316000)
    liba.so => ./liba.so (0x00007fb3ca2b0000)
    libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb3ca090000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb3c9d00000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb3c9960000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb3c9740000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb3c9340000)
    libb.so => ./libb.so (0x00007fb3c9120000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb3ca800000)

-Wl,-as-need(默认选项)则不会

1
2
3
4
5
6
7
8
9
10
$ g++ -o test test2.cpp -L. -la -lpthread
$ ldd test
    linux-vdso.so.1 (0x00007fffc0ccd000)
    liba.so => ./liba.so (0x00007fb805100000)
    libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb804d70000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fb804970000)
    libb.so => ./libb.so (0x00007fb804760000)
    libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fb8043c0000)
    /lib64/ld-linux-x86-64.so.2 (0x00007fb805600000)
    libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb8041a0000)

链接顺序–同一符号

请注意 same_func 相关的内容 按照《第一种情况》的方法编译到步骤 3.2,整体的输出是:

1
2
$ g++ -shared -fPIC b.cpp -o libb.so
$ g++ -shared -fPIC -L. -lb a.cpp -o liba.so

liba.so 在前(左),输出了liba.so的 same_func

1
2
3
4
5
$ g++ -o test test2.cpp -L. -la -lb
$ ./test
hello world!
hi!
i am a!

libb.so 在前(左),输出了libb.so的 same_func

1
2
3
4
5
$ g++ -o test -Wl,--no-as-need -L. -lb -la test2.cpp
$ ./test
hello world!
hi!
i am b!

也就是说,假设 libA.so 和 libB.so 都包含 func()函数(符号),编译时,顺序是左到右按顺序的,后加载的重名符号会被忽略掉,第一次找到的符号有效 如,-lA -lB,则将忽略 B 中的 func 版本,因此遇到问题的话:

  • 方案一:利用-Wl,–no-as-need,把库的顺序写对,一般满足大多数需求,设计时也应该避免重名
  • 方案二:利用 dlopen 等一系列 dl 函数动态加载库。可能你需要用 A 版本的 func1,又需要用 B 版本的 func2,而他们都是重名的,比如以下情景:(PKI 行业)程序需要集成两个厂商提供的 PKCS11 标准接口的驱动库,用于一台服务器兼容两种产品,而这两个库依赖的 openssl 版本不同,于是底下 openssl 就有一堆重名函数,但是实现未必一致。这种情况下,不仅厂商库需要链接指定版本 openssl,本身工程代码也需要用到 openssl,总不能链接 3 个不同的 openssl 库吧。
    • 要么,找到一个准确的顺序,刚好每个库都得到了他需要的函数
    • 否则,考虑使用动态加载(推荐):一个链接使用,一个动态加载使用;或者两个都动态加载,写个类封装一下接口,还可以使用工厂模式和单例模式。

显式指定动态链接或静态链接

-Wl,-Bstatic -la  -Wl,-Bdynamic -lb

  • 这样,a 将会被静态链接,而 b 将会被动态链接。前提是路径中有 liba.a 以及 libb.so。
  • 显式指定一般用于:在路径中既有 a 的动态库 liba.so 也有静态库 liba.a,而你希望链接静态库。
This post is licensed under CC BY 4.0 by the author.