【问题标题】:Unusual behavior of standard library function abs() on different C++ compilers标准库函数 abs() 在不同 C++ 编译器上的异常行为
【发布时间】:2018-01-04 05:11:31
【问题描述】:

考虑以下程序:

#include <cstdio>
#include <cmath>

int main()
{
    int d = (int)(abs(0.6) + 0.5);
    printf("%d", d);
    return 0;
}

g++ 7.2.0 输出0(见现场演示here

g++6.3.0(见现场演示here

prog.cc: In function 'int main()':
prog.cc:6:26: error: 'abs' was not declared in this scope
     int d = (int)(abs(0.6) + 0.5);
                          ^
prog.cc:6:26: note: suggested alternative:
In file included from prog.cc:2:0:
/opt/wandbox/gcc-6.3.0/include/c++/6.3.0/cmath:103:5: note:   'std::abs'
     abs(_Tp __x)
     ^~~

clang++ 5.0.0 输出1(见现场演示here

clang++3.6.0(见现场演示here

prog.cc:6:19: error: use of undeclared identifier 'abs'; did you mean 'fabs'?
    int d = (int)(abs(0.6) + 0.5);
                  ^~~
                  fabs
/usr/include/x86_64-linux-gnu/bits/mathcalls.h:181:14: note: 'fabs' declared here
__MATHCALLX (fabs,, (_Mdouble_ __x), (__const__));
             ^
/usr/include/math.h:71:26: note: expanded from macro '__MATHCALLX'
  __MATHDECLX (_Mdouble_,function,suffix, args, attrib)
                         ^
/usr/include/math.h:73:22: note: expanded from macro '__MATHDECLX'
  __MATHDECL_1(type, function,suffix, args) __attribute__ (attrib); \
                     ^
/usr/include/math.h:76:31: note: expanded from macro '__MATHDECL_1'
  extern type __MATH_PRECNAME(function,suffix) args __THROW
                              ^
/usr/include/math.h:79:42: note: expanded from macro '__MATH_PRECNAME'
#define __MATH_PRECNAME(name,r) __CONCAT(name,r)
                                         ^
/usr/include/x86_64-linux-gnu/sys/cdefs.h:88:23: note: expanded from macro '__CONCAT'
#define __CONCAT(x,y)   x ## y
                        ^
1 error generated.

Microsoft VC++ 19.00.23506 输出 1(见现场演示 here

这个程序到底发生了什么?为什么在不同的 C++ 编译器上编译时会给出不同的输出?为什么即使在同一编译器的不同版本上程序也会表现出不同的行为?这是编译器问题还是标准库(libstdc++ & libc++)问题? C++ 标准对此有何规定?

PS:我知道我需要写std::abs 而不是abs。但这不是我的问题。

【问题讨论】:

  • 数学标题远非统一。 g++ 实际上是最不标准的一次,而不是 VS。

标签: c++ language-lawyer libstdc++ c++-standard-library libc++


【解决方案1】:

从 C 标准库引入功能的所有 cname 库头文件必须在命名空间 std 中引入这些符号。它们也可能,但绝对不是必须将它们引入全局命名空间。 [headers]/4:

除非条款 [library] 到 [thread] 和附件中注明 [depr],每个header cname的内容与 C 标准库中指定的相应头文件 name.h。在 但是,C++ 标准库中的声明(名称除外 在 C 中定义为宏)在 命名空间标准。这些名称(包括任何 通过 [thread] 在子句 [language.support] 中添加的重载和 附件 [depr]) 首先在全局命名空间范围内声明,并且 然后通过显式 using 声明将其注入命名空间 std。

所以不同的编译器,甚至不同的编译器版本,意味着不同的实现细节。

【讨论】:

  • 因此,如果编译器和库升级,程序的行为很可能会发生变化,并且仍将被视为标准确认。对吗?
  • @Destructor - 是的。获得保证行为的唯一方法是通过命名空间std
  • 为了更深入地了解,请考虑 abs 在 C 中基本上分为 &lt;stdlib.h&gt;&lt;math.h&gt;。C++ 还根据 LWG 2192&lt;cmath&gt; 添加了积分重载。另请考虑&lt;cstdlib&gt; 可能包含在问题中包含的两个标题之一中,并且这很容易因实现和版本而异。 absRedhat's article 中描述的关于更改 GCC 对 C 标头的处理的事情之一。
  • @chris: 如果你写你的答案会更好
  • @Destructor,这样的答案对于问题中每种情况的确切情况最有用,但我没有这些信息。我只能根据输出推测某些实现选择整数版本,导致输出为 0,而另一些实现选择 double 版本,导致输出为 1。我很难确切地说为什么在每个实现中选择任何一个无需花费大量时间的情况下,特别是对于 C++ 版本是否可用全局名称的实现定义。
猜你喜欢
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
  • 2020-01-10
  • 2016-12-30
  • 1970-01-01
  • 2018-10-31
  • 1970-01-01
  • 2011-09-11
相关资源
最近更新 更多