为什么GCC在C++<cmath>中比在C<math.h>中更有效地实现isnan()函数?

47

这是我的代码:

int f(double x)
{
  return isnan(x);
}

如果我添加#include <cmath>,我会得到这个汇编代码:

xorl    %eax, %eax
ucomisd %xmm0, %xmm0
setp    %al

这是相当巧妙的:ucomisd会在x与其自身比较无序(即x为NAN)时设置奇偶标志位。然后setp将奇偶标志复制到结果(仅一个字节,因此初始清除%eax)。

但是如果我包含 #include <math.h> ,我得到这个汇编:

jmp     __isnan

现在代码不再是内联的,而且__isnan函数肯定比ucomisd指令慢,所以我们因为没有任何好处而产生了一个跳转。如果我将代码编译为C,我得到的结果是一样的。

现在,如果我将isnan()调用更改为__builtin_isnan(),则无论我包含哪个头文件,都会获得简单的ucomisd指令,并且它也适用于C。同样,如果我只需要return x != x

所以我的问题是,为什么C语言的<math.h>头文件提供的isnan()实现比C++的<cmath>头文件提供的实现效率低呢?人们真的期望使用__builtin_isnan()吗?如果是这样,为什么?

我在x86-64上测试了GCC 4.7.2和4.9.0,并使用了-O2-O3优化。


2
这是我的猜测:在C语言的c99标准之前,没有内联函数。没有内联函数意味着函数必须通过jmp/call(或某种分支)来调用。__builtin_isnan不是C语言的一部分。它可能是特定于平台的内置函数。 - thang
2
但是像 <math.h> 这样的系统头文件肯定可以使用特定于平台的内置函数。 - John Kugelman
1
我非常确定如果可能的话,isnan 会使用 __builtin_isnan。我看不出为什么你必须手动调用它。 - Rapptz
2
也许当C99出现时,没有人想到要回去更新isnan函数。 - thang
4
抱歉,我无法打开该链接。请提供需要翻译的具体内容,我将尽力进行翻译。 - Marc Glisse
显示剩余10条评论
1个回答

19

查看gcc 4.9中附带的libstdc++的<cmath>,你会得到以下内容:

  constexpr bool
  isnan(double __x)
  { return __builtin_isnan(__x); }

constexpr函数可以被强制内联,当然,该函数只是将工作委托给__builtin_isnan

<math.h>头文件并不使用__builtin_isnan,而是使用一种__isnan实现,这种实现可能比较长,不方便在此处贴出,但在我的机器上,它位于math.h的第430行™。由于C99标准要求使用宏来表示isnan等函数(参见C99标准的7.12节),因此'函数'定义如下:

#define isnan(x) (sizeof (x) == sizeof (float) ? __isnanf (x)   \
  : sizeof (x) == sizeof (double) ? __isnan (x) \
  : __isnanl (x))

然而,我认为它完全可以使用__builtin_isnan而不是__isnan,所以我认为这是个疏忽。正如Marc Glisse在评论中指出的那样,有一个相关的bug报告,针对类似使用isinf而不是isnan的问题。


实际上,这个 bug 是关于 isinf 函数的。虽然是一个不同函数的类似问题,但并不完全相同。 - thang
2
不要忘记包含 这个 说标准要求它们是宏。 - Mysticial
1
你认为将<math.h>更改为简单地说#define isnan(x) __builtin_isnan(x)是合法的吗? - John Zwinck
1
@JohnZwinck 是的,我想不到任何理由说明它无效。 - Rapptz
1
对于gcc而言,这是有效的。__builtin_isnan并不在每个编译器上都存在。 - thang
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接