未命名命名空间内名称的外部链接

αλε*_*λυτ 5 c++ unnamed-namespace c++14

根据 C++ 标准的第 3.5/4 条:

未命名命名空间或在未命名命名空间内直接或间接声明的命名空间具有内部链接。

同时在第 7.3.1.1 段中,我们有注释 96):

尽管未命名命名空间中的实体可能具有外部链接,但它们有效地由其翻译单元唯一的名称限定,因此无法从任何其他翻译单元看到。

如果标准保证无法从另一个翻译单元访问未命名命名空间内定义的名称,如何显式地为未命名命名空间内的名称建立外部链接,以及如何检查链接实际上是外部的?

在哪些情况下对未命名命名空间内的名称进行显式外部链接有用?

Che*_*Alf 5

关于

\n\n
\n

在哪些情况下,对未命名命名空间内的名称进行显式外部链接是有用的?

\n
\n\n

对于 C++03 模板来说,外部链接的需求非常重要。例如,作为模板参数的函数指针必须是指向外部链接函数的指针。例如,以下内容将无法使用 C++03 编译器进行编译:

\n\n
template< void(*f)() >\nvoid tfunc() { f(); }\n\n#include <stdio.h>\nstatic void g() { printf( "Hello!\\n" ); }\n\nint main()\n{\n    tfunc<g>();\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

它可以使用 C++11 编译器正常编译。

\n\n

因此,对于 C++11,具有外部链接但翻译单元之间没有名称冲突的匿名命名空间机制仅在技术上对于类来说是必需的。类具有外部链接。人们不想选择保证不会出现在其他翻译单元中的类名。

\n\n

在 C++11 中,规则不仅改变了模板参数,还改变了匿名命名空间中的事物是否具有外部链接。对于 C++03,匿名命名空间具有正式的外部链接,除非它本身位于匿名命名空间内(C++03 \xc2\xa73.5/4 最后一个破折号 + C++03 \xc2\xa77.3.1.1 /1)。在 C++11 中,匿名命名空间具有正式的内部链接。

\n\n

这对链接器来说并不重要,因为没有命名空间的链接,但作为描述事物链接的正式设备很重要:

\n\n C++11 \xc2\xa73.5/4: \n\n
\n

未命名的命名空间或在未命名的命名空间中直接或间接声明的命名空间具有内部链接。所有其他名称空间都有外部链接。具有命名空间范围且未在上面给出内部链接的名称,如果它是
变量的名称,则与封闭命名空间具有相同的链接;或
\n \xe2\x80\x94 一个函数;或
\n \xe2\x80\x94 命名类(第 9 条),或在声明中定义的未命名类,typedef其中该类具有typedef用于链接目的的名称(7.1.3);或
\n \xe2\x80\x94 命名枚举 (7.2),或在声明中定义的未命名枚举,typedef其中枚举具有用于链接目的的 typedef 名称 (7.1.3);或
\n \xe2\x80\x94 属于具有链接的枚举的枚举器;或
\n \xe2\x80\x94 模板。

\n
\n\n
\n\n

在继续回答您的其他问题之前,值得注意的是标准中的这段引用,

\n\n
\n

尽管未命名命名空间中的实体可能具有外部链接,但它们实际上是通过其翻译单元特有的名称进行限定的,因此永远无法从任何其他翻译单元中看到它们。

\n
\n\n

完全错误,因为extern "C"无论在哪个命名空间中声明实体,都可以从其他翻译单元中看到该实体。

\n\n

幸运的是,我记得注释是非规范的,即它们不定义语言。

\n\n
\n\n

关于

\n\n
\n

如何在未命名的命名空间内显式地为名称建立外部链接

\n
\n\n

只需将一个非const变量或函数声明为extern\n您可以声明一个非const变量或函数,如extern "C",使链接成为外部,但同时使命名空间与链接无关:C 语言没有它们。

