45

对于以下C源代码:

#include <math.h>

int main(void)
{
    double          x;

    x = log(0.0);

    return 0;
}

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

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

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

我不太明白这一点,因为无论它们是否具有数学意义,它们都应该编译——没有语法错误。谁能解释一下?

以防万一,我的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)

请注意,这个问题是关于常量折叠的,但建议的重复问题是关于缺少链接库的。

4

2 回答 2

59

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

其中许多功能仅在某些情况下进行了优化;如果它们在特定情况下未优化,则会发出对库函数的调用。

因此,因此gcc在使用内置函数时不需要链接到数学库,但由于未定义log(0)它可能会强制在运行时对其进行评估,因为它有副作用。gcc

如果我们查看C99 标准草案47.12.1 中的错误条件处理部分,它说(强调我的):

如果数学结果的大小是有限的,但如果在指定类型的对象中没有异常舍入误差就无法表示数学结果,则浮点结果会溢出。如果浮动结果溢出并且默认舍入生效,或者如果数学结果是有限参数的精确无穷大(例如 log(0.0)),则函数根据以下公式返回宏 HUGE_VAL、HUGE_VALF 或 HUGE_VALL 的值返回类型,与函数的正确值具有相同的符号; 如果整数表达式 math_errhandling & MATH_ERRNO 非零,则整数表达式 errno 获取值 ERANGE;如果整数表达式 math_errhandling & MATH_ERREXCEPT 不为零,则在数学结果为精确无穷大时引发“除零”浮点异常,否则引发“溢出”浮点异常。

我们可以从一个使用-Sflag 生成程序集并grep log过滤掉对log.

log(0.0)生成以下指令的情况下(现场查看):

call    log

但在log(10.0)没有call log生成指令的情况下,(现场查看)。

我们通常可以通过使用-fno-builtin 标志gcc来防止使用内置函数,这可能是测试是否正在使用内置函数的更快方法。

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

 gcc main.c -lm 
于 2014-06-18T20:38:42.093 回答
8

编译没问题,只是-lm缺少链接器开关。

第二个版本可能会编译和链接,因为gcc替换log(10.0)为常量,因此不需要调用数学库。在第二种情况下,结果在数学上是不确定的,并且评估会导致域错误。在这种情况下,表达式不能被常量替换,因为在运行时对域错误的处理可能会有所不同。

引用 C 标准(草案):

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

因此,对任一结果的评估log(0.0)都会导致返回值HUGE_VAL(不像NAN我之前声称的那样)或浮点异常。

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

于 2014-06-18T20:39:43.030 回答