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

7
根据C++标准3.5/4条款,未命名的命名空间或直接或间接声明在未命名命名空间中的命名空间具有内部链接。
同时,在7.3.1.1段落中,我们有注释96):
尽管未命名命名空间中的实体可能具有外部链接,但它们实际上是由翻译单元唯一的名称限定的,因此从任何其他翻译单元都无法看到它们。
如何显式地使未命名命名空间内的名称具有外部链接,并如何检查链接是否实际为外部链接,如果标准保证无法从另一个翻译单元访问在未命名命名空间中定义的名称?
在哪些情况下,为未命名命名空间中的名称进行显式外部链接是有用的?

1
什么?命名空间没有链接性。哦,至少在C++11中它有形式上的链接性。让我检查一下C++03。这是委员会引入的一些无意义的东西。哦,天啊,它在C++03中已经存在了。噢好吧。 - Cheers and hth. - Alf
我认为当C++11中未命名命名空间的链接发生变化时,应该删除注释。 - Jonathan Wakely
1
这个问题已经被 CWG issue 解决了。 - T.C.
非常抱歉我不得不逐步编辑我的答案以达到正确性。由于多个规则的更改(这些规则并不完全合理),我被迫这样做。如果有人能指出任何剩余的技术不准确之处,我将不胜感激。我知道这个答案虽然得到了赞同,但并不好,因为它并没有真正回答“何时有用”的问题。我想不出任何情况下会用到它? - Cheers and hth. - Alf
3个回答

5

在匿名命名空间内进行显式外部链接的情况有哪些优势呢?

对于 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变量或函数声明为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 x.o & g++ -c anon.cpp -o y.o

C:\my\forums\so\088> g++ main.cpp x.o y.o
y.o:anon.cpp:(.text+0x0): multiple definition of `foo'
x.o:anon.cpp:(.text+0x0): first defined here
y.o:anon.cpp:(.text+0x7): multiple definition of `bar()'
x.o:anon.cpp:(.text+0x7): first defined here
collect2.exe: error: ld returned 1 exit status
C:\my\forums\so\088> _

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


1
未命名命名空间内的非const变量具有内部链接而不是外部链接。 - ixSci
@ixSci:嗯,我会直接说你错了。而且,我已经隐含地说过了。 :) 例如,当要求以C++03编译时,g++也同意我的观点。你有任何证据支持这个说法吗? - Cheers and hth. - Alf
1
标准[3.5/4]:未命名的命名空间或直接或间接声明在未命名的命名空间中的命名空间具有内部链接。所有其他命名空间具有外部链接。如果具有命名空间范围的名称未在上面给出内部链接,则该名称与封闭命名空间具有相同的链接,如果它是变量的名称。 - ixSci
@ixSci:谢谢。规则从C++03到C++11发生了变化。显然,这种形式主义将使事情与C++11中模板的更改规则保持一致。我需要修正答案。 - Cheers and hth. - Alf
你的 namespace { void foo() {} } 示例具有内部链接,而不是外部链接。你可以使用 nm 来确认它,它将显示为小写的 t,表示文本部分中的本地符号。 - Jonathan Wakely
显示剩余2条评论

4

如何明确地为匿名命名空间内的名称进行外部链接

我想到的唯一方法是将其与 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

小写字母表示f具有局部可见性,这意味着它不能从其他目标文件链接。大写字母表示g具有外部链接。
然而,这并不是标准保证的,因为ELF可见性不是C++标准的一部分,一些编译器在ELF平台上即使没有使用可见性也实现了链接,例如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

因此,使用extern "C"可以使名称具有外部链接性,即使它出现在未命名的命名空间中,但这并不意味着注释是正确的,因为您可以从其他翻译单元引用该名称,因为它不使用未命名的命名空间作用域。这让我觉得这个注释只是C++03的剩余物,当时未命名命名空间中的实体没有自动具有内部链接性,应该更正或删除该注释(事实上,T.C.指出它已经被DR 1603删除)。

在哪些情况下,在未命名的命名空间中明确指定名称的外部链接性是有用的?

我无法想到任何有用的情况。


1
根据标准[3.5/2]:
当名称具有外部链接时,它所表示的实体可以由其他翻译单元的范围或同一翻译单元中的其他范围的名称引用。
并且
当名称具有内部链接时,它所表示的实体可以由同一翻译单元中其他范围的名称引用。
因此,基本上,如果您可以在与定义某些内容的翻译单元不同的翻译单元中引用该内容,则该内容具有外部链接;否则,它具有内部链接。因此,考虑到问题中的注释:
尽管未命名命名空间中的实体可能具有外部链接,但它们实际上是由其独特于其翻译单元的名称限定的,并且因此永远无法从任何其他翻译单元中看到。
我们实际上面临着一种情况,即我们有一个名称,但我们不知道它,因此无论我们如何努力,都无法从不同的翻译单元引用它。这使得它与具有内部链接的名称无法区分。因此,在我看来,这只是一个词汇游戏 - 如果您无法区分一种情况和另一种情况,则它们是相同的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接