2012-03-29 8 views
5

Por lo tanto, sé que en C necesita vincular el código a la biblioteca matemática, libm, para poder usar sus funciones. Hoy, mientras trataba de demostrarle esto a un amigo, y explicar por qué necesita hacer esto, me encontré con la siguiente situación que no entiendo.constante literal vs variable en la biblioteca matemática

Considere el siguiente código:

#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; 
} 

Si se define VARIABLE, es necesario enlazar con libm como normalmente se esperaría; de lo contrario, obtendrá el error de enlace main.c:(.text+0x29): undefined reference to sqrt habitual que indica que el compilador no puede encontrar la definición para la función sqrt. Me sorprendió ver que si comento #define VARIABLE, el código funciona bien y el resultado es correcto.

¿Por qué tengo que vincular a libm cuando se usan variables, pero no es necesario cuando se usan constantes literales? ¿Cómo encuentra el compilador la definición de sqrt cuando la biblioteca no está vinculada? Estoy usando gcc 4.4.5 en Linux.

Respuesta

4

Como todo el mundo menciona, sí que tiene que ver con constant folding.

Con las optimizaciones desactivadas, GCC solo parece hacerlo cuando se utiliza sqrt(2.0). Aquí está la evidencia:

Caso 1: Con la 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 

Se puede ver que emite una llamada a la función sqrt. Por lo tanto, obtendrá un error de enlazador si no vincula la biblioteca matemática.

Caso 2: Con el 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 hay llamada al sqrt. Por lo tanto, no hay error de enlazador.


con optimizaciones en, GCC hará constante de propagación en ambos casos. Entonces ningún error de enlazador en ningún caso.

$ 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 
$ 
5

GCC puede hacer constant folding para varias funciones de biblioteca estándar. Obviamente, si la función se pliega en tiempo de compilación, no hay necesidad de una llamada de función en tiempo de ejecución, por lo que no es necesario enlazar a libm. Puede confirmar esto tomando una mirada al ensamblador que produce el compilador (usando objdump o similar).

Supongo que estas optimizaciones solo se activan cuando el argumento es una expresión constante.

+0

"sólo se activa cuando el argumento es una expresión constante" - Creo que si se compila con '-O2', la versión con' VARIABLE' definido no llamará a la 'sqrt() la función de tiempo de ejecución '(y no será necesario vincularlo a 'libm'). Además, para que los lectores no tengan la idea de que esto solo lo hace GCC, este tipo de optimización generalmente la realiza cualquier compilador de C/C++. –

4

Creo que GCC usa su built-in. Recopilé tu código con: -fno-builtin-sqrt y obtuve el error del enlazador esperado.

Las funciones ISO C90 ... sin, sprintf, sqrt ... están todos reconocido como funciones incorporadas a menos que se especifique -fno-builtin

+0

¡eh! ¡eso realmente lleva a un error de enlace en el caso de las constantes! ¿Esto es solo para 'sqrt' o incluye otras funciones también? [EDITAR] Gracias por la actualización – GradGuy

+0

@GradGuy Ver [la página] (http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html). – cnicutar

2

Eso es porque gcc es lo suficientemente inteligente para darse cuenta de que la raíz cuadrada de la constante 2 es también una constante, por lo que sólo genera un código como:

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

Por lo tanto, no es necesario hacer un cálculo de raíz cuadrada en tiempo de ejecución, gcc ya lo ha hecho en tiempo de compilación.

Esto es similar a un programa de evaluación comparativa que no bucketloads de cálculos a continuación, no hace nada con el resultado:

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

Es probable (en altos niveles de optimización) para ver todo el código do something rather strenuous optimizado de la existencia .

Los gcc documentos tienen una página entera dedicada a estos muebles empotrados here y la sección correspondiente a esa página para sqrt y otros es:

Las funciones ISO 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 y vsprintf están reconocidas como incorporado en funciones a menos que se especifique -fno-builtin (o -fno-builtin-function para una función individual).

Por lo tanto, mucho, de verdad :-)

Cuestiones relacionadas