在 Alpine Docker 中编译时可能导致链接错误的原因是什么?

What could be causing linking errors when compiling in an Alpine Docker?

我正在尝试在从 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

我收到以下错误:

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

我有:

图像中还安装了相关软件包 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

验证静态库 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

但是,我仍然很好奇,为什么使用完全相同的库和指令,这将无法在 Alpine 图像中编译,而在 Ubuntu 图像中编译却没有问题。

I am still curious why, using the exact same library and instructions, this would fail to compile in an Alpine image while compiling without issue in an Ubuntu image.

Alpine 上 linking 错误的原因可能有点令人惊讶,实际上并非特定于 Alpine。

虽然这失败了 link:

gcc -largp -o progname progname.o

这个有效:

gcc -o progname progname.o -largp

原因是传递给linker的参数顺序,跟linking算法有关。通常,在 linking 命令行中首先指定对象(可能还有用户的静态库),然后是使用 -l 的库。标准 linker 算法在 Eli Bendersky 的文章 Library order in static linking:

中得到了完美的解释

Object files and libraries are provided in a certain order on the command-line, from left to right. This is the linking order. Here's what the linker does:

  • The linker maintains a symbol table. This symbol table does a bunch of things, but among them is keeping two lists:
    • A list of symbols exported by all the objects and libraries encountered so far.
    • A list of undefined symbols that the encountered objects and libraries requested to import and were not found yet.
  • When the linker encounters a new object file, it looks at:
    • The symbols it exports: these are added to the list of exported symbols mentioned above. If any symbol is in the undefined list, it's removed from there because it has now been found. If any symbol has already been in the exported list, we get a "multiple definition" error: two different objects export the same symbol and the linker is confused.
    • The symbols it imports: these are added to the list of undefined symbols, unless they can be found in the list of exported symbols.
  • When the linker encounters a new library, things are a bit more interesting. The linker goes over all the objects in the library. For each one, it first looks at the symbols it exports.
    • If any of the symbols it exports are on the undefined list, the object is added to the link and the next step is executed. Otherwise, the next step is skipped.
    • If the object has been added to the link, it's treated as described above - its undefined and exported symbols get added to the symbol table.
    • Finally, if any of the objects in the library has been included in the link, the library is rescanned again - it's possible that symbols imported by the included object can be found in other objects within the same library.

-largp 首先出现时,linker 不会在 linking 过程中包含它的任何对象,因为它还没有任何未定义的符号。如果静态库是由路径提供的,而不是 -l,那么它的所有对象都会添加到 linking 过程。