2012-03-29 31 views
5

Vì vậy, tôi biết rằng trong C bạn cần liên kết mã với thư viện toán học, libm, để có thể sử dụng các chức năng của nó. Hôm nay, trong khi tôi đang cố gắng để chứng minh điều này với một người bạn, và giải thích lý do tại sao bạn cần phải làm điều này, tôi đã đi qua tình huống sau đây mà tôi không hiểu.hằng số chữ so với biến trong thư viện toán

Xét đoạn mã sau:

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

Nếu VARIABLE được định nghĩa, bạn cần liên kết chống lại libm như bạn thường mong đợi; nếu không bạn sẽ nhận được lỗi liên kết thông thường main.c:(.text+0x29): undefined reference to sqrt cho biết trình biên dịch không thể tìm thấy định nghĩa cho hàm sqrt. Tôi đã ngạc nhiên khi thấy rằng nếu tôi nhận xét #define VARIABLE, mã chạy tốt và kết quả là chính xác!

Tại sao tôi cần phải liên kết đến libm khi biến được sử dụng nhưng tôi không cần phải làm như vậy khi hằng số chữ được sử dụng? Trình biên dịch tìm định nghĩa của sqrt khi thư viện không được liên kết như thế nào? Tôi đang sử dụng gcc 4.4.5 trong linux.

Trả lời

4

Như mọi người đều nhắc đến, vâng nó đã làm với constant folding.

Với tối ưu hóa tắt, GCC dường như chỉ làm khi sử dụng sqrt(2.0). Dưới đây là bằng chứng:

Trường hợp 1: Với biến.

.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 

Bạn có thể thấy rằng nó phát ra cuộc gọi đến chức năng sqrt. Vì vậy, bạn sẽ nhận được một lỗi linker nếu bạn không liên kết các thư viện toán học.

Trường hợp 2: Bằng chữ.

.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 

Không có cuộc gọi đến sqrt. Do đó không có lỗi liên kết.


Với việc tối ưu trên, GCC sẽ làm công tác tuyên truyền liên tục trong cả hai trường hợp. Vì vậy, không có lỗi liên kết trong cả hai trường hợp.

$ 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 có thể làm constant folding đối với một số chức năng thư viện chuẩn. Rõ ràng, nếu chức năng được gấp lại tại thời gian biên dịch, không cần có một cuộc gọi hàm thời gian chạy, do đó không cần phải liên kết đến libm. Bạn có thể xác nhận điều này bằng cách xem xét trình biên dịch mà trình biên dịch tạo ra (sử dụng objdump hoặc tương tự).

Tôi đoán các tối ưu hóa này chỉ được kích hoạt khi đối số là biểu thức không đổi.

+0

"chỉ được kích hoạt khi đối số là biểu thức liên tục" - Tôi tin rằng nếu bạn biên dịch với '-O2', phiên bản có' VARIABLE' được xác định sẽ không còn gọi hàm runtime 'sqrt()' nữa. và sẽ không cần phải được liên kết với 'libm'). Ngoài ra, chỉ vì vậy độc giả không nhận được ý tưởng rằng đây là một cái gì đó chỉ được thực hiện bởi GCC, loại tối ưu hóa thường được thực hiện bởi bất kỳ trình biên dịch C/C++. –

4

Tôi nghĩ rằng GCC sử dụng nội trang dựng sẵn của nó. Tôi đã biên soạn mã của bạn với: -fno-builtin-sqrt và gặp lỗi trình liên kết dự kiến.

Các chức năng ISO C90 ... sin, sprintf, sqrt ... tất cả đều được công nhận là chức năng built-in trừ khi được quy định -fno-builtin

+0

huh! thực sự dẫn đến lỗi liên kết trong trường hợp hằng số! Đây có phải là chỉ cho 'sqrt' hoặc bao gồm các chức năng khác không? [EDIT] Cảm ơn bạn đã cập nhật – GradGuy

+0

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

2

Đó là bởi vì gcc là đủ thông minh để hình dung ra rằng căn bậc hai của hằng số 2 là cũng là một hằng số, do đó, nó chỉ tạo ra mã như:

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

Do đó không cần thực hiện phép tính căn bậc hai trong thời gian chạy, gcc đã thực hiện nó tại thời gian biên dịch.

Đây là giống như một chương trình chuẩn mà không bucketloads tính toán sau đó không làm gì với kết quả:

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

Bạn đang có khả năng (ở mức độ tối ưu hóa cao) để xem tất cả các mã do something rather strenuous tối ưu hóa ra sự tồn tại .

Các gcc tài liệu có một trang hoàn toàn dành riêng cho các built-in here và phần có liên quan trong trang đó cho sqrt và những người khác là:

Các chức năng 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, vprintfvsprintf đều công nhận là built- trong các hàm trừ khi -fno-builtin được chỉ định (hoặc -fno-builtin-function được chỉ định cho một hàm riêng lẻ).

Vì vậy, khá nhiều, thực sự :-)

Các vấn đề liên quan