Literal constant vs variable in the math library

So, I know that in C you need to link the code to the libm math library in order to be able to use its functions. Today, when I tried to demonstrate this to a friend and explained why you need it, I came across the following situation, which 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 defined, you need to bind to libm , as you usually expected; otherwise, you will get the usual binding error main.c:(.text+0x29): undefined reference to sqrt , indicating that the compiler cannot find a definition for the sqrt function. I was surprised to see that if I comment on #define VARIABLE , the code will work fine and the result will be correct!

Why do I need to reference libm when variables are used, but I do not need to do this when literal constants are used? How does the compiler find the sqrt definition when the library is not linked? I am using gcc 4.4.5 under linux.

+5
source share
4 answers

As everyone mentions, yes, this is due to constant folding .

When optimizing, GCC only does this when sqrt(2.0) . Here is the evidence:

Case 1: With a 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 issues a call to the sqrt function. This way you will get a linker error if you do not link the math library.

Case 2: with a 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 

No sqrt call. Therefore, there is no linker error.


When optimized for GCC, it will do continuous distribution in both cases. Thus, no linker error in both cases.

 $ 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 $ 
+4
source

GCC can perform continuous folding for several functions of the standard library. Obviously, if a function bends at compile time, there is no need to call a run-time function, so there is no need to bind to libm. You can confirm this by looking at the compiler that the compiler produces (using objdump or similar).

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

+5
source

I think GCC uses the built-in. I compiled your code using -fno-builtin-sqrt and got the expected linker error.

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

+4
source

This is because gcc is smart enough to realize that the square root of constant 2 is also a constant, so it just generates code, for example:

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

Therefore, there is no need to do square root calculations at runtime; gcc already performed it at compile time.

This is similar to a benchmarking program that does bucketloads of calculations and then does nothing with the result:

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

Probably (at high levels of optimization) to see all the code do something rather strenuous , optimized from existence.

The gcc docs have a whole page dedicated to these built-in functions here and a corresponding section on this page for sqrt and others:

ISO functions C90 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 , if recognized as not specified -fno-builtin (or for t210> for a single function)

So, quite a lot, really :-)

+2
source

Source: https://habr.com/ru/post/987548/


All Articles