2012-03-29 18 views
5

Quindi, so che in C è necessario collegare il codice alla libreria matematica, libm, per poter utilizzare le sue funzioni. Oggi, mentre stavo cercando di dimostrarlo ad un amico e spiegare perché è necessario farlo, mi sono imbattuto nella seguente situazione che non capisco.costante letterale vs variabile nella libreria matematica

Si consideri il seguente codice:

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

Se VARIABLE è definito, è necessario collegare contro libm come ci si aspetterebbe; in caso contrario si ottiene il solito errore di collegamento main.c:(.text+0x29): undefined reference to sqrt che indica che il compilatore non riesce a trovare la definizione per la funzione sqrt. Sono stato sorpreso di vedere che se commento #define VARIABLE, il codice funziona bene e il risultato è corretto!

Perché è necessario collegarsi a libm quando si utilizzano variabili, ma non è necessario farlo quando vengono utilizzate costanti letterali? In che modo il compilatore trova la definizione di sqrt quando la libreria non è collegata? Sto usando gcc 4.4.5 sotto Linux.

risposta

4

Come tutti citano, sì, ha a che fare con constant folding.

Con le ottimizzazioni su, GCC sembra farlo solo quando viene utilizzato sqrt(2.0). Ecco le prove:

Caso 1: Con la variabile.

.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 

Si può vedere che emette una chiamata alla funzione sqrt. Quindi otterrai un errore linker se non colleghi la libreria matematica.

Caso 2: Con il letterale.

.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 

Nessuna chiamata allo sqrt. Quindi nessun errore del linker.


Con ottimizzazioni su, GCC farà costante di propagazione nei due casi. Quindi nessun errore di linker in entrambi i casi.

$ 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 può fare constant folding per diverse funzioni di libreria standard. Ovviamente, se la funzione viene piegata in fase di compilazione, non è necessario chiamare una funzione di runtime, quindi non è necessario collegarsi a libm. Puoi confermarlo osservando l'assemblatore prodotto dal compilatore (usando objdump o simile).

Immagino che queste ottimizzazioni vengano attivate solo quando l'argomento è un'espressione costante.

+0

"attivato solo quando l'argomento è un'espressione costante" - Credo che se si compila con '-O2', la versione con' VARIABLE' definita non sarà più chiamare il 'sqrt()' funzione di runtime (e non sarà necessario collegarsi a 'libm'). Inoltre, solo così i lettori non hanno l'idea che questo è qualcosa fatto solo da GCC, questo tipo di ottimizzazione viene generalmente eseguita da qualsiasi compilatore C/C++. –

4

Penso che GCC usi il suo integrato. Ho compilato il tuo codice con: -fno-builtin-sqrt e ho ottenuto l'errore del linker previsto.

Le funzioni ISO C90 ... sin, sprintf, sqrt ... sono tutti riconosciuto come funzioni built-in a meno che non sia specificato -fno-builtin

+0

eh! che in realtà porta a errori di collegamento nel caso di costanti! È solo per 'sqrt' o include anche altre funzioni? [EDIT] Grazie per l'aggiornamento – GradGuy

+0

@GradGuy Vedi [la pagina] (http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html). – cnicutar

2

Questo perché gcc è abbastanza intelligente per capire che la radice quadrata della costante 2 è anche una costante, in modo che solo genera il codice come:

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

Quindi non è necessario eseguire un calcolo della radice quadrata in fase di esecuzione, lo ha già fatto gcc in fase di compilazione.

Questo è simile a un programma di benchmarking che fa bucketloads di calcoli poi non fa nulla con il risultato:

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

È molto probabile (ad alti livelli di ottimizzazione) per vedere tutto il codice ottimizzato do something rather strenuous di esistere .

I gcc documenti hanno una pagina intera dedicata a questi built-in here e la relativa sezione in quella pagina per sqrt e gli altri è:

Le funzioni 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 e vsprintf sono tutti riconosciuti come incorporato in funzioni a meno che non sia specificato -fno-builtin (o -fno-builtin-function specificato per una singola funzione).

Così, un bel po ', in realtà :-)

Problemi correlati