为什么要用C语言链接数学库?

如果我在C程序中包含<stdlib.h><stdio.h>,在编译时我不必链接它们,但我必须链接到<math.h>,在GCC中使用- lm,例如:

gcc test.c -o test -lm

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

150499 次浏览

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

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

我认为这有点武断。您必须在某个地方划清界限(哪些库是默认的,哪些库需要指定)。

它让你有机会用一个具有相同功能的不同产品来替代它,但我不认为这样做很常见。

我认为GCC这样做是为了保持与原始cc可执行文件的向后兼容性。我猜为什么cc这样做是因为构建时间——cc是为远不如我们现在的机器编写的。许多程序没有任何浮点数学运算,它们可能会将所有不常用的库从默认值中删除。我猜Unix操作系统的构建时间和与之配套的工具是驱动因素。

我将猜一猜,它是一种让根本不使用它的应用程序执行得更好的方法。以下是我的看法。

x86操作系统(我想还有其他操作系统)需要在上下文切换中存储FPU状态。然而,大多数操作系统只在应用程序第一次尝试使用FPU时才会保存/恢复这种状态。

除此之外,在数学库中可能还有一些基本代码,可以在加载库时将FPU设置为正常的基状态。

所以,如果你根本不链接任何数学代码,这一切都不会发生,因此操作系统根本不需要保存/恢复任何FPU状态,使上下文切换稍微更有效。

不过这只是猜测。

同样的基本前提仍然适用于非fpu情况(前提是,它是为了让没有使用libm的应用程序表现得更好)。

例如,如果有一个软fpu,就像在早期的c语言中那样,那么将libm分开可以防止大量的大代码(如果使用了它,则会很慢)不必要地被链接。

此外,如果只有静态链接可用,则适用类似的论点,即它将保持可执行文件的大小和编译时间较短。

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

在阅读了其他一些答案和评论之后,我认为libc。一个参考和它所链接的libm引用都有很多关于为什么两者是分开的说明。

请注意'libm. xml中的许多函数。A '(数学库)在'math.h'中定义,但在lib . A中不存在。有些是,这可能会让人困惑,但经验法则是——C库包含ANSI规定必须存在的那些函数,因此如果只使用ANSI函数,就不需要-lm。相比之下,' libm。a'包含更多的函数,并支持额外的功能,如matherr回调和在FP错误时遵守几个替代的行为标准。有关详细信息,请参阅libm部分。

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链接。

如果我放入stdlib.h或stdio.h,我不需要链接它们,但我必须在编译时链接:

stdlib.hstdio.h是头文件。你把它们包括进来是为了方便。如果你链接到适当的库中,它们只预测哪些符号是可用的。实现在库文件中,这是函数真正存在的地方。

包含math.h只是获得对所有数学函数的访问的第一步。

同样,如果你不使用libm的函数,你也不必链接到libm,即使你做了#include <math.h>,这对你和编译器来说只是一个关于符号的信息步骤。

stdlib.hstdio.h指的是libc中可用的函数,这些函数总是被链接到,这样用户就不必自己去做。

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

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

要完成所有这些浮点运算,您需要一个相当大的库。数学运算会很慢。所以你很少使用浮动。你试着用整数或按比例的整数来做所有的事情。当你必须包括math.h时,你咬紧牙关。通常,您会编写自己的近似和查找表来避免这种情况。

权衡存在了很长一段时间。有时会有竞争的数学软件包,称为“快速数学”或类似的。数学的最佳解决方案是什么?真的很准确但是很慢?不准确但速度快?三角函数的大表格?直到协处理器被保证存在于计算机中,大多数实现才变得显而易见。我可以想象,现在某处有一些程序员正在研究嵌入式芯片,试图决定是否引入数学库来处理一些数学问题。

这就是为什么数学不是标准< em > < / em >。许多或大多数程序都没有使用一个浮点数。如果fpu一直存在,浮点数和双精度浮点数的操作成本一直很低,毫无疑问,就会有一个“标准数学”。

如前所述,C库libc默认是链接的,该库包含stdlib.h、stdio.h和其他几个标准头文件的实现。只是补充一下,根据&;GCC简介"链接器命令用于基本的“Hello world”;C语言的程序如下所示:

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库的选项信用证

在这里给出了一个解释:

因此,如果你的程序使用数学函数并包含math.h,那么你需要通过传递-lm标志显式链接数学库。这种特殊分离的原因是数学家对计算数学的方式非常挑剔,他们可能希望使用自己的数学函数实现而不是标准实现。如果数学函数被集中到libc.a中,就不可能做到这一点。

(编辑)

不过,我不确定我是否同意这一点。如果你有一个提供sqrt()的库,并且你在标准库之前传递它,Unix链接器将获取你的版本,对吗?

因为time()和其他一些函数是在C库(libc)中定义的builtin, GCC 总是链接到libc 除非,所以使用-ffreestanding编译选项。然而,数学函数存在于libm中,它没有被gcc隐式链接。

因为荒谬的历史实践,没有人愿意修正。将C和POSIX所需的所有函数合并到一个库文件中,不仅可以避免反复询问这个问题,还可以在动态链接时节省大量的时间和内存,因为链接的每个.so文件都需要文件系统操作来定位和找到它,以及它的静态变量、重定位等几页。

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

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

所有像stdio.hstdlib.h这样的库在libc.solibc.a中都有它们的实现,默认情况下由链接器链接。libc.so的库在编译时自动链接,并包含在可执行文件中。

但是math.hlibm.solibm.a中有它的实现,这与libc.so是分开的。默认情况下它不会被链接,你必须在GCC中使用- lm标记编译程序时手动链接它。

GNU GCC团队将其设计为与其他头文件分离,而默认情况下其他头文件会被链接,但math.h文件不会。

这里阅读14.3项,如果你愿意,你可以全部阅读: math.h需要连接的原因 < / p >

看看这篇文章:为什么我们要在GCC中链接math.h ?

来看看用法:

使用库

注意,即使使用一些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上)上进行了测试。

文章在这里给出了一些解释:

你调用的数学函数是由编译器内置函数实现的

参见:

这是个bug。你不必再显式地指定-lm了。也许如果有足够多的人抱怨,它就会被修复。(我不认真地相信这一点,因为维护这一区别的人显然是非常固执的,但我希望如此。)