使用 sqrt 函数时的链接器问题(C、gcc)

Linker problem when using sqrt function (C, gcc)

当我尝试编译下面的最小示例时遇到一个奇怪的问题。第一次调用 sqrt 没有问题,但第二次调用会引发一个我不理解的链接器错误,因为我在调用 gcc 时确实指定了数学库。当我评论第二行时,它 compiles/links 正确。

代码如下:

// File wtf_sqrt.c
#include <math.h>

int main (int argc, char *argv[]) {
  int x = 3;  // Just an int...
  
  sqrt(3);  // This line works fine
  sqrt(x);  // But this one seems to give the linker trouble. Why?
  
  return 0;
}

这是我的编译命令:

gcc -lm -o wtf_sqrt wtf_sqrt.c

这是返回的错误:

/tmp/ccgQN7y7.o: In function `main':
wtf_sqrt.c:(.text+0x1c): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status

我在 Ubuntu 18.04 LTS 上使用 gcc 版本 7.5.0。 libc6-dev 已安装(证据是,当我在 main 的第一行中使用它时,sqrt 有效)。代码是用gedit写的,所以应该不是空字符的问题。不确定我还能给你什么其他信息......

在这一点上,我真的怀疑这是我的 config/distribution 的问题,但我需要一些外部建议。

问题可能出在 sqrt() 所采用的参数中。这是 math.h header:

中的函数
_Check_return_ _CRT_JIT_INTRINSIC double __cdecl sqrt(_In_ double _X);

如您所见,函数采用的参数类型是double。在您的第一个 sqrt() 调用中,您只需在括号内键入值 3 即可传递值 - 编译器可能会将此 3 视为双精度值并成功编译。但是在第二次调用中,您传递了一个参数 x,它是一个 int(并且您没有将其转换为 double)。由于 C 不支持函数重载,编译器找不到带有 int 参数的 sqrt 函数并弹出错误。将 x 转换为 double 应该可以解决问题:

sqrt((double)x);

正如用户 aragaer、Weather Vane 和 Tom Karzes 在评论中所建议的那样,问题出在编译命令中 -lm 的位置。我错误地认为首先提供它是正确的方法,因为那时链接器可能知道在哪里可以找到该函数,但我应该最后提供它以便链接器已经知道我需要 sqrt 从我的程序.事实上,重要的不仅是 -l 选项的顺序,还有(要)编译的文件的顺序。所以正确的命令是:

gcc -o wtf_sqrt wtf_sqrt.c -lm

正如用户 Weather Vane 所解释的,对 sqrt 的第一次调用可能由于优化而被替换或删除,因此不会产生错误。

这个问题实际上有两个部分,每个部分都是前面问题的重复。

当使用常量参数调用时 sqrt 不会导致 link 错误的事实是编译器在编译期间对其进行评估,如回答 here, here, and .

即使 -lm 包含在 link 中也会出现 link 错误的事实是 -lm 必须列在使用数学库的模块之后, 正如回答 here, here, here, and here.