log(10.0) может компилироваться, но log(0.0) не может?

Для следующего исходного кода C:

#include <math.h>

int main(void)
{
    double          x;

    x = log(0.0);

    return 0;
}

Когда я собираю с gcc -lm, Я получил:

/tmp/ccxxANVH.o: In function `main':
a.c:(.text+0xd): undefined reference to `log'
collect2: error: ld returned 1 exit status

Но если я заменю log(0.0) с log(10.0), то он может успешно скомпилироваться.

Я не совсем понимаю, так как независимо от того, имеют они математический смысл или нет, они должны компилироваться - синтаксической ошибки нет. Кто-нибудь может объяснить это?

На всякий случай мой gcc -v выход:

Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.8.2-19ubuntu1' --with-bugurl=file:///usr/share/doc/gcc-4.8/README.Bugs --enable-languages=c,c++,java,go,d,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.8 --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.8 --libdir=/usr/lib --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-gnu-unique-object --disable-libmudflap --enable-plugin --with-system-zlib --disable-browser-plugin --enable-java-awt=gtk --enable-gtk-cairo --with-java-home=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64/jre --enable-java-home --with-jvm-root-dir=/usr/lib/jvm/java-1.5.0-gcj-4.8-amd64 --with-jvm-jar-dir=/usr/lib/jvm-exports/java-1.5.0-gcj-4.8-amd64 --with-arch-directory=amd64 --with-ecj-jar=/usr/share/java/eclipse-ecj.jar --enable-objc-gc --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.8.2 (Ubuntu 4.8.2-19ubuntu1)

Обратите внимание, что этот вопрос касается постоянного сворачивания, но предложенный дублирующий вопрос касается отсутствующей библиотеки ссылок.

2 ответа

Решение

gcc во многих случаях могут использовать встроенные функции, их документация гласит:

Многие из этих функций оптимизированы только в определенных случаях; если они не оптимизированы в конкретном случае, вызывается функция библиотеки.

И потому gcc не нужно будет ссылаться на математическую библиотеку при использовании встроенной функции, но так как log(0) не определяется это, вероятно, силы gcc оценить его во время выполнения, так как он имеет побочный эффект.

Если мы посмотрим на проект стандартного раздела C99 7.12.1 Обработка условий ошибки в пункте 4 гласит (выделение мое):

Плавающий результат переполняется, если величина математического результата конечна, но настолько велика, что математический результат не может быть представлен без чрезвычайной ошибки округления в объекте указанного типа. Если происходит переполнение плавающего результата и округление по умолчанию, или если математический результат является точной бесконечностью от конечных аргументов (например, log(0.0)), то функция возвращает значение макроса HUGE_VAL, HUGE_VALF или HUGE_VALL в соответствии с тип возврата с тем же знаком, что и правильное значение функции; если целочисленное выражение math_errhandling & MATH_ERRNO не равно нулю, целочисленное выражение errno получает значение ERANGE; если целочисленное выражение math_errhandling & MATH_ERREXCEPT отличное от нуля, исключение "деление на ноль" возникает, если математический результат является точной бесконечностью, а исключение "переполнение" возникает в противном случае.

Мы можем видеть из живого примера, используя -S флаг для генерации сборки и grep log отфильтровать звонки log,

В случае log(0.0) генерируется следующая инструкция ( смотрите вживую):

call    log

но в случае log(10.0) нет call log инструкция генерируется, ( см. вживую).

Обычно мы можем предотвратить gcc от использования встроенной функции с помощью флага -fno-builtin, который, вероятно, является более быстрым способом проверить, используется ли встроенная функция.

Обратите внимание, что -lm должен идти после исходного файла, например (взят из связанного ответа), если main.c требуется математическая библиотека, то вы будете использовать:

 gcc main.c -lm 

Компиляция в порядке, это просто переключатель компоновщика -lm чего не хватает

Вторая версия, вероятно, компилирует и ссылается, потому что gcc заменяет log(10.0) с константой, поэтому не требуется вызов математической библиотеки. Во втором случае результат математически не определен, и в результате оценки возникает ошибка домена. В этом случае выражение не может быть заменено константой, поскольку обработка ошибок домена может отличаться во время выполнения.

Цитата из стандарта С ( черновик):

В случае ошибки домена функция возвращает значение, определенное реализацией; если целочисленное выражение math_errhandling & MATH_ERRNO не равно нулю, целочисленное выражение errno получает значение EDOM; если целочисленное выражение math_errhandling & MATH_ERREXCEPT отличное от нуля, возникает "недопустимое" исключение с плавающей точкой.

Итак, оценка log(0.0) либо приводит к возвращению значения HUGE_VAL (не NAN как я утверждал ранее) или исключение с плавающей запятой.

РЕДАКТИРОВАТЬ: я исправил свой ответ на основе полученных комментариев и добавил ссылку на описание в C-стандарте.

Другие вопросы по тегам