如果我在C程序中加入<stdlib.h>
或<stdio.h>
,我不必在编译时链接这些内容,但我必须使用<math.h>
链接到-lm
与gcc,例如:
gcc test.c -o test -lm
这是什么原因?为什么我必须显式链接数学库而不是其他库?
答案 0 :(得分:218)
stdlib.h
和stdio.h
中的函数在libc.so
(或libc.a
用于静态链接)中实现,默认情况下链接到您的可执行文件中(就像{{ 1}}被指定)。可以指示GCC避免使用-lc
或-nostdlib
选项的此自动链接。
-nodefaultlibs
中的数学函数在math.h
(或libm.so
用于静态链接)中实现,默认情况下libm.a
未链接。这个libm
/ libm
拆分有历史原因,但没有一个令人信服。
有趣的是,C ++运行时libc
需要libstdc++
,因此如果您使用GCC(libm
)编译C ++程序,则会自动链接g++
。< / p>
答案 1 :(得分:71)
请记住,C是一种旧语言,FPU是一种相对较新的现象。我第一次在8位处理器上看到C,即使是32位整数运算也需要做很多工作。其中许多实现甚至没有拥有浮点数学库!
即使在最初的68000台机器上(Mac,Atari ST,Amiga),浮点协处理器通常都是昂贵的附加设备。
要完成所有浮点数学运算,你需要一个相当大的库。数学运算会很慢。所以你很少使用花车。您尝试使用整数或缩放整数执行所有操作。当你必须包括math.h时,你咬紧牙关。通常,你会编写自己的近似值和查找表来避免它。
权衡取舍存在很长时间。有时会有竞争性的数学包称为“fastmath”等。什么是数学的最佳解决方案?真的准确但缓慢的东西?不准确但速度快? trig功能的大表?直到协处理器被保证在计算机中,大多数实现变得明显。我想现在有一些程序员在那里,在嵌入式芯片上工作,试图决定是否引入数学库来处理一些数学问题。
这就是数学不是 标准 的原因。许多或大多数程序都没有使用单个浮点数。如果FPU一直存在并且浮动和双打总是便宜的操作,毫无疑问会有一个“stdmath”。
答案 2 :(得分:67)
由于荒谬的历史实践,没有人愿意修理。将C和POSIX所需的所有功能整合到单个库文件中不仅可以避免一遍又一遍地询问这个问题,而且还可以在动态链接时节省大量时间和内存,因为每个.so
文件链接需要文件系统操作来定位和查找它,以及一些页面用于静态变量,重定位等。
所有功能都在一个库中的实现,-lm
,-lpthread
,-lrt
等选项都是无操作(或链接到空.a
文件)完全符合POSIX,当然更可取。
注意:我在谈论POSIX,因为C本身没有指定有关如何调用编译器的任何内容。因此,您可以将gcc -std=c99 -lm
视为特定于实现的方式,必须为符合行为调用编译器。
答案 3 :(得分:33)
由于time()
和其他一些函数{C}(builtin
)本身定义了libc
,而GCC 总是链接到libc ,除非< / em>您使用-ffreestanding
编译选项。但是,数学函数存在于libm
中,而不是由gcc隐式链接。
答案 4 :(得分:26)
给出了解释here:
因此,如果您的程序使用数学函数并包含
math.h
,那么您需要通过传递-lm
标志来显式链接数学库。这种特殊分离的原因是数学家对计算数学的方式非常挑剔,他们可能想要使用他们自己的数学函数实现而不是标准实现。如果数学函数被归为libc.a
,则不可能这样做。
[编辑]
但是,我不确定我是否同意这一点。如果您有一个提供sqrt()
的库,并且您在标准库之前传递它,那么Unix链接器将采用您的版本,对吗?
答案 5 :(得分:5)
正如ephemient所说,C库libc默认是链接的,这个库包含stdlib.h,stdio.h和其他几个标准头文件的实现。只是添加它,根据“An Introduction to GCC”C中基本“Hello World”程序的链接器命令如下:
ld -dynamic-linker /lib/ld-linux.so.2 /usr/lib/crt1.o
/usr/lib/crti.o /usr/libgcc-lib /i686/3.3.1/crtbegin.o
-L/usr/lib/gcc-lib/i686/3.3.1 hello.o -lgcc -lgcc_eh -lc
-lgcc -lgcc_eh /usr/lib/gcc-lib/i686/3.3.1/crtend.o /usr/lib/crtn.o
请注意链接C库的第三行中的选项 -lc 。
答案 6 :(得分:4)
在An Introduction to GCC - Linking with external libraries中有关于链接到外部库的详细讨论。如果库是标准库的成员(如stdio),那么您不需要指定编译器(实际上是链接器)来链接它们。
编辑:在阅读了其他一些答案和评论之后,我认为libc.a reference和它链接到的两个libm引用都有很多关于两者分开的原因。
请注意,'libm.a'(数学库)中的许多函数都在'math.h'中定义,但在libc.a中不存在。有些可能会让人感到困惑,但经验法则是这样 - C库包含ANSI规定必须存在的那些函数,因此如果只使用ANSI函数,则不需要-lm。相比之下,`libm.a'包含更多功能并支持其他功能,例如matherr回调以及在FP错误情况下遵守几种替代行为标准。有关更多详细信息,请参见libm部分。
答案 7 :(得分:3)
stdio是标准C库的一部分,默认情况下,gcc将链接。
数学函数实现位于单独的libm文件中,默认情况下不链接,因此您必须指定它-lm。顺便说一句,这些头文件和库文件之间没有关系。
答案 8 :(得分:3)
我认为这有点武断。你必须在某处绘制一条线(哪些库是默认的,哪些是需要指定的。)
它让你有机会用具有相同功能的另一个替换它,但我认为这样做并不常见。
编辑:(来自我自己的评论):我认为gcc这样做是为了保持与原始cc的向后兼容性。我猜cc为什么会这样做是因为构建时间 - cc是为功耗远低于我们现在的机器编写的。很多程序都没有浮点数学,他们可能会把每个不常用的库都用掉。我猜测UNIX操作系统的构建时间以及随之而来的工具是驱动力。答案 9 :(得分:3)
如果我把stdlib.h或stdio.h放在一起,我不需要链接那些,但我必须在编译时链接:
stdlib.h
,stdio.h
是标头文件。为方便起见,请包含它们。他们只预测如果您链接到适当的库中,哪些符号将可用。实现在库文件中,这就是函数真正存在的地方。
包含math.h
只是获得所有数学函数访问权限的第一步。
此外,如果您不使用它的功能,则不必链接libm
,即使您执行#include <math.h>
这对您来说只是一个信息性步骤,对于编译器来说也是如此符号。
stdlib.h
,stdio.h
指的是libc
中可用的功能,这些功能恰好始终是链接在一起的,因此用户无需亲自执行此操作。
答案 10 :(得分:2)
我会猜测这是一种让不使用它的应用程序表现得更好的方法。以下是我对此的看法。
x86操作系统(我想其他人)需要在上下文切换时存储FPU状态。但是,大多数操作系统仅在应用尝试首次使用FPU后才会保存/恢复此状态。
除此之外,数学库中可能还有一些基本代码,可以在加载库时将FPU设置为理智的基本状态。
因此,如果您根本没有链接任何数学代码,则不会发生这种情况,因此操作系统根本不必保存/恢复任何FPU状态,从而使上下文切换效率稍高。
只是一个猜测。
编辑:在回复一些评论时,同样的基本前提仍然适用于非FPU案例(前提是制作不使用libm的应用程序会略微执行更好)。
例如,如果在C的早期存在软FPU,那么将libm分开可以防止大量的(并且如果使用的话那么慢)代码被不必要地链接在内。
此外,如果只有静态链接可用,那么类似的参数适用于保持可执行文件大小和编译时间。
答案 11 :(得分:1)
这是一个错误。您不必再明确指定 -lm
。也许如果有足够多的人抱怨它,它就会得到解决。 (我并不真的相信这一点,因为维持这种区别的维护者显然非常固执,但我可以希望。)
答案 12 :(得分:0)
stdio.h
和stdlib.h
之类的所有库都在libc.so
或libc.a
中实现,并且默认情况下由链接器链接。 libc.so
的库在编译时自动链接,并包含在可执行文件中。
但是math.h
在libm.so
或libm.a
中有其实现,它与libc.so
是分开的,并且默认情况下它没有链接,您必须在编译程序时手动链接它
在gcc
中使用-lm
标志。
gnu gcc团队将其设计为与其他头文件分开,而其他头文件默认情况下会链接,但math.h文件不会。
此处阅读了编号14.3的项目,如果您愿意,可以全部阅读:
why we have to link math.h in gcc?
查看这篇文章:using the library
看一下用法:
action
答案 13 :(得分:0)
请注意,即使您使用某些C数学函数,也不一定总是需要指定-lm
。
例如,以下简单程序:
#include <stdio.h>
#include <math.h>
int main() {
printf("output: %f\n", sqrt(2.0));
return 0;
}
可以使用以下命令编译并成功运行:
gcc test.c -o test
在gcc 7.5.0(在Ubuntu 16.04上)和gcc 4.8.0(在CentOS 7上)上进行了测试。
帖子here给出了一些解释:
您调用的数学函数是由编译器内置函数实现的
另请参阅: