日志(10.0)可以编译但日志(0.0)不能?

对于以下C源代码:

#include <math.h> int main(void) { double x; x = log(0.0); return 0; } 

当我用gcc -lm编译时,我得到:

 /tmp/ccxxANVH.o: In function `main': ac:(.text+0xd): undefined reference to `log' collect2: error: ld returned 1 exit status 

但是,如果我用log(10.0)replacelog(0.0) log(10.0) ,那么它可以成功编译。

我不太明白这一点,因为无论他们是否具有math意义,他们都应该编译 – 没有语法错误。 有谁能解释这个吗?

以防万一,我的gcc -v输出:

 Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.2-19ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu Thread model: posix gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1) 

请注意,这个问题是关于不断折叠,但build议重复的问题是关于一个缺less的链接库。

gcc可以在很多情况下使用内置函数 ,他们的文档说:

许多这些function只在某些情况下被优化; 如果它们在特定情况下未被优化,则发出对库函数的调用。

因此,当使用内build函数时, gcc不需要链接到math库,但是由于log(0) 没有被定义,所以gcc可能会在运行时对它进行评估,因为它有副作用。

如果我们看一下C99标准草案第7.12.1节的处理错误的条件,在第4段中说( 强调我的 ):

如果math结果的幅度是有限的,但是如此大以至于在指定types的对象中不能表示math结果而没有特别的舍入误差,则浮点结果溢出。 如果浮点结果溢出并且默认舍入生效,或者如果math结果是有限自variables的精确无穷大(例如log(0.0)),则函数根据下式返回macrosHUGE_VAL,HUGE_VALF或HUGE_VALL的值返回types ,与函数的正确值相同的符号; 如果整数expression式math_errhandling&MATH_ERRNO不为零,则整型expression式errno将获取值ERANGE; 如果整数expression式math_errhandling&MATH_ERREXCEPT为非零,那么如果math结果是一个精确的无穷大,那么就会引发“除零”的浮点exception,否则就会引发“溢出”浮点exception。

我们可以从一个活动的例子中看到,使用-S标志来生成汇编和grep log来过滤掉log

log(0.0)的情况下,会生成以下指令( 请参阅实时 ):

 call log 

但在log(10.0)的情况下,不会生成call log指令,( 请参阅现场 )。

我们通常可以通过使用-fno-builtin标志来防止gcc使用内build函数,这可能是testing内build函数是否被使用的一种更快的方法。

请注意, -lm 需要在源文件之后 ,例如( 取自链接的答案 )如果main.c需要math库,那么您将使用:

  gcc main.c -lm 

编译没问题,只是链接器开关-lm丢失了。

第二个版本可能编译和链接,因为gcc用一个常量replacelog(10.0) ,所以不需要调用math库。 在第二种情况下,结果在math上未定义,并且评估结果在域错误中。 在这种情况下,expression式不能被常量replace,因为在运行时处理域错误可能会有所不同。

C标准引言( 草案 ):

在域错误上,该函数返回一个实现定义的值; 如果整数expression式math_errhandling&MATH_ERRNO不为零,则整型expression式errno获取EDOM值; 如果整数expression式math_errhandling&MATH_ERREXCEPT为非零,则会引发“无效的”浮点exception。

因此,对log(0.0)求值会导致返回值HUGE_VAL (而不是我之前声称的NAN )或浮点exception。

编辑:我根据收到的意见更正了我的答案,并添加到C标准描述的链接。