所以,我知道在C
中你需要将代码链接到数学库libm
,以便能够使用它的函数。今天,当我试图向朋友证明这一点,并解释为什么你需要这样做时,我遇到了以下我不理解的情况。
请考虑以下代码:
#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;
}
如果定义了VARIABLE
,则需要按照您通常预期的方式链接libm
;否则,您会收到通常的main.c:(.text+0x29): undefined reference to sqrt
链接错误,指示编译器无法找到函数sqrt
的定义。我很惊讶地看到,如果我评论#define VARIABLE
,代码运行正常,结果是正确的!
为什么在使用变量时我需要链接到libm
但是在使用文字常量时我不需要这样做?当库没有链接时,编译器如何找到sqrt
的定义?我在linux下使用gcc 4.4.5
。
答案 0 :(得分:5)
GCC可以为constant folding执行几个标准库函数。显然,如果函数在编译时折叠,则不需要运行时函数调用,因此不需要链接到libm。您可以通过查看编译器生成的汇编程序(使用objdump
或类似函数)来确认这一点。
我猜这些优化仅在参数是常量表达式时触发。
答案 1 :(得分:4)
我认为GCC使用其内置功能。我用-fno-builtin-sqrt
编译了你的代码并得到了预期的链接器错误。
ISO C90功能......
sin
,sprintf
,sqrt
...都是 除非指定-fno-builtin
,否则将被识别为内置函数
答案 2 :(得分:4)
正如大家所说,是的,它与constant folding有关。
关闭优化,GCC似乎只在使用sqrt(2.0)
时执行此操作。这是证据:
案例1:使用变量。
.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
您可以看到它会调用sqrt
函数。因此,如果不链接数学库,则会出现链接器错误。
案例2:使用文字。
.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
没有致电sqrt
。因此没有链接器错误。
通过优化,GCC将在两种情况下进行持续传播。因此在任何一种情况下都没有链接器错误。
$ 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
$
答案 3 :(得分:2)
那是因为gcc
足够聪明,可以发现常数2的平方根也是一个常量,所以它只生成如下代码:
mov register, whatever-the-square-root-of-2-is
因此无需在运行时进行平方根计算,gcc
已在编译时完成。
这类似于一个基准测试程序,它执行大量的计算,然后对结果不执行任何操作:
int main (void) {
// do something rather strenuous
return 0;
}
您可能(在高优化级别)看到所有优化的do something rather strenuous
代码都不存在。
gcc
个文档有一整页专门用于这些内置插件here,该页面中sqrt
和其他内容的相关部分是:
除非指定
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
(或为单个函数指定vsprintf
),否则ISO C90函数-fno-builtin
和-fno-builtin-function
都被识别为内置函数。 / p>
所以,非常多,真的: - )