константа литерала против переменной в математической библиотеке - PullRequest
5 голосов
/ 29 марта 2012

Итак, я знаю, что в 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, когда библиотека не связана?Я использую gcc 4.4.5 под Linux.

Ответы [ 4 ]

5 голосов
/ 29 марта 2012

GCC может выполнять постоянное свертывание для нескольких функций стандартной библиотеки. Очевидно, что если функция свернута во время компиляции, нет необходимости в вызове функции времени выполнения, поэтому нет необходимости ссылаться на libm. Вы можете подтвердить это, взглянув на ассемблер, который создает компилятор (используя objdump или аналогичный).

Я полагаю, что эти оптимизации запускаются только тогда, когда аргумент является константным выражением.

4 голосов
/ 29 марта 2012

Как все упоминают, да, это связано с постоянным свертыванием .

При отключенной оптимизации 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
$ 
4 голосов
/ 29 марта 2012

Я думаю, что GCC использует его встроенный. Я скомпилировал ваш код с: -fno-builtin-sqrt и получил ожидаемую ошибку компоновщика.

Функции ISO C90 ... sin, sprintf, sqrt ... все распознаются как встроенные функции, если не указано -fno-builtin

2 голосов
/ 29 марта 2012

Это потому, что 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 имеют целую страницу, посвященную этим встроенным элементам здесь , а соответствующий раздел на этой странице для 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, vprintf и vsprintf все распознаются как встроенные функции, если не указано -fno-builtin (или -fno-builtin-function не указано для отдельной функции).

Итак, довольно много, правда: -)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...