我是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找不到我的头文件中定义的任何函数,即使我已经包含它。
我认为我在这里做了一些根本性的错误,但我真的找不到什么。
答案 0 :(得分:8)
这里有两个问题:
test.c
编译时没有提供库中例程声明的头文件? test.c
,但在命令行中最后列出的是test.c
?我们无法提供第一个完整答案,因为您没有显示源代码。正如其他人所指出的那样,C有一些余地可以提供隐含的声明,主要是出于历史原因。这些隐式声明可能与您的例程的实际定义不匹配,这可能会导致错误,因此您应该避免使用隐式声明。
你的第二个问题的答案就是这个。给定您显示的两个命令行之一,编译器将编译test.c
,然后调用链接器。 (编译器也可以做其他事情,例如编译而不链接或链接来自先前编译的源的对象模块。)当编译器调用链接器时,它按照与您的顺序对应的顺序传递链接器参数将它们传递给链接器。特别是,如果您将-lcde
放在test.c
之前,那么编译器会将-lcde
放在来自test.c
,test.o
的对象模块之前,当它运行时接头
这很重要,因为链接器的运行方式。除此之外,链接器还有一个需要定义的符号列表。该列表最初为空。链接器从左到右处理命令行的输入。当链接器在其命令行中看到类似test.o
的对象模块时,它会读取对象模块并对其进行处理。通常,对象模块包含对未定义的某些符号的引用,例如对库例程的调用。如果链接器已经从先前文件中定义了这些符号,则它会将引用连接到定义。如果它没有定义,它会将符号添加到链接器需要定义的符号列表中。
当链接器处理库文件时,它会检查库中的每个对象模块,以查看该对象模块是否定义了链接器所需定义列表中的符号。如果是,则链接器读取该对象模块并将其(及其定义)添加到链接器正在构建的可执行文件中。如果不是,链接器将忽略该对象模块。
现在我们可以看到为什么test.c -lcde
有效,但-lcde test.c
没有。在前一种情况下,链接器会列出test.o
所需的所有内容,然后从cde
库中获取这些内容。在后一种情况下,链接器会看到cde
库,但它不需要任何东西,所以它继续而不从库中取任何东西。然后链接器读取test.c
并添加到所需符号列表中。然后命令行结束,链接器没有更多文件,但仍然没有定义的符号。所以它报告错误。
因此,通常,库应该在命令行中列出最后一个。
答案 1 :(得分:3)
如果没有头文件,您将无法获得功能的原型。 这不是错误,但是C编译器将假设您调用的函数的原型,编译器没有看到原型
int functionname()
empty()表示类似"任何参数",所以你传递给该函数的任何参数,都是调用函数的方式。
取决于参数的实际类型和函数的返回值,这可能恰到好处,或者它可能至少在某些情况下起作用。
在链接阶段,当您生成可执行文件时,链接器将只链接一个函数的名称。如果你调用名为" foo"的函数,你的图书馆也有一个名为" foo"的功能符号,这是链接器选择的内容。
答案 2 :(得分:1)
在C中,假设一个没有原型的函数有这样的原型:
int function();
这意味着一个函数接受任意数量的任何参数,返回一个int。 所以,当然它可以工作,但尝试传递一个参数,你的函数在libcde中没有预料到它会崩溃。
你可以将libelf.a放在你的库中(而不是将它的目标文件复制到你的库中),这很少是一个好主意:你变得依赖于本地系统配置(你必须找到它的位置)图书馆位于。
答案 3 :(得分:0)
C ++中的标头也用于为要调用的函数提供原型。编译器使用原型来帮助您在调用外部函数时不会错误参数count / type。所以从这一点来看,静态或动态库没有任何区别。