5

So, I know that in C you need to link the code to the math library, libm, to be able to use its functions. Today, while I was trying to demonstrate this to a friend, and explain why you need to do this, I came across the following situation that I do not understand.

Consider the following code:

#include <math.h>
#include <stdio.h>

/* #define VARIABLE */

int main(void)
{
#ifdef VARIABLE
    double a = 2.0;
    double b = sqrt(a);
    printf("b = %lf\n",b);
#else
    double b = sqrt(2.0);
    printf("b = %lf\n",b);
#endif
    return 0;
}

If VARIABLE is defined, you need to link against libm as you would normally expect; otherwise you get the usual main.c:(.text+0x29): undefined reference to sqrt linking error indicating that the compiler cannot find the definition for the function sqrt. I was surprised to see that if I comment #define VARIABLE, the code runs fine and the result is correct!

Why is it that I need to link to libm when variables are used but I don't need to do so when literal constants are used? How does the compiler find the definition of sqrt when the library is not linked? I'm using gcc 4.4.5 under linux.

mmirzadeh
  • 6,893
  • 8
  • 36
  • 47

4 Answers4

5

GCC can do constant folding for several standard-library functions. Obviously, if the function is folded at compile-time, there is no need for a run-time function call, so no need to link to libm. You could confirm this by taking a looking at the assembler that the compiler produces (using objdump or similar).

I guess these optimizations are only triggered when the argument is a constant expression.

Oliver Charlesworth
  • 267,707
  • 33
  • 569
  • 680
  • "only triggered when the argument is a constant expression" - I believe that if you compile with `-O2`, the version with `VARIABLE` defined will no longer call the runtime `sqrt()` function (and won't need to be linked to `libm`). Also, just so readers don't get the idea that this is something only done by GCC, this kind of optimization is generally done by any C/C++ compiler. – Michael Burr Mar 29 '12 at 07:42
4

I think it GCC uses its builtin. I compiled your code with: -fno-builtin-sqrt and got the expected linker error.

The ISO C90 functions ... sin, sprintf, sqrt ... are all recognized as built-in functions unless -fno-builtin is specified

cnicutar
  • 178,505
  • 25
  • 365
  • 392
  • huh! that actually does lead to link error in the case of constants! Is this only for `sqrt` or includes other functions as well? [EDIT] Thanks for update – mmirzadeh Mar 29 '12 at 07:29
4

As everyone mentions, yes it has to do with constant folding.

With optimizations off, GCC only seems to do it when sqrt(2.0) is used. Here's the evidence:

Case 1: With the variable.

    .file   "main.c"
    .section    .rodata
.LC1:
    .string "b = %lf\n"
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    fldl    .LC0
    fstpl   24(%esp)
    fldl    24(%esp)
    fsqrt
    fucom   %st(0)
    fnstsw  %ax
    sahf
    jp  .L5
    je  .L2
    fstp    %st(0)
    jmp .L4
.L5:
    fstp    %st(0)
.L4:
    fldl    24(%esp)
    fstpl   (%esp)
    call    sqrt
.L2:
    fstpl   16(%esp)
    movl    $.LC1, %eax
    fldl    16(%esp)
    fstpl   4(%esp)
    movl    %eax, (%esp)
    call    printf
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .section    .rodata
    .align 8
.LC0:
    .long   0
    .long   1073741824
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

You can see that it emits a call to the sqrt function. So you'll get a linker error if you don't link the math library.

Case 2: With the Literal.

    .file   "main.c"
    .section    .rodata
.LC1:
    .string "b = %lf\n"
    .text
.globl main
    .type   main, @function
main:
    pushl   %ebp
    movl    %esp, %ebp
    andl    $-16, %esp
    subl    $32, %esp
    fldl    .LC0
    fstpl   24(%esp)
    movl    $.LC1, %eax
    fldl    24(%esp)
    fstpl   4(%esp)
    movl    %eax, (%esp)
    call    printf
    movl    $0, %eax
    leave
    ret
    .size   main, .-main
    .section    .rodata
    .align 8
.LC0:
    .long   1719614413
    .long   1073127582
    .ident  "GCC: (Ubuntu 4.4.3-4ubuntu5) 4.4.3"
    .section    .note.GNU-stack,"",@progbits

There's no call to sqrt. Hence no linker error.


With optimizations on, GCC will do constant propagation in both cases. So no linker error in either case.

$ gcc main.c -save-temps
main.o: In function `main':
main.c:(.text+0x30): undefined reference to `sqrt'
collect2: ld returned 1 exit status
$ gcc main.c -save-temps -O2
$ 
Mysticial
  • 464,885
  • 45
  • 335
  • 332
2

That's because gcc is clever enough to figure out that the square root of the constant 2 is also a constant, so it just generates code like:

mov register, whatever-the-square-root-of-2-is

Hence no need to do a square root calculation at run time, gcc has already done it at compile time.

This is akin to a benchmarking program which does bucketloads of calculations then does nothing with the result:

int main (void) {
    // do something rather strenuous
    return 0;
}

You're likely (at high optimisation levels) to see all the do something rather strenuous code optimised out of existence.

The gcc docs have a whole page dedicated to these built-ins here and the relevant section in that page for sqrt and others is:

The ISO C90 functions abort, abs, acos, asin, atan2, atan, calloc, ceil, cosh, cos, exit, exp, fabs, floor, fmod, fprintf, fputs, frexp, fscanf, isalnum, isalpha, iscntrl, isdigit, isgraph, islower, isprint, ispunct, isspace, isupper, isxdigit, tolower, toupper, labs, ldexp, log10, log, malloc, memchr, memcmp, memcpy, memset, modf, pow, printf, putchar, puts, scanf, sinh, sin, snprintf, sprintf, sqrt, sscanf, strcat, strchr, strcmp, strcpy, strcspn, strlen, strncat, strncmp, strncpy, strpbrk, strrchr, strspn, strstr, tanh, tan, vfprintf, vprintf and vsprintf are all recognized as built-in functions unless -fno-builtin is specified (or -fno-builtin-function is specified for an individual function).

So, quite a lot, really :-)

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953