在C中为什么要链接数学库?

333
如果在C程序中包含了<stdlib.h><stdio.h>,则编译时不需要链接这些库,但是必须使用-lm链接<math.h>,例如使用GCC编译。
gcc test.c -o test -lm

这是为什么?为什么我必须明确链接数学库,但不需要链接其他库?


14个回答

310
stdlib.hstdio.h中的函数实现在libc.so(或者对于静态链接是libc.a)中,这个库默认会被链接到你的可执行文件中(就像指定了-lc一样)。使用-nostdlib-nodefaultlibs 选项可以告诉GCC避免这种自动链接。 math.h中的数学函数实现在libm.so(或者对于静态链接是libm.a)中,但libm并不会默认链接进来。历史原因导致了这种libm/libc分离方式,但这些原因都不是很令人信服。
有趣的是,C++运行时库libstdc++需要libm,因此如果你使用GCC(g++)编译一个C++程序,libm会被自动链接进来。

13
这与Linux无关,因为在Linux出现之前就很常见了。我猜这与试图将可执行文件的大小最小化有关,因为有很多程序不需要数学函数。 - David Thornley
52
在古老的系统中,如果数学函数包含在libc中,则编译所有程序的速度会变慢,输出的可执行文件会更大,并且运行时需要更多的内存,对于大多数根本不使用这些数学函数的程序没有任何好处。如今,我们有很好的共享库支持,即使静态链接,标准库也被设置为可以丢弃未使用的代码,因此这些都不再是好的理由了。 - ephemient
47
即使在早期,链接到一个库也不会将该库的所有内容都拉入可执行文件中。尽管经常被忽视,但链接器在历史上一直非常高效。 - anon
10
共享库比你想象的要早得多,它们诞生于20世纪50年代,而不是80年代。 - anon
9
我想说的是,归根结底,我们看到的不过是GCC保守主义:“一直以来都是这样做的”。我只希望他们在编写编译器扩展时也能采用同样的理论。 - anon
显示剩余14条评论

99

请记住C语言是一种古老的语言,FPU则是一个相对较新的现象。我第一次在8位处理器上看到C语言时,即使进行32位整数运算也需要大量工作。许多这样的实现甚至没有可用的浮点数学库!

即使在第一批68000计算机(Mac、Atari ST和Amiga)上,浮点数协处理器通常也是昂贵的附加组件。

要进行所有这些浮点数学运算,您需要一个非常大的库。而且计算速度会很慢,因此您很少使用浮点数。您尝试使用整数或缩放整数来完成所有任务。当您不得不包含math.h时,要咬紧牙关。通常情况下,您会编写自己的近似值和查找表来避免使用它。

长期以来存在着权衡取舍。有时会有竞争的数学软件包,称为“fastmath”或类似名称。什么是数学问题的最佳解决方案?真正精确但缓慢的东西?不准确但快速的东西?三角函数的大型表格?直到协处理器可以保证在计算机中存在,大多数实现才变得明显。我想象中,现在某个地方有一位程序员正在嵌入式芯片上工作,试图决定是否引入数学库来处理某些数学问题。

这就是为什么数学不是标准的原因。许多甚至大多数程序都没有使用单个浮点数。如果FPUs一直存在,并且浮点数和双精度浮点数始终便宜易操作,毫无疑问会出现“stdmath”。


1
嘿,我在桌面电脑上使用 Java 的 Pade 近似公式来计算 (1+x)^y。对数、指数和幂仍然很慢。 - quant_dev
1
好点子。我在音频插件中看过sin()的近似值。 - Nosredna
13
这就解释了为什么默认情况下没有链接 libm,但是自 C89 起,数学库一直是标准库的一部分,甚至在此之前,K&R 也 事实上 将其标准化了,因此你提到的 "stdmath" 说法不合适。 - Fred Foo
1
@FredFoo 类型和接口已经标准化,但实现并没有。我认为Nosredna指的是一个标准数学库。 - Tim Bird

90

由于无法解决的荒谬历史惯例,C和POSIX所需的所有功能都合并到单个库文件中不仅可以避免反复问这个问题,而且在动态链接时会节省大量时间和内存,因为每个链接的.so文件都需要进行文件系统操作来查找它,并且需要一些页面来处理其静态变量、重定位等。

如果所有函数都在一个库中,并且-lm-lpthread-lrt等选项都是空操作(或链接到空的.a文件),那么实现就是完全符合 POSIX 标准的,而且肯定更可取。

注意:我谈论的是 POSIX,因为 C 本身并没有指定如何调用编译器。因此,您可以将gcc -std=c99 -lm视为实现特定的编译器调用方式,以获得符合标准的行为。


11
指出 POSIX 并不要求存在分离的 libm、libc 和 librt 库而给你点赞。例如,在 Mac OS 上,所有内容都位于单个 libSystem 中(它还包括 libdbm、libdl、libgcc_s、libinfo、libm、libpoll、libproc 和 librpcsvc)。 - F'x
5
未经链接或数字支持就猜测库查找对性能的影响,会被判定为-1分。“查看性能剖面,不要臆断”。 - F'x
15
这不是猜测。我没有发表过任何论文,但我自己做了所有的测量,差异巨大。只需使用 strace 中的一个计时选项来观察启动时间花费在动态链接上的数量,或者比较在所有标准工具都是静态链接的系统上运行 ./configure 和在动态链接的系统上运行的区别。即使是主流的桌面应用程序开发人员和系统集成商也意识到动态链接的成本;这就是为什么会有类似于 prelink 的东西存在。我相信你可以在一些论文中找到基准测试数据。 - R.. GitHub STOP HELPING ICE
7
不知道为什么之前我忘了提到这个:strace -tt 命令可以轻松地显示动态链接所花费的时间,不太美观。在 Linux 上,检查 /proc/sys/smaps 可以显示额外库的内存开销。 - R.. GitHub STOP HELPING ICE
3
这个回答的很多内容似乎是错误地假设链接库会将它的所有内容都引入进来,而不仅仅是你使用过的函数(以翻译单元为粒度,但历史上它们被正确地分割成单独的函数)。 - R.. GitHub STOP HELPING ICE
显示剩余7条评论

