从标准库中重新定义一个函数是否违反了一个定义规则?
Does redefining a function from the standard library violate the one-definition rule?
#include <cmath>
double log(double) {return 1.0;}
int main() {
log(1.0);
}
假设<cmath>
中的函数log()
是在全局命名空间中声明的(实际上这是未指定的,我们只是做这样的假设),那么它引用与log()
我们定义的函数。
那么这段代码是否违反了一个定义规则(参见here,因为不需要诊断,这段代码可能会在某些编译器中编译,我们无法断言它是否正确)?
注意:经过最近的编辑,这不是以下的副本:What exactly is One Definition Rule in C++?
以下内容涉及 OP 的先前修订版。我把它留在这里以防将来的读者来到这里提出类似的查询。
I guess that two names refer to the same entity if and only if they have the same declarative region, where the concept "declarative region" is defined in the standard [...] Is this guess correct? Is there any word in the standard supporting this?
它通俗地称为变量隐藏或阴影。标准几乎逐字逐句地表达了您所说的内容。 §3.3.10 ¶1在当前的C++17标准草案中:
A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived class
So does this code violate the one-definition rule (see here, since no diagnostic required, this code may compile in some compiler and we cannot assert it is correct)?
我不希望如此。该标准要求 all cheader
headers(尤其是 cmath
)在 std
命名空间中引入它们的符号。也将其注入全局命名空间的实现是符合标准的(因为标准将该位保留为未指定),但我会发现它的形式不佳。你是对的,可能发生。现在,如果您要包含 math.h
(违背明智的建议),那肯定会导致违反单一定义规则。
典型场景。
如果 extern "C" double log(double)
最初是在全局命名空间中声明的,那么您已经重新声明它并提供了定义。该实现先前提到的 extern "C"
会延续到您的匹配重新声明中。您的定义适用于属于实现的函数,并且违反了 ODR。
至于UB的表现形式:把log
当作一个弱链接符号显然是很常见的。根据 ABI 规则,您的实现会覆盖 libc.so
。
(如果实现不做extern "C"
,基本上还是一样。)
其他可能的情况。
如果在namespace std
中声明了log
,然后又将其带入了全局命名空间,那么你的声明就会与之冲突。 (实际上,using
声明在技术上是一个声明。)诊断出此错误。
Assumption-violating场景。
then it refers to the same function as the log
function we defined
实现将 <cmath>
名称放入全局命名空间的一种方法是在 namespace std
中声明 extern "C"
函数,然后执行 using namespace std
,并确保当包含任何标准 header 时,这总是作为第一件事发生。由于 using namespace
不是 "sticky"——它只适用于指定名称空间中的前面声明——标准库的其余部分将不可见。 (这不会 声明 全局名称空间中的名称,但标准只说 "placed within the global namespace scope.")
在这样的实现中,您的声明将隐藏标准声明并声明一个具有新的错位名称的新函数(比如 _Z3logd
而不是简单的 log
)和一个新的 fully-qualified 名称(::log
而不是 ::std::log
)。那么就不会有 ODR 违规(除非某些内联函数在一个 TU 中使用一个 log
而在另一个 TU 中使用另一个)。
当心。 ODR 仅涉及将包含在生成的程序中的定义。这意味着它不涉及库中可能存在的符号,因为(正常)linker 不会加载整个库,而只会加载解析符号所需的部分。例如在这段代码中:
#include <cmath>
double log(double) {return 1.0;}
int main()
{
log(1.0);
}
没有违反ODR:
- 要么来自 C 标准库的日志符号只包含在
std
命名空间中,根本没有冲突
- 或者也包含在全局命名空间
在后一种情况下,声明 double log(double)
与 cmath 中的声明不冲突,因为它们是相同的。并且由于符号 log
已经被定义,它在标准库中的 定义 将不会包含在程序中。因此,程序中只存在 log
函数的一个定义,即 double log(double) {return 1.0;}
.
如果您从数学库中提取包含 log
的目标模块,并在您的程序中明确地 link 它,情况就会有所不同。因为目标模块总是包含在生成的程序中,而库中的目标模块仅在解析未定义符号时有条件地包含。
标准参考文献:
C++11 草案 n3337 或 C++14 草案 n4296(或最新修订版 n4618)在第 2.2 段翻译阶段中是明确的[lex.phases]:
§9. All external entity references are resolved. Library components are linked to satisfy external references
to entities not defined in the current translation. All such translator output is collected into a program
image which contains information needed for execution in its execution environment.
如图所示,代码仅使用一个翻译单元,并且log
已在其中定义,因此不会使用库中的定义。
#include <cmath>
double log(double) {return 1.0;}
int main() {
log(1.0);
}
假设<cmath>
中的函数log()
是在全局命名空间中声明的(实际上这是未指定的,我们只是做这样的假设),那么它引用与log()
我们定义的函数。
那么这段代码是否违反了一个定义规则(参见here,因为不需要诊断,这段代码可能会在某些编译器中编译,我们无法断言它是否正确)?
注意:经过最近的编辑,这不是以下的副本:What exactly is One Definition Rule in C++?
以下内容涉及 OP 的先前修订版。我把它留在这里以防将来的读者来到这里提出类似的查询。
I guess that two names refer to the same entity if and only if they have the same declarative region, where the concept "declarative region" is defined in the standard [...] Is this guess correct? Is there any word in the standard supporting this?
它通俗地称为变量隐藏或阴影。标准几乎逐字逐句地表达了您所说的内容。 §3.3.10 ¶1在当前的C++17标准草案中:
A name can be hidden by an explicit declaration of that same name in a nested declarative region or derived class
So does this code violate the one-definition rule (see here, since no diagnostic required, this code may compile in some compiler and we cannot assert it is correct)?
我不希望如此。该标准要求 all cheader
headers(尤其是 cmath
)在 std
命名空间中引入它们的符号。也将其注入全局命名空间的实现是符合标准的(因为标准将该位保留为未指定),但我会发现它的形式不佳。你是对的,可能发生。现在,如果您要包含 math.h
(违背明智的建议),那肯定会导致违反单一定义规则。
典型场景。
如果 extern "C" double log(double)
最初是在全局命名空间中声明的,那么您已经重新声明它并提供了定义。该实现先前提到的 extern "C"
会延续到您的匹配重新声明中。您的定义适用于属于实现的函数,并且违反了 ODR。
至于UB的表现形式:把log
当作一个弱链接符号显然是很常见的。根据 ABI 规则,您的实现会覆盖 libc.so
。
(如果实现不做extern "C"
,基本上还是一样。)
其他可能的情况。
如果在namespace std
中声明了log
,然后又将其带入了全局命名空间,那么你的声明就会与之冲突。 (实际上,using
声明在技术上是一个声明。)诊断出此错误。
Assumption-violating场景。
then it refers to the same function as the
log
function we defined
实现将 <cmath>
名称放入全局命名空间的一种方法是在 namespace std
中声明 extern "C"
函数,然后执行 using namespace std
,并确保当包含任何标准 header 时,这总是作为第一件事发生。由于 using namespace
不是 "sticky"——它只适用于指定名称空间中的前面声明——标准库的其余部分将不可见。 (这不会 声明 全局名称空间中的名称,但标准只说 "placed within the global namespace scope.")
在这样的实现中,您的声明将隐藏标准声明并声明一个具有新的错位名称的新函数(比如 _Z3logd
而不是简单的 log
)和一个新的 fully-qualified 名称(::log
而不是 ::std::log
)。那么就不会有 ODR 违规(除非某些内联函数在一个 TU 中使用一个 log
而在另一个 TU 中使用另一个)。
当心。 ODR 仅涉及将包含在生成的程序中的定义。这意味着它不涉及库中可能存在的符号,因为(正常)linker 不会加载整个库,而只会加载解析符号所需的部分。例如在这段代码中:
#include <cmath>
double log(double) {return 1.0;}
int main()
{
log(1.0);
}
没有违反ODR:
- 要么来自 C 标准库的日志符号只包含在
std
命名空间中,根本没有冲突 - 或者也包含在全局命名空间
在后一种情况下,声明 double log(double)
与 cmath 中的声明不冲突,因为它们是相同的。并且由于符号 log
已经被定义,它在标准库中的 定义 将不会包含在程序中。因此,程序中只存在 log
函数的一个定义,即 double log(double) {return 1.0;}
.
如果您从数学库中提取包含 log
的目标模块,并在您的程序中明确地 link 它,情况就会有所不同。因为目标模块总是包含在生成的程序中,而库中的目标模块仅在解析未定义符号时有条件地包含。
标准参考文献:
C++11 草案 n3337 或 C++14 草案 n4296(或最新修订版 n4618)在第 2.2 段翻译阶段中是明确的[lex.phases]:
§9. All external entity references are resolved. Library components are linked to satisfy external references to entities not defined in the current translation. All such translator output is collected into a program image which contains information needed for execution in its execution environment.
如图所示,代码仅使用一个翻译单元,并且log
已在其中定义,因此不会使用库中的定义。