为啥必须在 C 中链接数学库?

Posted

技术标签:

【中文标题】为啥必须在 C 中链接数学库?【英文标题】:Why do you have to link the math library in C?为什么必须在 C 中链接数学库? 【发布时间】:2010-11-05 06:08:08 【问题描述】:

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

gcc test.c -o test -lm

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

【问题讨论】:

【参考方案1】:

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

【讨论】:

这与 Linux 无关,因为它早在 Linux 之前就很常见。我怀疑这与尝试最小化可执行文件大小有关,因为有很多程序不需要数学函数。 在古老的系统上,如果数学函数包含在 libc 中,那么编译所有程序会更慢,输出的可执行文件会更大,运行时需要更多的内存,对大多数人没有好处 完全不使用这些数学函数的程序。现在我们对共享库有很好的支持,即使在静态链接时,标准库也被设置为可以丢弃未使用的代码,所以这些都不再是好的理由了。 @ephemient 即使在过去,链接到库并不会将库的所有内容都拉入可执行文件。链接器虽然是一种经常被忽视的技术,但在历史上一直非常有效。 @ephemient 此外,共享库的存在时间比您想象的要长。它们是在 1950 年代发明的,而不是 1980 年代。 我想归根结底,我们所看到的只不过是 GCC 的保守主义:“它总是这样工作的”。我只希望他们对编译器扩展应用相同的推理。【参考方案2】:

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

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

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

权衡取舍由来已久。有时会出现相互竞争的数学包,称为“fastmath”等。数学的最佳解决方案是什么?真的准确但缓慢的东西?不准确但速度很快?三角函数的大表?直到协处理器被保证在计算机中,大多数实现才变得显而易见。我想现在某个地方有一些程序员,正在研究嵌入式芯片,试图决定是否引入数学库来处理一些数学问题。

这就是数学不是标准的原因。许多甚至大多数程序都没有使用单个浮点数。如果 FPU 一直存在并且浮点数和双精度数总是很便宜,那么毫无疑问会有一个“stdmath”。

【讨论】:

嘿,我在台式电脑中使用 Java 中 (1+x)^y 的 Pade 近似值。 Log、exp 和 pow 仍然很慢。 好点。我已经在音频插件中看到了 sin() 的近似值。 这解释了为什么 libm 默认没有链接,但数学是 C89 的标准,在此之前,K&R 事实上标准化它,所以你的“stdmath”评论没有意义。 @FredFoo 类型和接口是标准化的,但不是实现。我认为 Nosredna 指的是标准数学库。【参考方案3】:

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

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

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

【讨论】:

+1 指出 POSIX 不需要存在分离的 libm、libc 和 librt 库。例如,在 Mac OS 上,所有内容都位于单个 libSystem 中(还包括 libdbm、libdl、libgcc_s、libinfo、libm、libpoll、libproc 和 librpcsvc)。 –1 用于推测库查找对性能的影响,而无需使用链接或数字进行备份。 “简介。不要推测” 这不是猜测。我没有任何已发表的论文,但我自己完成了所有测量,并且差异很大。只需将strace 与其中一个计时选项一起使用,即可查看在动态链接上花费了多少启动时间,或者比较在所有标准实用程序都静态链接的系统上与动态链接的系统上运行./configure .甚至主流桌面应用程序开发人员和系统集成商都知道动态链接的成本;这就是存在诸如预链接之类的东西的原因。我相信您可以在其中一些论文中找到基准。 @FX:不知道为什么我之前忘了提这个:strace -tt 会很容易地告诉你花在动态链接上的时间。这不漂亮。在 Linux 上,检查 /proc/sys/smaps 会显示额外库的内存开销。 @TimBird:大部分答案似乎都错误地假设链接库会从中提取所有内容,而不仅仅是函数(在翻译单元粒度上,但从历史上看,它们被正确拆分为您使用的单个函数)。【参考方案4】:

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