\n\n
namespace {\n    extern "C" void foo() {}    // Extern linkage\n}  // namespace <anon>\n\nvoid bar() {}  // Also extern linkage, but visible to other TUs.\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n\n

关于

\n\n
\n

如何检查链接实际上是外部的

\n
\n\n

好吧,这种链接会影响诸如与单一定义规则(通常缩写为“ ODR ”)可能发生冲突之类的问题,在 C++11 中为 \xc2\xa73.2。

\n\n

因此,查看实际链接的一种方法是链接从上述源生成的两个目标文件,就好像您有两个具有相同源代码的翻译单元一样:

\n\n
\nC:\\my\\forums\\so\\088> g++ -c anon.cpp -o xo & g++ -c anon.cpp -o yo \n\nC:\\my\\forums\\so\ \088> g++ main.cpp xo yo \ny.o:anon.cpp:(.text+0x0): `foo\'的多重定义\nx.o:anon.cpp:(.text+0x0): 首先定义这里\ny.o:anon.cpp:(.text+0x7): `bar()\'的多重定义\nx.o:anon.cpp:(.text+0x7): 首先在这里定义\ncollect2.exe:错误: ld 返回 1 退出状态\n\nC:\\my\\forums\\so\\088> _\n
\n\n

链接器抱怨 的多个定义foo,因为与 C 语言绑定一样,就链接器而言,它作为inline全局命名空间的非外部链接成员,具有两个(可能冲突的)定义。

\n


Jon*_*ely 5

如何在未命名的命名空间内显式地为名称建立外部链接

我能想到的唯一方法是给它C语言链接,以便它的链接名称忽略命名空间限定:

namespace {
  extern void f() { }      // has internal linkage despite 'extern'
  extern "C" void g() { }  // ignores linkage of namespace
}
void (*p)() = f;  // ensure 'f' won't be optimized away
Run Code Online (Sandbox Code Playgroud)

(对标准的严格阅读表明g应该具有内部链接,但这不是编译器似乎所做的。)

如果标准保证无法从另一个翻译单元访问未命名命名空间内定义的名称,如何检查链接实际上是外部的?

通常,ELF 编译器将实现与非全局符号的内部链接,因此您可以编译代码并检查目标文件:

$ g++ -c linkage.cc
$ nm linkage.o
0000000000000000 t _ZN12_GLOBAL__N_11fEv
0000000000000007 T g
0000000000000000 D p
Run Code Online (Sandbox Code Playgroud)

未命名命名空间的损坏名称可能因编译器而异,但对其进行损坏后将显示:

$ nm -C  linkage.o
0000000000000008 t (anonymous namespace)::f()
0000000000000000 T g
0000000000000000 D p
Run Code Online (Sandbox Code Playgroud)

小写字母t表示f具有本地可见性,这意味着它不能从其他目标文件链接到。大写字母T表示g有外部链接。

不过,标准并不能保证这一点,因为 ELF 可见性不是 C++ 标准的一部分,并且一些编译器即使在 EL​​F 平台上也无需使用可见性即可实现链接,例如 EDG 编译器为相同代码生成全局符号:

$ nm linkage.o
0000000000000008 T _ZN23_GLOBAL__N__7_link_cc_p1fEv
0000000000000004 C __EDGCPFE__4_9
0000000000000000 T g
0000000000000000 D p
$ nm -C linkage.o
0000000000000008 T (anonymous namespace)::f()
0000000000000004 C __EDGCPFE__4_9
0000000000000000 T g
0000000000000000 D p
Run Code Online (Sandbox Code Playgroud)

因此,使用 usingextern "C"允许您给出名称外部链接,即使它出现在未命名的命名空间中,但这并不会使注释正确,因为您可以从其他翻译单元引用该名称,因为它不使用未命名的命名空间范围。这对我来说意味着该注释只是 C++03 的遗留物,当时未命名命名空间中的实体不会自动具有内部链接,并且该注释应该被更正或删除(事实上 TC 指出它已被DR 1603删除) )。

在哪些情况下,对未命名命名空间内的名称进行显式外部链接是有用的?

我想不出它有什么用处。