只对部分库进行静态链接

131
如何在使用GCC连接时仅将某些特定库静态链接到我的二进制文件中? gcc ... -static ... 尝试静态连接所有已连接的库,但我没有一些库的静态版本(例如:libX11)。

7
可能是在gcc中同时使用静态链接库和动态链接库的重复问题。 - lionello
8个回答

128

gcc -lsome_dynamic_lib code.c some_static_lib.a


9
在链接目标文件时,尤其是静态库时,需要将其放在代码文件之后。在古老和现代的链接环境中(截至2010年11月,我不确定较旧版本的现状),如果在code.c文件之前列出静态库,则保证其中的符号将被忽略,除非其中一个库对象文件中恰好有一个main()函数。 - Jonathan Leffler
54
您能详细说明这是如何工作的吗?只有代码的答案对于初学者来说并不有帮助。 - jb.
10
默认情况下,gcc使用动态链接。当你使用-l某个动态库时,它会按预期进行动态链接。但是,当你明确地给gcc提供一个静态库时,它将始终尝试对其进行静态链接。不过,有一些关于符号解析顺序的棘手细节;我不太确定如何运作。我了解到,当存在疑虑时,尝试重新排列库标志的顺序就可以了 :-) - bchurchill
4
如果你在静态链接中使用一个 GPL 图书馆,就会涉及许可问题,例如:http://www.gnu.org/licenses/gpl-faq.html#IfLibraryIsGPL。 - laplasz
1
@HiB GPL适用于静态和动态链接的方式相同。 - osvein
显示剩余2条评论

60

你还可以使用-Bdynamic选项中的ld

gcc <objectfiles> -static -lstatic1 -lstatic2 -Wl,-Bdynamic -ldynamic1 -ldynamic2
所有它后面的库(包括由gcc自动链接的系统库)将被动态链接。

25
-Wl,-Bdynamic 需要使用 GNU ld,因此这种解决方案在 gcc 使用系统 ld 的系统上不起作用(例如 Mac OS X)。 - pts

41
gcc objectfiles -o program -Wl,-Bstatic -ls1 -ls2 -Wl,-Bdynamic -ld1 -ld2

你也可以使用 -static-libgcc -static-libstdc++ 标志来链接 GCC 库。

请记住,如果同时存在 libs1.solibs1.a,在使用 -Wl,-Bstatic 或在 -Wl,-Bdynamic 之后,连接器将选择 libs1.so。在调用 -ls1 之前,请不要忘记传递 -L/libs1-library-location/


2
至少,这个解决方案可以静态链接到libgomp! - Jérôme
这对我很有效,但在命令中的某个地方使用“-static”会失败(我认为它试图静态链接更多东西而不仅仅是我想要的库)。 - nh2
7
注意:-Wl,-Bstatic-Wl,-Bdynamic 的顺序很重要。 - Pavel Vlasov

29

ld的man页面中(这不适用于gcc),关于--static选项:

您可以在命令行上多次使用此选项:它会影响随后的-l选项的库搜索。

一个解决方案是在命令行上在--static选项之前放置动态依赖项。

另一种可能性是不使用--static,而是提供静态链接特定库的静态对象文件的完整文件名/路径(即不使用-l选项)。例如:

# echo "int main() {}" > test.cpp
# c++ test.cpp /usr/lib/libX11.a
# ldd a.out
linux-vdso.so.1 =>  (0x00007fff385cc000)
libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007f9a5b233000)
libm.so.6 => /lib/libm.so.6 (0x00007f9a5afb0000)
libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x00007f9a5ad99000)
libc.so.6 => /lib/libc.so.6 (0x00007f9a5aa46000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9a5b53f000)

正如您在示例中看到的那样,libX11不在动态链接库列表中,因为它是静态链接的。

请注意:即使使用完整的文件名/路径指定,.so 文件始终会以动态方式链接。


libX11.aldd a.out的输出之间有什么关系? - khatchad
1
啊,我明白了。ldd 输出所需的共享库列表,但 libX11 并未出现在其中。 - khatchad
这不太清楚。你说“这个选项”和“那个选项”。是哪个选项? - Octopus

19
据我理解,问题如下:您有几个库,一些是静态的,一些是动态的,还有一些既是静态的又是动态的。默认情况下,gcc链接“大多数动态库”。也就是说,gcc尽可能链接到动态库,否则会退回到静态库。当您使用gcc的-static选项时,行为是仅链接静态库,并在找不到静态库的情况下退出并显示错误,即使存在适当的动态库。
另一个选项是我在多个场合希望gcc具有的,它称为-mostly-static,实质上是-dynamic(默认)的相反。如果存在-mostly-static,它将优先链接静态库,但会退回到动态库。
该选项不存在,但可以通过以下算法模拟:
1. 构造链接命令行,不包括-static。 2. 迭代动态链接选项。 3. 累计库路径,即形式为-L的选项,存储于变量中。 4. 对于每个动态链接选项,即形式为-l的选项,运行命令gcc -print-file-name=lib.a并捕获输出。 5. 如果命令打印出与您传递的内容不同的内容,则为静态库的完整路径。用静态库的完整路径替换动态库选项。 6. 重复此过程,直到处理完整个链接命令行。该脚本还可以选择排除静态链接的库名称列表。
下面的Bash脚本似乎能够解决问题:
#!/bin/bash

