声明会影响 std 命名空间吗?

Can a declaration affect the std namespace?

#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;
}

我预计输出将是 -55,但输出是 -5-5

不知为什么会出现这种情况?

std的使用有什么关系吗?

通过在 global 命名空间中声明(和定义)标准函数然后将它们引入命名空间 <cmath> 来实现 <cmath> 的语言规范 allows 实现=13=] 通过使用声明。未说明是否使用这种方法

20.5.1.2 Headers
4 [...] In the C++ standard library, however, the declarations (except for names which are defined as macros in C) are within namespace scope (6.3.6) of the namespace std. It is unspecified whether these names (including any overloads added in Clauses 21 through 33 and Annex D) are first declared within the global namespace scope and are then injected into namespace std by explicit using-declarations (10.3.3).

显然,您正在处理决定遵循这种方法的实现之一(例如 GCC)。 IE。您的实现提供 ::abs,而 std::abs 只是 "refers" 到 ::abs

在这种情况下仍然存在的一个问题是,为什么除了标准 ::abs 之外,您还可以声明自己的 ::abs,即为什么没有多重定义错误。这可能是由某些实现(例如 GCC)提供的另一个特性引起的:它们将标准函数声明为所谓的 weak symbols,因此允许您使用自己的定义 "replace" 它们.

这两个因素共同产生了您观察到的效果:::abs 的弱符号替换也会导致 std::abs 的替换。这与语言标准的吻合程度是另一回事......无论如何,不​​要依赖这种行为 - 语言不能保证它。

在 GCC 中,可以通过以下简约示例重现此行为。一个源文件

#include <iostream>

void foo() __attribute__((weak));
void foo() { std::cout << "Hello!" << std::endl; }

另一个源文件

#include <iostream>

void foo();
namespace N { using ::foo; }

void foo() { std::cout << "Goodbye!" << std::endl; }

int main()
{
  foo();
  N::foo();
}

在这种情况下,您还将观察到第二个源文件中 ::foo ("Goodbye!") 的新定义也会影响 N::foo 的行为。两次调用都将输出 "Goodbye!"。如果您从第二个源文件中删除 ::foo 的定义,则两个调用都将分派到 ::foo 的 "original" 定义并输出 "Hello!".


上面20.5.1.2/4给出的权限是为了简化<cmath>的实现。允许实现简单地包含 C 风格 <math.h>,然后重新声明 std 中的函数并添加一些特定于 C++ 的添加和调整。如果上述解释正确地描述了问题的内部机制,那么它的主要部分取决于 C 风格版本 函数的弱符号的可替换性。

请注意,如果我们在上面的程序中简单地将 int 全局替换为 double,代码(在 GCC 下)将表现 "as expected" - 它将输出 -5 5 .发生这种情况是因为 C 标准库没有 abs(double) 函数。通过声明我们自己的 abs(double),我们不会替换任何东西。

但是如果在从 int 切换到 double 之后我们也从 abs 切换到 fabs,那么原来的怪异行为将重新出现(输出 -5 -5).

这和上面的解释是一致的

您的代码导致未定义的行为。

C++17 [extern.names]/4:

Each function signature from the C standard library declared with external linkage is reserved to the implementation for use as a function signature with both extern "C" and extern "C++" linkage, or as a name of namespace scope in the global namespace.

因此您不能创建与标准 C 库函数具有相同原型的函数 int abs(int);。无论您实际包含哪个 headers,或者那些 headers 是否也将 C 库名称放入全局命名空间。

但是,如果您提供不同的参数类型,则允许重载 abs