45

For the following C source code:

#include <math.h>

int main(void)
{
    double          x;

    x = log(0.0);

    return 0;
}

When I compile with gcc -lm, I got:

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

But, if I replace log(0.0) with log(10.0), then it can compile successfully.

I don't quite understand this, since no matter they make mathematical sense or not, they should compile -- there is no syntax error. Could anyone explain this?

Just in case, my gcc -v output:

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)

Note that this question is about constant folding but the suggested duplicate question is about a missing linking library.

Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
xuhdev
  • 8,018
  • 2
  • 41
  • 69
  • 14
    This probably has to do with constant propagation and not specifying `-lm`. – Mysticial Jun 18 '14 at 20:38
  • @Cornstalks Indeed, one should specify `-lm` after the source file. – xuhdev Jun 19 '14 at 04:00
  • I disagree with your premise that an undefined mathematical expression should compile. Since log(0) is undefined / singular, it's presence in a program can only be the result of a programming mistake. I would not have expected the compiler to catch this but consider the fact that it does a **feature**, not a **defect**. – Gdalya Jun 25 '14 at 18:56
  • @gdalya it should return -Inf, not undefined. See [here](http://en.m.wikipedia.org/wiki/Floating_point#Infinities) – xuhdev Jun 25 '14 at 19:35
  • @Gdalya how to deal with such situations is defined in the C language standard which I quote in my answer below. For example see my [answer here](http://stackoverflow.com/a/19955996/1708801) on why having `pow(0,0)` return `1` is useful even though there is no consensus on what it really means. – Shafik Yaghmour Jun 25 '14 at 20:25
  • Possible duplicate of [sqrt() function not working with variable arguments](http://stackoverflow.com/questions/3533594/sqrt-function-not-working-with-variable-arguments) – phuclv Aug 20 '16 at 17:21
  • 1
    @LưuVĩnhPhúc It's two different questions. See my edit in the last line. – xuhdev Aug 20 '16 at 17:30
  • @LưuVĩnhPhúc they are different questions the former is about linking this is about optimization. – Shafik Yaghmour Aug 20 '16 at 21:42
  • Both can be explained by built-in functions, because it's compiling even without linking a library. But yes it might not be an exact dup – phuclv Aug 21 '16 at 05:53

2 Answers2

59

gcc can use builtin functions in many cases, their documentation says:

Many of these functions are only optimized in certain cases; if they are not optimized in a particular case, a call to the library function is emitted.

so therefore gcc will not need to link against the math library when using the builtin function but since log(0) is not defined it probably forcesgcc to evaluate it at run-time since it has a side effect.

If we look at the draft C99 standard section 7.12.1 Treatment of error conditions in paragraph 4 it says (emphasis mine):

A floating result overflows if the magnitude of the mathematical result is finite but so large that the mathematical result cannot be represented without extraordinary roundoff error in an object of the specified type. If a floating result overflows and default rounding is in effect, or if the mathematical result is an exact infinity from finite arguments (for example log(0.0)), then the function returns the value of the macro HUGE_VAL, HUGE_VALF, or HUGE_VALL according to the return type, with the same sign as the correct value of the function; if the integer expression math_errhandling & MATH_ERRNO is nonzero, the integer expression errno acquires the value ERANGE; if the integer expression math_errhandling & MATH_ERREXCEPT is nonzero, the ‘‘divide-by-zero’’ floating-point exception is raised if the mathematical result is an exact infinity and the ‘‘overflow’’ floating-point exception is raised otherwise.

We can see from a live example using -S flag to generate assembly and grep log to filter out calls to log.

In the case of log(0.0) the following instruction is generated (see it live):

call    log

but in the case of log(10.0) no call log instruction is generated, (see it live).

We can usually prevent gcc from using builtin function by using the -fno-builtin flag which is probably a quicker way to test whether a builtin is being used.

Note that -lm needs to go after the source file, for example (taken from linked answer) if main.c required the math library then you would use:

 gcc main.c -lm 
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Shafik Yaghmour
  • 154,301
  • 39
  • 440
  • 740
  • 8
    The reason that gcc can't evaluate `log(0)` at runtime is tricky. The standard specifies it returns `-HUGE_VAL`, but it also causes a range error as a side effect (visible in `errno` for example) so it can't eliminate the call. – Tavian Barnes Jun 18 '14 at 20:45
  • @TavianBarnes indeed I was going to add that as well. – Shafik Yaghmour Jun 18 '14 at 20:46
  • 3
    @TavianBarnes: Well, a compiler *could* replace `log(0.0)` with code that yields `HUGE_VAL` and sets `errno`, but it's probably simpler just to generate the call (since it has to be able to do so for non-constant arguments anyway). – Keith Thompson Jun 18 '14 at 20:56
  • Exactly. Plus that's not the only visible side effect, it also sets an FP flag. It'd probably bloat code tremendously to inline all that. – Tavian Barnes Jun 18 '14 at 21:01
  • I tried the code to be compiled on another machine (RHEL 6) with `gcc -lm`, there is a `call log` line generated for both cases. It seems that the order of the libraries specified is not definitely the search order? – xuhdev Jun 18 '14 at 21:04
  • @xuhdev not sure, there can be many effects, for example `-fno-builtin` will force it to generate the instruction. – Shafik Yaghmour Jun 18 '14 at 21:07
  • 2
    @KeithThompson I figure they want to keep the builtin evaluation code simple and avoid handling exceptions and such not which makes the runtime code more complicated. – Shafik Yaghmour Jun 18 '14 at 21:15
8

The compilation is alright, it's just the linker switch -lm that is missing.

The second version probably compiles and links because gcc replaces log(10.0) with a constant, so no call to the math library is needed. In the second case, the result is mathematically undefined, and evaluation results in a domain error. In that case, the expression cannot be replaced by a constant, since handling of domain errors might be different at run-time.

Quote from the C-standard (draft):

On a domain error, the function returns an implementation-defined value; if the integer expression math_errhandling & MATH_ERRNO is nonzero, the integer expression errno acquires the value EDOM; if the integer expression math_errhandling & MATH_ERREXCEPT is nonzero, the ‘‘invalid’’ floating-point exception is raised.

So evaluation of log(0.0) either results in returning the value HUGE_VAL (not NAN as I claimed before) or a floating point exception.

EDIT: I corrected my answer based on the comments received and added link to the description in the C-standard.

Axel
  • 13,939
  • 5
  • 50
  • 79
  • The result is -Inf, not NaN. – Tavian Barnes Jun 18 '14 at 20:47
  • 1
    @TavianBarnes: I think that's implementation defined. The standard just says this: "The log functions compute the base-e (natural) logarithm of x. A domain error occurs if the argument is negative. A range error may occur if the argument is zero." – Axel Jun 19 '14 at 07:02
  • 2
    @Axel "... if the mathematical result is an exact infinity (for example log(0.0)), then the function returns the value of the macro HUGE_VAL, HUGE_VALF, or HUGE_VALL according to the return type, with the same sign as the correct value of the function" – Tavian Barnes Jun 19 '14 at 12:50
  • Though it's still possible HUGE_VAL!=INFINITY... ;-) No, really the important thing is not so much which value would be returned, but that the behaviour is suspect to whether or not floating point exceptions are enabled at runtime which forbids replacing the call to `log(0.0)` at compile time. – Axel Jun 19 '14 at 14:08
  • `HUGE_VAL` may not be infinity, but it's definitely not NaN, as the standard requires it to "expand to a positive double constant expression". – T.C. Jun 21 '14 at 19:16