同时,在7.3.1.1段落中,我们有注释96):
尽管未命名命名空间中的实体可能具有外部链接,但它们实际上是由翻译单元唯一的名称限定的,因此从任何其他翻译单元都无法看到它们。
如何显式地使未命名命名空间内的名称具有外部链接,并如何检查链接是否实际为外部链接,如果标准保证无法从另一个翻译单元访问在未命名命名空间中定义的名称?
在哪些情况下,为未命名命名空间中的名称进行显式外部链接是有用的?
在匿名命名空间内进行显式外部链接的情况有哪些优势呢?
对于 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
外部链接成员,具有两个(可能冲突的)定义。
namespace { void foo() {} }
示例具有内部链接,而不是外部链接。你可以使用 nm
来确认它,它将显示为小写的 t
,表示文本部分中的本地符号。 - Jonathan Wakely如何明确地为匿名命名空间内的名称进行外部链接
我想到的唯一方法是将其与 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
具有外部链接。$ 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删除)。
在哪些情况下,在未命名的命名空间中明确指定名称的外部链接性是有用的?
我无法想到任何有用的情况。