关于编译:为什么必须在C中链接数学库?

Why do you have to link the math library in C?

如果我在C程序中包含,则在编译时不必链接它们,但是我必须链接到,例如,将-lm与gcc一起使用:

1
gcc test.c -o test -lm

这是什么原因呢? 为什么我必须显式链接数学库,而不是其他库?


stdlib.hstdio.h中的函数在libc.so中(在静态链接中为libc.a)具有实现,默认情况下已链接到您的可执行文件中(就像指定了-lc)。可以指示GCC避免使用-nostdlib-nodefaultlibs选项进行此自动链接。

math.h中的数学函数在libm.so中实现(对于静态链接,则为libm.a),默认情况下未链接libmlibm / libc拆分有历史原因,但没有一个非常令人信服。

有趣的是,C ++运行时libstdc++需要libm,因此,如果使用GCC(g++)编译C ++程序,则会自动获得libm链接。


请记住,C是一门古老的语言,FPU是相对较新的现象。我首先在8位处理器上看到C语言,即使要做32位整数算术也需要很多工作。其中许多实现甚至都没有可用的浮点数学库!

即使在最初的68000台计算机(Mac,Atari ST,Amiga)上,浮点协处理器也常常是昂贵的附件。

要进行所有浮点数学运算,您需要一个相当大的库。数学将变得缓慢。因此,您很少使用浮子。您尝试使用整数或可缩放整数进行所有操作。当您必须包括math.h时,您会咬牙切齿。通常,您会编写自己的近似值和查找表来避免这种情况。

权衡存在很长时间。有时会有称为" fastmath"之类的竞争性数学软件包。什么是数学的最佳解决方案?真的很准确但是很慢的东西?不准确但是很快?大表触发功能?直到确保协处理器位于计算机中,大多数实现才变得显而易见。我想象现在有某个程序员在某个嵌入式芯片上工作,试图决定是否引入数学库来处理一些数学问题。

这就是为什么数学不是标准的原因。许多甚至大多数程序都没有使用单个浮点数。如果FPU一直存在,并且浮动和双倍交易总是便宜的话,那么无疑会有" stdmath"。


由于荒谬的历史实践,没有人愿意解决。将C和POSIX所需的所有功能整合到一个库文件中,不仅可以避免一遍又一遍地问这个问题,而且在动态链接时还可以节省大量的时间和内存,因为每个链接的.so文件都需要文件系统操作以查找和查找它,还有几页用于其静态变量,重定位等。

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

注意:我说的是POSIX,因为C本身未指定有关如何调用编译器的任何内容。因此,您可以将gcc -std=c99 -lm视为必须调用编译器以实现一致行为的特定于实现的方式。


因为time()和某些其他函数是在C库(libc)本身中定义的,并且GCC始终链接到libc,除非您使用-ffreestanding编译选项。但是,数学函数位于libm中,而gcc没有将其隐式链接。


这里给出一个解释:

So if your program is using math functions and including math.h, then you need to explicitly link the math library by passing the -lm flag. The reason for this particular separation is that mathematicians are very picky about the way their math is being computed and they may want to use their own implementation of the math functions instead of the standard implementation. If the math functions were lumped into libc.a it wouldn't be possible to do that.

[编辑]

不过,我不确定我是否同意。如果您有一个提供sqrt()的库,并且在标准库之前传递了它,那么Unix链接器将使用您的版本,对吗?


正如临时人员所说,C库libc默认情况下是链接的,并且该库包含stdlib.h,stdio.h和其他几个标准头文件的实现。只是要添加它,根据" GCC简介",使用C编写的基本" Hello World"程序的链接器命令如下:

1
2
3
4
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选项。


在GCC简介-与外部库的链接中,有关于对外部库的链接的详尽讨论。如果一个库是标准库的成员(如stdio),则无需指定编译器(实际上是链接器)来链接它们。

编辑:在阅读了其他一些答案和评论后,我认为libc.a引用和链接到两者的libm引用中有很多要说为什么两者分开。

Note that many of the functions in 'libm.a' (the math library) are defined in 'math.h' but are not present in libc.a. Some are, which may get confusing, but the rule of thumb is this--the C library contains those functions that ANSI dictates must exist, so that you don't need the -lm if you only use ANSI functions. In contrast, `libm.a' contains more functions and supports additional functionality such as the matherr call-back and compliance to several alternative standards of behavior in case of FP errors. See section libm, for more details.


If I put stdlib.h or stdio.h, I don't have to link those but I have to link when I compile:

stdlib.hstdio.h是头文件。为了方便起见,将它们包括在内。他们仅预测在链接到正确的库后将提供哪些符号。实现位于库文件中,这是功能的真正所在。

包括math.h只是访问所有数学函数的第一步。

另外,即使您不使用libm的功能,也不必针对libm进行链接,即使您对符号的编译器执行了#include 只是对您来说是一个参考步骤。

stdlib.hstdio.h指的是libc中可用的功能,这些功能恰好总是被链接在一起,因此用户不必自己做。


我认为这是任意的。您必须在某处画一条线(哪些是默认库,哪些需要指定)。

它使您有机会用功能相同的另一种替代它,但是我认为这样做不是很普遍。

编辑:(从我自己的评论):我认为gcc这样做是为了保持与原始cc的向后兼容性。我对cc为什么要这样做的猜测是由于构建时间-cc是为功率比现在少得多的机器编写的。许多程序没有浮点数学运算,因此它们可能会将默认情况下不常用的每个库都使用了。我猜想UNIX OS的构建时间及其附带的工具是推动力。


我想这是一种使根本不使用它的应用程序性能更好的方法。这是我的想法。

x86 OS(以及我想象的其他OS)需要在上下文切换中存储FPU状态。但是,大多数OS仅在应用程序首次尝试使用FPU之后才费心保存/恢复此状态。

除此之外,数学库中可能还包含一些基本代码,这些基本代码会在加载库时将FPU设置为基本状态。

因此,如果您根本不链接任何数学代码,则不会发生任何事情,因此,操作系统根本不必保存/恢复任何FPU状态,从而使上下文切换效率更高。

只是一个猜测而已。

编辑:作为对某些评论的回应,相同的基本前提仍然适用于非FPU案例(前提是要使不使用libm的应用程序的性能稍好一些)。

例如,如果有一个软FPU,它在C的早期就很不错。那么将libm分开可以防止不必要地链接很多大的(如果使用的话,速度很慢)代码。

另外,如果只有静态链接可用,那么将应用类似的论点,即它将减小可执行文件的大小并缩短编译时间。


stdio是标准C库的一部分,默认情况下,gcc将链接到该库。

数学函数的实现位于单独的libm文件中,默认情况下未链接到该文件,因此您必须将其指定为-lm。顺便说一下,这些头文件和库文件之间没有关系。