没有归档文件的静态链接是如何工作的?

3

我有两个文件

main.c

void swap();

int buf[2] = {1, 2}; 

int main() 
{
    swap();
    return 0;
}

swap.c

extern int buf[];

int* bufp0 = &buf[0]; /* .data */
int* bufp1; /* .bss */

void swap()
{
    int temp;
    
    bufp1 = &buf[1];
    temp = *bufp0;
    *bufp0 = *bufp1;
    *bufp1 = temp;
}

以下是一本书的两个摘录:

During this scan, the linker maintains a set E of relocatable object files that 
will be merged to form the executable, a set U of unresolved symbols 
(i.e., symbols referred to, but not yet defined), and a set D of symbols that 
have been defined in previous input files.
Initially, E, U , and D are empty.

For each input file f on the command line, the linker determines if f is an
object file or an archive. If f is an object file, the linker adds f to E, updates
U and D to reflect the symbol definitions and references in f , and proceeds
to the next input file.

If f is an archive, the linker attempts to match the unresolved symbols in U
against the symbols defined by the members of the archive. If some archive
member, m, defines a symbol that resolves a reference in U , then m is added
to E, and the linker updates U and D to reflect the symbol definitions and
references in m. This process iterates over the member object files in the
archive until a fixed point is reached where U and D no longer change. At
this point, any member object files not contained in E are simply discarded
and the linker proceeds to the next input file.

If U is nonempty when the linker finishes scanning the input files on the
command line, it prints an error and terminates. Otherwise, it merges and
relocates the object files in E to build the output executable file.

The general rule for libraries is to place them at the end of the command
line. If the members of the different libraries are independent, in that no member
references a symbol defined by another member, then the libraries can be placed
at the end of the command line in any order.

If, on the other hand, the libraries are not independent, then they must be
ordered so that for each symbol s that is referenced externally by a member of an
archive, at least one definition of s follows a reference to s on the command line.

For example, suppose foo.c calls functions in libx.a and libz.a that call func-
tions in liby.a. Then libx.a and libz.a must precede liby.a on the command
line:

unix> gcc foo.c libx.a libz.a liby.a


我运行了以下命令来将两个目标文件静态链接(不创建任何归档文件)。
gcc -static -o main.o main.c swap.c

我预计上述命令会失败,因为main.cswap.c都有相互定义的引用。但与我的预期相反,它成功了。只有当我在命令行结尾再次传递main.c时,我才希望它能成功。
链接器如何解决这两个文件中的引用?当链接器尝试静态链接多个对象文件而不是归档文件时,工作方式是否有所不同? 我猜测链接器回到main.c以解决swap.c中的buf引用。
2个回答

2
通常情况下,链接器的默认行为是包含给定的每个目标模块文件中的所有内容,并且仅在处理库时获取链接器已知的定义引用的目标模块。因此,当链接器处理main.o文件时,它将准备好将其中的所有内容放入正在构建的输出文件中。这包括记住(无论是在内存中还是通过链接器暂时维护的辅助文件中)由main.o定义的所有符号以及main.o具有未解决引用的所有符号。当链接器处理swap.o文件时,它将从swap.o文件中添加所有内容到正在构建的输出文件中。进一步地,对于任何在main.o文件中由swap.o文件中的定义满足的引用,它会解析这些引用,并且对于任何在swap.o文件中由main.o文件中的定义满足的引用,它也会解析这些引用。正如您引用的文本所说,“(...) 链接器将f添加到E中,更新U和D以反映f中的符号定义和引用,并继续处理下一个输入文件。” 对于一个目标模块文件,该步骤实际上对于链接器添加到可执行文件的每个目标模块都是相同的,无论该目标模块来自目标模块文件还是来自库文件。区别在于如果对象模块在文件中,则链接器无条件地将其添加到可执行文件中,但是如果对象模块在库中,则仅当它定义了链接器当前正在寻找的符号时,链接器才将其添加到可执行文件中。

那么,链接器基本上包含一个全局符号表,其中包含所有对象文件的符号定义?如果我没错的话,这意味着在终端上给出的文件顺序并不重要? - Sathvik Swaminathan
@SathvikSwaminathan:是的,当只有目标文件(并且系统库自动包含在最后)时,在这里讨论的方面,目标文件的顺序并不重要。(它可能会影响将事物放入可执行文件的顺序并产生一些其他后果,但它不会影响是否解析所有符号引用。)当存在库文件时,它们相对于彼此和目标文件的顺序确实很重要。 - Eric Postpischil
我现在明白了,谢谢。我会接受你的答案。 - Sathvik Swaminathan

1
在您的情况下,gcc命令(不带-c)生成一个可执行文件。该命令将命令行上的每个“.c”文件编译为“.o”文件。然后,它调用一个链接器(ld),指向命令行中的所有.o文件。链接器解析引用并生成一个名为...main.o(-o命名可执行文件)的可执行文件。您可以运行它。
静态存档库只是一组单独编译的.o文件。链接器检查存档中的所有文件以解析符号。您可以使用“-c”限定符预编译.c文件,生成.o文件,然后在命令行上使用它们,或者创建一个存档并使用存档代替。

谢谢您的回复。是的,我理解了。我的问题是关于链接器用于解析符号的算法。链接器使用的算法在我问题的第一个摘录中提到。如果链接器确实使用了该算法,则不应该能够解析swap.c中的buf引用,因为bufmain.c中定义,并且链接器已经处理过它了。与第二个摘录中提到的归档文件的情况类似,我假设需要将main.c附加到终端行以进行成功的链接,但显然并非如此。 - Sathvik Swaminathan
符号解析通常不依赖于文件的顺序。链接器会生成一个全局符号表,从所有文件中抽样所有符号及其用法。在整个表构建完成后,才进行实际的链接。 - Serge
但是摘录似乎表明了相反的情况...提到符号解析取决于文件的顺序,并且这是由所使用的算法所证明的。 - Sathvik Swaminathan
只有库文件有特定的规则。不同的库文件中可能存在相同符号的多个定义。将使用第一个在库文件中找到的符号定义,而忽略其他的定义。然而,该符号的使用可能存在于在此之前或之后放置的文件或库中。对于仅为 .o 文件(或在您的情况下为 .c 文件)的情况,相同符号的多个定义将导致冲突和链接器消息。 - Serge

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