在 Alpine Docker 中编译时,什么可能导致链接错误?

Chr*_*nth 4 c linker docker alpine-linux

我正在尝试在从 Alpine 3.7 基础映像构建的 docker 容器中编译一个程序。该程序使用argp.h, 并将其包含为 #include <argp.h>. 我已经安装了 argp-standalone 并验证它是否已添加到映像中。该文件argp.h位于usr/include,但是当我使用以下命令编译程序时:

gcc -W -Wall -Wextra -I/usr/include   -c -o progname.o progname.c
gcc -largp -o progname progname.o
Run Code Online (Sandbox Code Playgroud)

我收到以下错误:

progname.o: In function `parse_opt':
progname.c:(.text+0x4c9): undefined reference to `argp_failure'
progname.c:(.text+0x50f): undefined reference to `argp_failure'
progname.c:(.text+0x555): undefined reference to `argp_failure'
progname.c:(.text+0x59b): undefined reference to `argp_failure'
progname.c:(.text+0x5ce): undefined reference to `argp_error'
progname.c:(.text+0x5f4): undefined reference to `argp_error'
progname.o: In function `main':
progname.c:(.text+0x1397): undefined reference to `argp_parse'
collect2: error: ld returned 1 exit status
make: *** [Makefile:9: progname] Error 1
Run Code Online (Sandbox Code Playgroud)

我有:

  • 确保图像上的版本argp.h确实包含argp_failureargp_parseargp_error函数。
  • 尝试移动argp.h到机器上的不同位置(例如,进入编译所在的同一目录,进入/usr/lib
  • 尝试使用-l和进行编译-L

映像中还安装的相关软件包有build-basemakegcc。在 Ubuntu 映像上编译时,即使没有-largp-I/usr/include标志,这些相同的命令也可以正常工作。Alpine 图像中可能发生什么不同的情况导致此功能不起作用?

编辑

根据 @Pablo 的评论,我现在将其编译如下:

gcc -W -Wall -Wextra -I/usr/include -L/usr/lib -c -o progname.o progname.c
gcc -largp -o progname progname.o
Run Code Online (Sandbox Code Playgroud)

验证静态库 后,libargp.a位于 中/usr/lib。然而,同样的问题仍然存在。

编辑2

编译如下(再次按照@Pablo的建议)解决了我遇到的错误:

gcc -W -Wall -Wextra -I/usr/include -L/usr/lib -c -o progname.o progname.c
gcc -o progname progname.o /usr/lib/libargp.a
Run Code Online (Sandbox Code Playgroud)

然而,我仍然很好奇为什么使用完全相同的库和指令,在 Alpine 映像中编译失败,而在 Ubuntu 映像中编译没有问题。

val*_*ano 5

我仍然好奇为什么使用完全相同的库和指令,在 Alpine 映像中编译失败,而在 Ubuntu 映像中编译没有问题。

Alpine 上出现链接错误的原因可能有点令人惊讶,实际上并不是 Alpine 特有的。

虽然这无法链接:

gcc -largp -o progname progname.o
Run Code Online (Sandbox Code Playgroud)

这有效:

gcc -o progname progname.o -largp
Run Code Online (Sandbox Code Playgroud)

原因是传递给链接器的参数顺序,并且与链接算法有关。通常,在链接命令行中首先指定对象(可能还指定任何用户的静态库),然后使用-l. Eli Bendersky 的文章静态链接中的库顺序完美地解释了标准链接器算法:

目标文件和库在命令行上按一定顺序从左到右提供。这是链接顺序。这是链接器的作用:

  • 链接器维护一个符号表。这个符号表做了很多事情,但其中保留了两个列表:
    • 到目前为止遇到的所有对象和库导出的符号列表。
    • 遇到的对象和库请求导入但尚未找到的未定义符号的列表。
  • 当链接器遇到新的目标文件时,它会查看:
    • 它导出的符号:这些符号被添加到上面提到的导出符号列表中。如果任何符号位于未定义列表中,则会将其从那里删除,因为现在已找到它。如果导出列表中已经存在任何符号,我们会收到“多重定义”错误:两个不同的对象导出相同的符号,并且链接器会感到困惑。
    • 它导入的符号:这些符号将添加到未定义符号列表中,除非可以在导出符号列表中找到它们。
  • 当链接器遇到新库时,事情会变得更有趣。链接器会遍历库中的所有对象。对于每个符号,它首先查看其导出的符号。
    • 如果它导出的任何符号位于未定义列表中,则该对象将添加到链接并执行下一步。否则,将跳过下一步。
    • 如果对象已添加到链接,则按上述方式处理 - 其未定义和导出的符号将添加到符号表中。
    • 最后,如果库中的任何对象已包含在链接中,则会再次重新扫描库 - 可能会在同一库内的其他对象中找到包含的对象导入的符号。

-largp首次出现时,链接器不会在链接过程中包含其任何对象,因为它还没有任何未定义的符号。如果静态库由路径提供,而不是通过 提供-l,则其所有对象都将添加到链接过程中。