#include <iostream>
#include <cmath>
/* Intentionally incorrect abs() which seems to override std::abs() */
int abs(int a) {
return a > 0? -a : a;
}
int main() {
int a = abs(-5);
int b = std::abs(-5);
std::cout<< a << std::endl << b << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我期望输出将是-5和5,但输出是-5和-5.
我想知道为什么会发生这种情况?
它与使用std或什么有关?
AnT*_*AnT 91
语言规范允许实现<cmath>通过在全局命名空间中声明(和定义)标准函数,然后std通过using-declarations 将它们引入命名空间来实现.未指定是否使用此方法
20.5.1.2标头
4 [...]然而,在C++标准库中,声明(在C中定义为宏的名称除外)在命名空间的命名空间范围(6.3.6)内std.未指定这些名称(包括第21至33条和附件D中添加的任何重载)是否首先在全局命名空间范围内声明,然后std通过显式使用声明(10.3.3)注入命名空间.
显然,您正在处理决定遵循此方法的实现之一(例如GCC).即你的实现提供::abs,而std::abs只是"引用" ::abs.
在这种情况下仍然存在的一个问题是,为什么除了标准之外,::abs您还可以声明自己的标准::abs,即为什么没有多重定义错误.这可能是由某些实现(例如GCC)提供的另一个特性引起的:它们将标准函数声明为所谓的弱符号,从而允许您用自己的定义"替换"它们.
这两个因素共同创造了您观察到的效果:弱符号替换::abs也会导致替换std::abs.这与语言标准的一致性是一个不同的故事......无论如何,不要依赖于这种行为 - 语言无法保证.
在GCC中,这种行为可以通过以下简约示例再现.一个源文件
#include <iostream>
void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }
Run Code Online (Sandbox Code Playgroud)
另一个源文件
#include <iostream>
void foo();
namespace N { using ::foo; }
void foo() { std::cout << "Goodbye!" << std::endl; }
int main()
{
foo();
N::foo();
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,您还将观察到第二个源文件中::foo("Goodbye!")的新定义也会影响其行为N::foo.两个调用都会输出"Goodbye!".如果::foo从第二个源文件中删除定义,则两个调用都将调度到"原始"定义::foo和输出"Hello!".
上述20.5.1.2/4给出的许可是为了简化实现<cmath>.允许实现只包含C风格<math.h>,然后重新声明函数std并添加一些特定于C++的添加和调整.如果上面的解释恰当地描述了问题的内在机制,那么它的主要部分取决于函数的C风格版本的弱符号的可替换性.
请注意,如果我们简单地在全球范围取代int用double在上面的程序,代码(GCC下)的行为"预期" -它会输出-5 5.这是因为C标准库没有abs(double)功能.通过声明我们自己abs(double),我们不会取代任何东西.
但是,如果从切换后int与double我们也从切换abs到fabs,原来怪异的行为将在其盛开(输出重现-5 -5).
这与上述说明一致.
M.M*_*M.M 13
您的代码导致未定义的行为.
C++ 17 [extern.names]/4:
来自使用外部链接声明的C标准库的每个函数签名保留给实现,以用作具有extern"C"和extern"C++"链接的函数签名,或者作为全局命名空间中的命名空间范围的名称.
因此,您无法使用与标准C库函数相同的原型来创建函数int abs(int);.无论您实际包含哪些标头,或者这些标头是否也将C库名称放入全局名称空间.
但是,abs如果提供不同的参数类型,则允许重载.