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

时间:2012-03-29 07:21:32

标签: c linker

所以,我知道在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

4 个答案:

答案 0 :(得分:5)

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

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

答案 1 :(得分:4)

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

  

ISO C90功能...... sinsprintfsqrt ...都是   除非指定-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>

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