【讨论】:

在 LLVM gcc 上我不必添加 -lm。这是为什么呢?【参考方案5】:

给出解释here:

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

[编辑]

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

【讨论】:

我认为不能保证会发生这种情况;您可能最终会出现符号冲突。它可能取决于链接器和库的布局。我仍然觉得这个理由很弱;如果你正在制作一个自定义 sqrt 函数,你真的不应该给它与标准 sqrt 函数相同的名称,即使它做同样的事情...... 确实,将您自己的函数(非静态)命名为 sqrt 会导致程序具有未定义的行为。 @Bastien 很好的发现。说到你的意思,“在标准库之前”是什么意思?我想,标准库是默认链接的,不需要通过命令行选项链接。因此,标准库将是链接器的首选,不能将自己的实现放在“标准库之前”。 @RockyInde:看看我的回答,我想我的意思实际上是“在标准数学库之前”。但我认为有编译器选项可以不链接标准 C 库,这将允许您传递您的。 @BastienLéonard 我使用 7.2 版的 gcc,-lm 是完全可选的。任何想法【参考方案6】:

An Introduction to GCC - Linking with external libraries 中对链接到外部库进行了彻底的讨论。如果一个库是标准库的成员(如 stdio),那么您无需指定编译器(实际上是链接器)来链接它们。

编辑:在阅读了其他一些答案和 cmets 之后,我认为 libc.a reference 和它链接到的 libm 参考对于为什么两者是分开的有很多话要说。

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

【讨论】:

这没有回答为什么你必须单独链接匹配库的问题。显然,您希望必须单独链接 OpenGL 库,但可以说数学库通常很有用。 @David:没错。从这个问题中我不清楚这是 OP 所问的问题。正如你评论的那样,我正在编辑我的答案。 我知道为什么我编译了一个使用sqrt 函数的程序,并且它在不包含-lm 的库的情况下工作。谢谢!【参考方案7】:

正如 ehemient 所说,默认链接 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

【讨论】:

【参考方案8】:

我认为这有点武断。您必须在某处画一条线(哪些库是默认库,哪些需要指定)。

它使您有机会将其替换为具有相同功能的其他产品,但我认为这样做并不常见。

编辑:(来自我自己的 cmets):我认为 gcc 这样做是为了保持与原始 cc 的向后兼容性。我猜为什么 cc 这样做是因为构建时间—— cc 是为比我们现在拥有的功率少得多的机器编写的。很多程序没有浮点数学,他们可能会将每个不常用的库都排除在默认值之外。我猜想 UNIX 操作系统的构建时间和随之而来的工具是驱动力。

【讨论】:

我认为问题背后的心态是 libm 的内容大部分是标准 C 库的一部分,为什么它们不在 libc 中? 为什么要 gcc 是为了保持与 AT&T Unix 中原始 cc 的兼容性。我在 1988 年使用 3B2,你必须 -lm 才能得到数学。当时我觉得这完全是武断的。在 Visual Studio 中,我不记得曾经必须添加数学,但有时您必须添加其他看似 c 运行时库。我认为编译器供应商是有原因的(构建时间?),但现在,我敢打赌 gcc 只是试图向后兼容。【参考方案9】:

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

stdlib.h, stdio.h 是头文件。为了方便起见,您将它们包括在内。如果您在正确的库中链接,他们只会预测哪些符号将可用。实现在库文件中,这是函数真正存在的地方。

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

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

stdlib.hstdio.h 指的是libc 中可用的函数,这些函数恰好总是被链接,这样用户就不必自己动手了。

【讨论】:

【参考方案10】:

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

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

【讨论】:

他知道..他在问为什么 他说了原因。 Simon 解释说,有些库是默认链接的,比如 stdio,而数学库默认没有链接,所以必须指定。 我想说问题的本质是问为什么 libm 默认不链接(甚至与 libc 分开),因为它的内容主要是 c 标准库的一部分。【参考方案11】:

我会猜测这是一种让完全不使用它的应用程序性能稍好一些的方法。这是我对此的看法。

x86 操作系统(我想其他操作系统)需要在上下文切换时存储 FPU 状态。但是,大多数操作系统只是在应用第一次尝试使用 FPU 后才费心保存/恢复此状态。

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

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

只是猜测。

编辑:针对某些 cmets,相同的基本前提仍然适用于非 FPU 情况(前提是它使未使用 libm 的应用程序稍微执行更好)。

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

此外,如果只有静态链接可用,那么类似的论点适用,它会降低可执行文件大小和编译时间。

【讨论】:

如果不链接 libm 而是通过其他方式(例如对浮点数的操作)接触 x87 FPU,x86 内核确实需要保存 FPU 状态。我不认为这是一个很好的猜测...... 当然,如果您手动使用 FPU,内核仍然需要保存/恢复其状态。我是说如果你从不使用它(包括不使用 libm),那么它就不必了。 确实它非常依赖内核。内核使用的数学库可以有一个 save_FPU_on_switch() 函数来打开它,而其他的只是检测 FPU 是否被触摸。 如果我没记错的话,整个问题早在浮点协处理器之前就已经出现了,甚至在微处理器上也是如此。 @earlz:保存数学库请求的方法将是一个糟糕的设计。如果他们通过其他方式使用 FPU 怎么办?唯一明智的方法(除了总是保存/恢复)是检测使用情况,然后开始保存/恢复。【参考方案12】:

这是一个错误。您不必再明确指定-lm。也许如果有足够多的人抱怨它,它就会得到解决。 (我不太相信这一点,因为维持这种区别的维护者显然非常固执,但我可以希望。)

【讨论】:

【参考方案13】:

stdio.hstdlib.h 这样的所有库都在libc.solibc.a 中实现,默认情况下由链接器链接。 libc.so 的库在编译时自动链接并包含在可执行文件中。 但是math.hlibm.solibm.a 中有它的实现,这与libc.so 是分开的,默认情况下它不会被链接,你必须在编译程序时手动链接它 在gcc 中使用-lm 标志。

gnu gcc 团队将其设计为与其他头文件分开,而其他头文件默认链接但 math.h 文件没有。

这里阅读第 14.3 条,如果您愿意,可以阅读全部内容: Reason why math.h is needs to be linked 看这篇文章:why we have to link math.h in gcc? 看看用法: using the library

【讨论】:

这已经在其他答案中说过了。这甚至不能回答这个问题。问题是为什么默认情况下不链接 libm。 这意味着math.h是libraray文件单独写在libm.so中,而其他头文件在libc.so中,而其他头文件自动链接但math.h是需要通过添加 -lm 标志手动链接 都是标准库的一部分。问题是为什么默认情况下不是全部链接。 “因为这就是 gcc 团队的设计方式”是一个糟糕的答案。问题是什么原因。这里有很好的答案,详细介绍了历史原因。您的编辑使您的答案更好,但我仍然看不到它比这里的所有其他答案增加了什么价值。【参考方案14】:

请注意,即使您使用某些 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的帖子给出了一些解释:

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

另见:

https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html https://***.com/a/57062793/6064933

【讨论】:

这应该是最佳答案

以上是关于为啥必须在 C 中链接数学库?的主要内容,如果未能解决你的问题,请参考以下文章

为啥即使在包含 math.h 并使用 -lm 链接到数学库之后我也会收到“未定义的符号:.sqrtf”

VS c++2005中头文件有的为啥必须加.h比如malloc

为啥生成 pdb 文件会导致静态链接库的大小大幅增加?

为啥必须在可选链接之前使用点运算符 - 在函数之前? [复制]

为啥必须在 C 中向后读取指针声明? [关闭]

为啥一种语言编写的dll文件,可以被不同编程语言使用?