数学库中的文字常量与变量

所以,我知道在C你需要将代码链接到数学库libm ,以便能够使用它的函数。 今天,当我试图向朋友certificate这一点,并解释你为什么需要这样做时,我遇到了以下我不理解的情况。

请考虑以下代码:

 #include  #include  /* #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链接错误的main.c:(.text+0x29): undefined reference to sqrt表明编译器找不到函数sqrt的定义。 我很惊讶地看到,如果我评论#define VARIABLE ,代码运行正常,结果是正确的!

为什么在使用变量时我需要链接到libm但是在使用文字常量时我不需要这样做? 当库没有链接时,编译器如何找到sqrt的定义? 我在linux下使用gcc 4.4.5

正如大家所说,是的,它与不断折叠有关 。

通过优化 ,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 $ 

GCC可以为多个标准库函数进行常量折叠 。 显然,如果函数在编译时折叠,则不需要运行时函数调用,因此不需要链接到libm。 您可以通过查看编译器生成的汇编程序(使用objdump或类似objdump )来确认这一点。

我猜这些优化仅在参数是常量表达式时触发。

我认为GCC使用它的内置。 我用以下代码编译了你的代码: -fno-builtin-sqrt并得到了预期的链接器错误。

除非指定了-fno-builtin否则ISO C90函数… sinsprintfsqrt …都被识别为内置函数

这是因为gcc足够聪明,可以发现常量2的平方根也是一个常数,因此它只生成如下代码:

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

因此无需在运行时进行平方根计算, gcc已经在编译时完成了它。

这类似于一个基准测试程序,它执行大量的计算,然后对结果不执行任何操作:

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

你很可能(在高优化级别下)看到所有的do something rather strenuousdo something rather strenuous优化代码。

gcc文档在这里有一整页专门用于这些内置函数,而该页面中sqrt和其他内容的相关部分是:

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都被认为是内置的-in函数,除非指定了-fno-builtin (或者为单个函数指定了-fno-builtin-function function)。

所以,非常多,真的:-)