<cmath> 在 C++14 / C++11 的 <math.h> 中隐藏 isnan?

Posted

技术标签:

【中文标题】<cmath> 在 C++14 / C++11 的 <math.h> 中隐藏 isnan?【英文标题】:<cmath> hides isnan in <math.h> in C++14 / C++11? 【发布时间】:2016-12-31 23:40:12 【问题描述】:

我这里有一个小测试应用程序,它使用来自&lt;math.h&gt;isnan

#include <iostream>
#include <math.h>

int main()

    double d = NAN;

    std::cout << isnan(d) << '\n';

    return 0;

在 3 种不同的标准下构建和运行:

$ g++ -std=c++98 main.cpp; ./a.out
1

$ g++ -std=c++11 main.cpp; ./a.out
1

$ g++ -std=c++14 main.cpp; ./a.out
1

现在我们还包括&lt;cmath&gt;,并同时使用isnanstd::isnan 进行测试:

#include <iostream>
#include <cmath>
#include <math.h>

int main()

    double d = NAN;

    std::cout << std::isnan(d) << '\n';
    std::cout << isnan(d) << '\n';

    return 0;

构建并运行:

C++98 作品

$ g++ -std=c++98 main.cpp; ./a.out
1
1

C++11 和 C++14 没有,找不到isnan

$ g++ -std=c++11 main.cpp
main.cpp: In function ‘int main()’:
main.cpp:10:25: error: ‘isnan’ was not declared in this scope
     std::cout << isnan(d) << '\n';
                         ^
main.cpp:10:25: note: suggested alternative:
In file included from main.cpp:3:0:
/usr/include/c++/5/cmath:641:5: note:   ‘std::isnan’
     isnan(_Tp __x)
     ^

$ g++ -std=c++14 main.cpp
main.cpp: In function ‘int main()’:
main.cpp:10:25: error: ‘isnan’ was not declared in this scope
     std::cout << isnan(d) << '\n';
                         ^
main.cpp:10:25: note: suggested alternative:
In file included from main.cpp:3:0:
/usr/include/c++/5/cmath:641:5: note:   ‘std::isnan’
     isnan(_Tp __x)
     ^

请注意,包含的顺序并不重要。如果我在&lt;math.h&gt; 之前或之后包含&lt;cmath&gt;,结果是一样的。

问题

为什么isnan 不见了? 无需返回并更改旧代码以在新标准下编译,有什么办法可以解决这个问题吗?

【问题讨论】:

Clang 是使用 libc++,还是和 gcc 一样的 libstdc++? @ildjarn ldd 说是一样的 (/usr/lib/x86_64-linux-gnu/libstdc++.so.6)。我已编辑问题以删除 clang 部分。 您使用的是什么版本的编译器?我无法在 coliru 上使用 g++ 或 clang 进行复制。 看起来可能是库的错误。 gcc.godbolt.org 在 4.9.2 到 5.3 中失败,但在 6.1 上有效 @SteveLorimer 如果您在几天内没有找到任何东西,请给我一个 ping,我会悬赏。我真的很想知道发生了什么。 【参考方案1】:

简单总结一下相关点,大多来自Jonathan Wakely's excellent blog post:

glibc math.h 声明了与 C99/C++11 版本 (bool isnan(double);) 不兼容的过时 X/Open int isnan(double);。 glibc 2.23 的 math.h 通过不在 C++11 或更高版本中声明 isnan 函数来解决此问题。 它们仍然定义了一个isnan 宏。 #include &lt;cmath&gt; 按照 C++ 标准的要求核对该宏。 GCC 6 的 libstdc++ 提供了自己特殊的 math.h 标头,该标头在全局命名空间中声明了一个 bool isnan(double);(除非 libc math.h 声明了过时的签名),并且还按照标准的要求对宏进行了核对。 在 GCC 6 之前,#include &lt;math.h&gt; 仅包含来自您的 libc 的标头,因此宏不会被核化。 #include &lt;cmath&gt; 总是核对宏。

净结果,在 C++11 模式下:

glibc <  2.23, GCC <  6: <math.h> uses the macro; <cmath> uses obsolete signature
glibc >= 2.23, GCC <  6: <math.h> uses the macro; <cmath> results in error
glibc <  2.23, GCC >= 6: <math.h> and <cmath> use obsolete signature
glibc >= 2.23, GCC >= 6: <math.h> and <cmath> use standard signature

【讨论】:

@NathanOliver 你看到这个答案了吗? @SteveLorimer 我现在有。谢谢 T.C.另一个很好的答案。【参考方案2】:

如果你从 GCC 内部查看 &lt;cmath&gt;,它有:

. . .
#include <math.h>
. . .
#undef isnan

这就是顺序无关紧要的原因 - 每当您 #include &lt;cmath&gt;&lt;math.h&gt; 被自动包含并且其内容(部分)被删除时。

由于#ifndef _MATH_H,尝试再次包含它将无效。


现在,标准对这种行为有什么看法?

[depr.c.headers]:

... 每个 C 标头,每个 其中有一个name.h 形式的名称,表现得好像每个名称都放置了 在标准库命名空间中,对应的 cname 标头是 放置在全局命名空间范围内。未指定是否 这些名称首先在命名空间范围内声明或定义 ([basic.scope.namespace]) 命名空间std 然后被注入 通过显式 using-declarations 进入全局命名空间范围 ([namespace.udecl])。

[ 示例:标题&lt;cstdlib&gt; 确实提供了它的声明 和命名空间std 中的定义。它可能还提供这些 全局命名空间中的名称。标头&lt;stdlib.h&gt;肯定 在全局范围内提供相同的声明和定义 命名空间,就像在 C 标准中一样。它还可能提供这些名称 在命名空间std 内。 —结束示例]

所以&lt;cmath&gt; 在全局命名空间中不提供isnan 是可以的。

但是当两个都包含在一个编译单元中时会发生什么是一个灰色地带,尽管有人可能会争辩说上面的陈述暗示两个版本必须互操作,在在这种情况下,这将是 GCC/libstdc++(某些版本)中的错误。

【讨论】:

【参考方案3】:

math.h 中的很多函数实际上都是宏。由于这不是惯用的 C++ 标头 cmath 包含以下代码:

    ...
    #undef isinf
    #undef isnan
    #undef isnormal
    ...

然后将所有未定义的宏实现为namespace std 中的函数。至少对于 gcc 6.1.1 是这样。这就是为什么你的编译器找不到isnan

【讨论】:

我可以同时编译std::isnanisnan on gcc-6.1 on godbolt 如果是undef的问题,包含的顺序很重要,但没关系 @AntonMalyshev :为什么顺序很重要? cmath 几乎总是会包含 math.h,它将有一个标头保护——它总是会被包含一次,而且只包含一次。

以上是关于<cmath> 在 C++14 / C++11 的 <math.h> 中隐藏 isnan?的主要内容,如果未能解决你的问题,请参考以下文章

#include<cmath>啥意思?

C++中的<math>和<cmath>有啥区别

contest 1.14

系统头文件cmath,cstdlib报错

c语言和c++头文件在哪些地方有所不同

C++中的cmath头文件