5

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

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

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

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

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

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

4

3 回答 3

4

回覆

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

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

template< void(*f)() >
void tfunc() { f(); }

#include <stdio.h>
static void g() { printf( "Hello!\n" ); }

int main()
{
    tfunc<g>();
}

它使用 C++11 编译器编译得很好。

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

在 C++11中,规则不仅改变了模板参数,而且改变了匿名命名空间中的事物是否具有外部链接。对于 C++03,匿名命名空间具有正式的外部链接,可能除非它本身位于匿名命名空间中(C++03 §3.5/4 最后破折号 + C++03 §7.3.1.1/1)。对于 C++11,匿名命名空间具有正式的内部链接。

这对链接器无关紧要,因为没有命名空间的链接,但它作为描述事物链接的正式设备很重要:

C++11 §3.5/4:

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


在继续你的其他问题之前,值得注意的是标准中的这句话,

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

是完全错误的,因为一个extern "C"实体在其他翻译单元中是可见的,无论它在哪个命名空间中声明。

令人高兴的是,我记得,注释是非规范性的,即它们不定义语言。


回覆

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

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

namespace {
    extern "C" void foo() {}    // Extern linkage
}  // namespace <anon>

void bar() {}  // Also extern linkage, but visible to other TUs.

回覆

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

好吧,链接会影响可能与单一定义规则发生冲突的事情,通常缩写为“ ODR ”,在 C++11 中是 §3.2。

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

C:\my\forums\so\088> g++ -c anon.cpp -o xo & g++ -c anon.cpp -o yo

C:\my\forums\so\088> g++ main.cpp xo yo
yo:anon.cpp:(.text+0x0): 'foo' 的多重定义
xo:anon.cpp:(.text+0x0): 首先定义在这里
yo:anon.cpp:(.text+0x7): `bar()' 的多重定义
xo:anon.cpp:(.text+0x7): 首先在这里定义
collect2.exe:错误:ld 返回 1 退出状态

C:\my\forums\so\088> _

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

于 2016-02-09T10:08:33.140 回答
3

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

我能想到的唯一方法是给它 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

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

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

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

$ g++ -c linkage.cc
$ nm linkage.o
0000000000000000 t _ZN12_GLOBAL__N_11fEv
0000000000000007 T g
0000000000000000 D p

未命名命名空间的重整名称可能因编译器而异,但拆解它会显示:

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

小写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

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

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

我想不出任何有用的情况。

于 2016-02-09T11:53:41.363 回答
1

根据标准[3.5/2]:

当一个名称具有外部链接时,它所表示的实体可以被其他翻译单元的范围或同一翻译单元的其他范围的名称引用。

当名称具有内部链接时,它所表示的实体可以由同一翻译单元中其他范围的名称引用。

因此,基本上,如果您可以在翻译单元中引用与已定义该内容的翻译单元不同的内容,则它具有外部链接,否则具有内部链接。因此,鉴于问题的注释:

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

我们实际上有一种情况,我们有一个名字但我们不知道它,因此无论我们多么努力,我们都无法从不同的翻译单元中引用它。它使它与具有内部链接的那个没有区别。所以,在我看来,这只是一个词杂耍——如果你不能区分一种情况和另一种情况,那么它们是一样的。

于 2016-02-09T11:03:33.637 回答