log(10.0)可以编译但是log(0.0)不能吗?

xuh*_*dev 46 c gcc math.h constantfolding

对于以下C源代码:

#include <math.h>

int main(void)
{
    double          x;

    x = log(0.0);

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

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

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

但是,如果我替换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)
Run Code Online (Sandbox Code Playgroud)

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

Sha*_*our 59

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

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

所以因此gcc将不需要使用内置功能时对数学库链接,但因为log(0)没有定义的,它可能迫使gcc在运行时,因为它有一个副作用,以评估它.

如果我们看一下C99标准部分草案4段中7.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
Run Code Online (Sandbox Code Playgroud)

但是在log(10.0)没有call log生成指令的情况下(现场直播).

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

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

 gcc main.c -lm 
Run Code Online (Sandbox Code Playgroud)

  • gcc在运行时无法评估`log(0)`的原因很棘手.标准指定它返回`-HUGE_VAL`,但它也会导致范围错误作为副作用(例如在`errno`中可见),因此它无法消除调用. (7认同)
  • @TavianBarnes:好吧,编译器*可以用代码生成`HUGE_VAL`替换`log(0.0)`并设置`errno`,但是生成调用可能更简单(因为它必须能够这样做)反正的非常数论据). (2认同)
  • @KeithThompson我认为他们希望保持内置评估代码简单并避免处理异常,这样就不会使运行时代码变得更复杂. (2认同)

Axe*_*xel 8

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

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

引用C标准(草案):

在域错误上,该函数返回一个实现定义的值; 如果整数表达式math_errhandling&MATH_ERRNO非零,则整数表达式errno获取值EDOM; 如果整数表达式math_errhandling&MATH_ERREXCEPT非零,则引发''invalid''浮点异常.

因此,对log(0.0)任一项的评估都会导致返回值HUGE_VAL(不是NAN我之前声明的)或浮点异常.

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

  • @Axel"......如果数学结果是精确无穷大(例如log(0.0)),则函数根据返回类型返回宏HUGE_VAL,HUGE_VALF或HUGE_VALL的值,其符号与正确的功能值" (2认同)