gcc无法正确包含math.h头文件

31

以下是一个简单的例子,概述了我的问题:

test.c:

#include <stdio.h>
#include <math.h>

main ()
{
   fmod ( 3, 2 );
}

以下是我用来编译test.c的命令。

gcc -lm test.c -o test

当我执行上述命令时,这是我得到的输出结果

/tmp/ccQmRk99.o: In function `main':
test.c:(.text+0x3e): undefined reference to `fmod'
collect2: ld returned 1 exit status

如果我使用cc代替,将得到相同的输出。 我正在使用以下版本的gcc。

gcc-4.6.real (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1

有什么想法,为什么我的程序无法编译?


这段代码在我的GCC 4.1.2和4.3.4编译链接都很好。 - Oliver Charlesworth
2
对我也起作用,使用gcc 4.4.3。尝试将-lm移到命令的末尾并检查您机器上是否存在/usr/lib/libm.so。 - Scott Wales
@ScottWales,彼得·彼戈就是他的名字!只需将其移至末尾即可解决问题。把它作为答案,我会接受它(请确保详细说明并包括历史背景)。 - puk
2
哇,这是个bug吗?在这种情况下参数的顺序不应该有影响... - ckruse
1
@ckruse:请参见此答案的评论:https://dev59.com/fGsz5IYBdhLWcg3wiYc0#7824642。 - Oliver Charlesworth
显示剩余6条评论
2个回答

68
问题出在链接器ld上,而不是gcc(因此有退出状态消息)。通常情况下,ld要求按照顺序指定对象和库:user supplier,其中user是使用库函数的对象,supplier是提供它的对象。
当您的test.c编译为一个对象时,编译器会声明fmod是未定义的引用。
$ gcc -c test.c
$ nm test.o
                 U fmod
0000000000000000 T main

(nm列出目标文件引用的所有函数)

连接器将未定义的引用更改为已定义的引用,并查找引用以确定它们是否在其他文件中提供。

$ gcc -lm test.o
$ nm a.out
0000000000600e30 d _DYNAMIC
0000000000600fe8 d _GLOBAL_OFFSET_TABLE_
00000000004006a8 R _IO_stdin_used
                 w _Jv_RegisterClasses
0000000000600e10 d __CTOR_END__
...
0000000000601018 D __dso_handle
                 w __gmon_start__
...
                 U __libc_start_main@@GLIBC_2.2.5
0000000000601020 A _edata
0000000000601030 A _end
0000000000400698 T _fini
0000000000400448 T _init
0000000000400490 T _start
00000000004004bc t call_gmon_start
0000000000601020 b completed.7382
0000000000601010 W data_start
0000000000601028 b dtor_idx.7384
                 U fmod@@GLIBC_2.2.5
0000000000400550 t frame_dummy
0000000000400574 T main

大多数涉及libc函数,这些函数在main函数之前和之后运行以设置环境。您可以看到fmod现在指向glibc,将由共享库系统解决。
我的系统默认使用共享库。如果我强制使用静态链接,我会遇到您看到的依赖关系。
$ gcc -static -lm test.o
test.o: In function `main':
test.c:(.text+0x40): undefined reference to `fmod'
collect2: ld returned 1 exit status

-lm放在链接器命令中test.o后面,可以成功链接。检查符号fmod现在应该已经被解析为实际地址,而确实如此。
$ gcc -static test.o -lm
$ nm a.out | grep fmod
0000000000400480 T __fmod
0000000000402b80 T __ieee754_fmod
0000000000400480 W fmod

1
关于链接过程的解释非常好,但是底线含义不够明确。您是在说将 -lm test.o 改为 test.o -lm 可以解决问题(因此,他应该将 gcc -lm test.c -o test 改为 gcc test.c -lm -o test)吗? - ugoren
10
是的,除非你有特定的原因需要不同,否则库文件应该总是放在链接命令行的最后。并且它们应该按依赖关系的顺序来放置,也就是如果 A 依赖于 B,那么在命令行中 B 必须始终放在 A 的后面。 - R.. GitHub STOP HELPING ICE
1
好的回答,我从来不知道“nm”。 - Jamie Burke

4

来自gcc(1)手册的一句话:" -l选项的放置很重要。"

具体而言:

   -llibrary
   -l library
       Search the library named library when linking.  (The second alternative with the library as a
       separate argument is only for POSIX compliance and is not recommended.)

       It makes a difference where in the command you write this option; the linker searches and processes
       libraries and object files in the order they are specified.  Thus, foo.o -lz bar.o searches library z
       after file foo.o but before bar.o.  If bar.o refers to functions in z, those functions may not be
       loaded.

       The linker searches a standard list of directories for the library, which is actually a file named
       liblibrary.a.  The linker then uses this file as if it had been specified precisely by name.

       The directories searched include several standard system directories plus any that you specify with
       -L.

       Normally the files found this way are library files---archive files whose members are object files.
       The linker handles an archive file by scanning through it for members which define symbols that have
       so far been referenced but not defined.  But if the file that is found is an ordinary object file, it
       is linked in the usual fashion.  The only difference between using an -l option and specifying a file
       name is that -l surrounds library with lib and .a and searches several directories.

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