1

我是 C 的新手,尤其是编写静态库,我的库中出现了奇怪的行为。

我写了一个名为 cde 的小静态库。我用 gcc 将不同的部分编译成 .o 文件,然后我用 ar 将它们全部放在一个 .a 文件中

现在,当我想测试我的库时,我会执行以下操作:

gcc test.c -L../bin -lcde -lelf

libcde.a 是我位于 ../bin 的库。libelf.a 是我的图书馆需要的图书馆(我不知道如何将它直接放入我自己的图书馆......)。

问题是我可以调用我的库的每个函数,而不必包含我的库头文件。这怎么可能?在编译时不应链接文件,因此编译器应该不知道我的库中有哪些函数可用...

当我按照以下方式运行它时,

gcc -L../bin -lcde -lelf test.c

文件 test.c 找不到我在头文件中定义的任何函数,即使我已经包含它。

我想我在这里做错了什么,但我真的不知道是什么。

4

4 回答 4

9

这里有两个问题:

  1. 为什么test.c编译时没有提供库中例程声明的标头?
  2. 为什么链接可以在命令行中test.c列出的第一个使用,但不能在test.c命令行的最后列出?

我们无法对第一个提供完整的答案,因为您没有显示源代码。正如其他人所指出的,C 有一些余地来提供隐式声明,主要是出于历史原因。这些隐式声明可能与您的例程的实际定义不匹配,这可能会导致错误,因此您应该避免隐式声明。

你的第二个问题的答案是这样的。给定您显示的两个命令行中的任何一个,编译器都会编译test.c然后调用链接器。(也可以让编译器做其他事情,例如在不链接的情况下进行编译,或者从先前编译的源中链接目标模块。)当编译器调用链接器时,它会按照与您的顺序相对应的顺序传递链接器参数将它们传递给链接器。特别是,如果您放在-lcdebefore test.c,则编译器在运行链接器时将放在-lcde来自test.c,的目标模块之前。test.o

这很重要,因为链接器的操作方式。除此之外,链接器有一个需要定义的符号列表。该列表最初是空的。链接器从左到右处理来自命令行的输入。当链接器在其命令行中看到一个目标模块test.o时,它会读取该目标模块并对其进行处理。通常,对象模块包含对它未定义的某些符号的引用,例如对库例程的调用。如果链接器已经从以前的文件中定义了这些符号,它会将引用连接到定义。如果它没有定义,它会将符号添加到链接器需要定义的符号列表中。

当链接器处理库文件时,它会检查库中的每个目标模块,以查看该目标模块是否定义了链接器所需定义列表中的符号。如果是这样,链接器会读取该对象模块并将其(及其定义)添加到链接器正在构建的可执行文件中。如果不是,链接器将忽略该目标模块。

Now we can see why test.c -lcde works but -lcde test.c does not. In the former case, the linker makes a list of everything that test.o needs, then it gets those things from the cde library. In the latter case, the linker sees the cde library but does not need anything from it yet, so it goes on without taking anything from the library. Then the linker reads test.c and adds to the list of needed symbols. Then the command line ends, and the linker has no more files but still has symbols without definitions. So it reports an error.

So, generally, libraries should be listed last on command lines.

于 2012-07-06T11:25:40.073 回答
4

如果没有头文件,您将没有函数的原型。这不是错误,但是 C 编译器将假定您调用的函数的原型,编译器没有看到原型是

 int functionname()

空 () 表示类似于“任何参数”的意思,因此您传递给该函数的任何参数都是调用该函数的方式。

根据参数的实际类型和函数的返回值,这可能会恰到好处,或者至少在某些情况下似乎可以工作。

在链接阶段,当您生成可执行文件时,链接器将仅以函数名称进行链接。如果您调用一个名为“foo”的函数,并且您的库也有一个名为“foo”的函数符号,那么这就是链接器所选择的。

于 2012-07-06T09:26:14.513 回答
1

在 C 中,假定没有原型调用的函数具有如下原型:

int function();

这意味着一个函数接受任意数量的任意参数,返回一个 int。所以,它当然可以工作,但是尝试传递一个参数,你的函数在 libcde 中并不期望它会崩溃。

您可以将 libelf.a 放入您的库中(而是将目标文件从其中复制到您的库中),这很少是一个好主意:您变得依赖于本地系统配置(您必须找到库所在的位置)。

于 2012-07-06T09:28:16.553 回答
0

C++ 中的标头还用于为您要调用的函数提供原型。编译器使用原型来帮助您在调用外部函数时不会弄错参数计数/类型。因此,到目前为止,您的静态或动态库没有任何区别。

于 2012-07-06T09:22:03.337 回答