gcc log(10.0)可以编译,但log(0.0)不能编译,引用未定义?

3b6akqbq  于 2022-11-24  发布在  其他
关注(0)|答案(2)|浏览(226)

对于以下 * 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(10.0)替换log(0.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)

请注意,这个问题是关于恒定折叠的,但suggested duplicate question是关于缺失的连接库的。

5us2dqdw

5us2dqdw1#

gcc可以在许多情况下使用builtin functions,他们的文档说明:
这些函数中的许多函数仅在某些情况下被优化;如果它们在特定情况下未被优化,则发出对库函数的调用。
因此,当使用内置函数时,gcc将不需要针对数学库进行链接,但是由于log(0)not defined,所以它可能迫使gcc在运行时对其进行求值,因为它具有副作用。
如果我们看一下draft C99 standard部分7.12.1 * 错误条件的处理 * 在第 * 4 * 段中,它说(* 强调我的 ):
如果数学结果的大小是有限的,但由于太大而无法在指定类型的对象中表示数学结果而没有异常舍入错误,则浮点结果溢出。如果浮点结果溢出并且默认舍入生效,或者如果数学结果是有限参数的无穷大
*(例如log(0.0)),则函数将根据返回类型**返回宏HUGE_VAL、HUGE_VALF或HUGE_VALL的值,并与函数的正确值具有相同的符号; * * 如果整数表达式math_errhandling & MATH_ERRNO为非零,则整数表达式errno获取值ERANGE; * * 如果整数表达式math_errhandling & MATH_ERREXCEPT为非零值,则在数学结果为正无穷大时会引发"被零除"浮点异常错误,否则会引发"溢出"浮点异常错误。
我们可以从一个真实的例子中看到,使用-S标志生成程序集,使用grep log过滤掉对log的调用。
log(0.0)的情况下,生成以下指令(* see it live *):

call    log

但在log(10.0)的情况下,不产生call log指令(* see it live)。
我们通常可以通过使用-fno-builtin标志来阻止gcc使用内置函数,这可能是一种测试是否正在使用内置函数的更快方法。
请注意,例如-lmneeds to go after the source file
取自链接答案 *),如果main.c需要数学库,则应使用:

gcc main.c -lm
zwghvu4y

zwghvu4y2#

编译正常,只是缺少链接器开关-lm
第二个版本可能会编译和链接,因为gcc用常量替换了log(10.0),所以不需要调用数学库。在第二种情况下,结果在数学上是未定义的,并且计算结果会导致域错误。在这种情况下,表达式不能用常量替换,因为在运行时对域错误的处理可能不同。
引用C标准(draft):
如果出现域错误,该函数将返回一个实现定义的值;如果整型表达式math_errhandling & MATH_ERRNO不为零,则整型表达式errno取值EDOM;如果整数表达式math_errhandling & MATH_ERREXCEPT为非零值,则会引发"无效"浮点异常错误。
因此,对log(0.0)的求值要么导致返回值HUGE_VAL(而不是我之前声明的NAN),要么导致浮点异常。
编辑:我根据收到的评论更正了我的答案,并添加了C标准中描述的链接。

相关问题