33

由于time()和其他一些函数是C库(libc)中定义的内置函数,GCC编译器总是链接到libc,除非您使用-ffreestanding编译选项。但是数学函数位于libm中,GCC不会自动链接。


9
LLVM gcc上我不需要加上-lm,为什么? - bot47

29

在这里给出了一个解释 (链接)

如果你的程序使用数学函数并包含math.h,那么你需要通过传递-lm标志显式链接数学库。这种特定分离的原因是数学家非常挑剔关于他们的数学是如何被计算的,并且他们可能想使用自己实现的数学函数而不是标准实现。如果将数学函数合并到libc.a中,那么这是不可能的。

[编辑]

我不确定我同意这个解释。如果你有一个提供了sqrt()的库,并且在标准库之前传递它,Unix链接器会采用你的版本,对吗?


11
我认为这并不能保证一定会发生;你可能会遇到符号冲突。这可能取决于链接器和库的布局。我仍然觉得这个理由很弱;如果你正在制作一个自定义的平方根函数,即使它执行相同的操作,你也不应该将其命名为标准平方根函数的名称... - ephemient
2
实际上,创建一个名为sqrt的自定义函数(非静态)会导致程序出现未定义行为。 - R.. GitHub STOP HELPING ICE
@Bastien 很好的发现。关于你的观点,你所说的“标准库之前”是什么意思?我认为标准库默认情况下已经被链接进来了,不需要通过命令行选项进行链接。因此,标准库将是链接器的首选,而且无法在标准库之前放置自己的实现。 - Rocky Inde
@RockyInde:看看我的回答,我想我实际上是指“标准数学库”之前。但我认为有编译器选项可以不链接标准C库,这将允许您传递自己的库。 - Bastien Léonard
@BastienLéonard 我使用的是版本为7.2的gcc,其中-lm是完全可选的。有什么想法吗? - Donghua Liu
只要您不使用2005年后期的某些小众发行版,就可以保证这种情况发生。 - clockw0rk

8

GCC介绍-链接到外部库中,有关于链接到外部库的全面讨论。如果一个库是标准库的成员(如stdio),那么您不需要告诉编译器(实际上是链接器)链接它们。

阅读其他答案和评论后,我认为libc.a参考文献以及它链接的libm参考文献都对为什么这两个库是分开的有很多解释。

请注意,'libm.a'(数学库)中的许多函数在'math.h'中定义,但在libc.a中不存在。其中一些可能会令人困惑,但经验法则是这样的 - C库包含ANSI指定必须存在的函数,因此如果只使用ANSI函数,则不需要-lm。相反,`libm.a'包含更多功能并支持附加功能,例如matherr回调和在FP错误情况下符合几种替代行为的标准。有关详细信息,请参见libm部分。


1
这并没有回答为什么你必须单独链接匹配库的问题。显然,你想要单独链接OpenGL库,但可以说数学库通常是有用的。 - David Thornley
@David:你说得对。从问题中我并没有清楚地看出这是OP所问的部分。当你发表评论时,我正在编辑我的回答。 - Bill the Lizard
我知道为什么编译使用 sqrt 函数的程序时,不需要通过 -lm 包含库也能正常工作。谢谢! - L_K

6
如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

注意第三行中的选项-lc,它链接了C库。

4
我认为这有点武断。您必须在某个地方划定界限(哪些库是默认的,哪些需要指定)。
它给了您机会用具有相同功能的不同库替换它,但我认为这种情况并不常见。
我认为GCC这样做是为了保持与原始cc可执行文件的向后兼容性。我猜cc这样做的原因是由于构建时间 - cc是为比我们现在拥有的更少的计算能力的机器编写的。许多程序没有任何浮点数学,他们可能将所有不常用的库从默认中取出。我猜想Unix操作系统和与其相关的工具的构建时间是推动力。

我认为这个问题背后的心态是,libm 的内容在很大程度上是标准 C 库的一部分,为什么它们不在 libc 中呢? - Evan Teran
2
gcc的原因是为了保持与AT&T Unix中原始cc的兼容性。我在1988年使用3B2,你必须使用-lm来获取数学函数库。当时对我来说似乎完全是武断的。在Visual Studio中,我不记得曾经需要添加数学函数库,但有时需要添加其他看似c-runtime库。我认为编译器供应商有一个理由(构建时间?),但现在,我打赌gcc只是试图向后兼容。 - Lou Franco

4
如果我使用stdlib.h或stdio.h,我不必链接它们,但是在编译时必须链接它们:
stdlib.h和stdio.h是头文件。您可以包含它们以方便自己。它们只会预测如果链接适当的库将会有哪些符号可用。实现在库文件中,那才是函数真正存在的地方。
包含math.h只是获得访问所有数学函数的第一步。
另外,即使您执行#include ,也不必针对libm进行链接,如果您不使用其函数的话,这只是一个信息步骤,为您和编译器提供关于符号的信息。
stdlib.h和stdio.h是libc中可用函数的引用,libc始终被链接,因此用户不必自己执行链接。

4

这是一个错误。你不应该再显式地指定-lm了。也许如果有足够多的人抱怨,它会被修复。 (我并不真正相信这一点,因为坚持这种区分的维护者显然非常固执,但我可以希望。)


GCC 的一个 bug? - chenzhongpu

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