if [ $# -eq 0 ]; then
    echo "Usage: $0 [--exclude <lib_name>]. . . <link_command>"
fi

exclude=()
lib_path=()

while [ $# -ne 0 ]; do
    case "$1" in
        -L*)
            if [ "$1" == -L ]; then
                shift
                LPATH="-L$1"
            else
                LPATH="$1"
            fi

            lib_path+=("$LPATH")
            echo -n "\"$LPATH\" "
            ;;

        -l*)
            NAME="$(echo $1 | sed 's/-l\(.*\)/\1/')"

            if echo "${exclude[@]}" | grep " $NAME " >/dev/null; then
                echo -n "$1 "
            else
                LIB="$(gcc $lib_path -print-file-name=lib"$NAME".a)"
                if [ "$LIB" == lib"$NAME".a ]; then
                    echo -n "$1 "
                else
                    echo -n "\"$LIB\" "
                fi
            fi
            ;;

        --exclude)
            shift
            exclude+=(" $1 ")
            ;;

        *) echo -n "$1 "
    esac

    shift
done

echo
例如:
mostlyStatic gcc -o test test.c -ldl -lpthread

在我的系统上返回:

gcc -o test test.c "/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/libdl.a" "/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/libpthread.a"

或者排除某个条件:

mostlyStatic --exclude dl gcc -o test test.c -ldl -lpthread

然后我得到:

gcc -o test test.c -ldl "/usr/lib/gcc/x86_64-linux-gnu/4.7/../../../x86_64-linux-gnu/libpthread.a"

15

还有一种-l:libstatic1.a(减号 l 冒号)选项可以在 gcc 中用于链接静态库(感谢https://dev59.com/h2w15IYBdhLWcg3wfb3i#20728782)。它有文档吗?在 gcc 的官方文档中没有(即对于共享库而言也并非完全正确):https://gcc.gnu.org/onlinedocs/gcc/Link-Options.html

-llibrary
-l library 

Search the library named library when linking. (The second alternative with the library as a separate argument is only for POSIX compliance and is not recommended.) ... The only difference between using an -l option and specifying a file name is that -l surrounds library with ‘lib’ and ‘.a’ and searches several directories.

binutils ld文档对此进行了描述。选项-lname将搜索libname.so,然后搜索libname.a,添加lib前缀和.so(如果此时启用),或者.a后缀。但是-l:name选项只会精确搜索指定的名称: https://sourceware.org/binutils/docs/ld/Options.html
-l namespec
--library=namespec

Add the archive or object file specified by namespec to the list of files to link. This option may be used any number of times. If namespec is of the form :filename, ld will search the library path for a file called filename, otherwise it will search the library path for a file called libnamespec.a.

On systems which support shared libraries, ld may also search for files other than libnamespec.a. Specifically, on ELF and SunOS systems, ld will search a directory for a library called libnamespec.so before searching for one called libnamespec.a. (By convention, a .so extension indicates a shared library.) Note that this behavior does not apply to :filename, which always specifies a file called filename.

The linker will search an archive only once, at the location where it is specified on the command line. If the archive defines a symbol which was undefined in some object which appeared before the archive on the command line, the linker will include the appropriate file(s) from the archive. However, an undefined symbol in an object appearing later on the command line will not cause the linker to search the archive again.

See the -( option for a way to force the linker to search archives multiple times.

You may list the same archive multiple times on the command line.

This type of archive searching is standard for Unix linkers. However, if you are using ld on AIX, note that it is different from the behaviour of the AIX linker.

变量-l:namespec自binutils 2.18版本(2007年)开始有文档记录:https://sourceware.org/binutils/docs-2.18/ld/Options.html

这个选项似乎在其他所有方法都失败的情况下起作用。 我们刚刚遇到了一个问题,我们需要静态链接libjsoncpp.a,因为我们的构建机器会生成链接到libjsocpp.so.0的二进制文件,而目标操作系统仅提供libjsoncpp.so.1。在我们能够解决这个差异之前,这是我们的唯一解决方案,可以在我们的情况下产生正确的结果。 - Tomasz W

4

一些装载器(链接器)提供了开启和关闭动态加载的开关。如果GCC在这样的系统上运行(Solaris - 可能还有其他系统),那么您可以使用相关选项。

如果您知道要静态链接哪些库,可以在链接行中简单地指定静态库文件 - 通过完整路径。


6
即使这个答案被接受了,但它并没有完全解决问题。正如@peoro所解释的那样,他试图解决的问题是他没有所有库的静态版本,这意味着他想尽可能地将许多库静态链接起来。请看我的答案。 - jcoffland

3

要在一行中链接动态库和静态库,您必须将静态库放在动态库和目标文件之后,像这样:

gcc -lssl main.o -lFooLib -o main

否则,它将无法正常工作。我花了一些时间才明白这一点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接