根据C++标准的第3.5/4条:
未命名的命名空间或直接或间接声明的命名空间 在未命名的命名空间中具有内部链接。
同时在第7.3.1.1段中,我们有注96(:
尽管未命名命名空间中的实体可能具有外部链接, 他们实际上通过翻译中独有的名称来限定 单位,因此从任何其他翻译单元中都看不到。
如何显式地为未命名命名空间内的名称进行外部链接,以及如何检查链接是否实际上是外部的,如果 Standard 保证无法从另一个翻译单元访问未命名命名空间内定义的名称?
在哪些情况下,对未命名命名空间中的名称进行显式外部链接很有用?
re
"> 在哪些情况下,对未命名命名空间内的名称进行显式外部链接很有用?
外部链接的必要性对于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++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"
,使链接 extern 但同时使命名空间与链接无关:C 语言没有它们。
namespace {
extern "C" void foo() {} // Extern linkage
} // namespace <anon>
void bar() {} // Also extern linkage, but visible to other TUs.
再
"> 如何检查联动是否确实是外部的
好吧,这种联系会影响与一个定义规则(通常缩写为">ODR"(的可能冲突,在第 11 C++ 中是 §3.2。
因此,查看链接的一种方法是链接从上述源代码生成的两个对象文件,就好像您有两个具有相同源代码的翻译单元一样:
C:\my\forums\so\088> g++ -c anon.cpp -o x.o & g++ -c anon.cpp -o y.oC:\my\forums\so\088> g++ main.cpp x.o y.oy.o:anon.cpp:(.text+0x0(:"foo"的多重定义x.o:anon.cpp:(.text+0x0(:首先在这里定义y.o:anon.cpp:(.text+0x7(:'bar((' 的多重定义x.o:anon.cpp:(.text+0x7(:首先在这里定义collect2.exe:错误:LD 返回 1 个退出状态C:\my\forums\so\088> _
链接器抱怨foo
的多个定义,因为与 C 语言绑定一样,就链接器而言,它显示为全局命名空间的非inline
外部链接成员,具有两个(可能冲突的(定义。
如何在未命名命名空间中显式创建名称的外部链接
我能想到的唯一方法是给它 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
应该有内部链接,但编译器似乎不是这样做的。
如果 Standard 保证无法从另一个翻译单元访问在未命名命名空间中定义的名称,如何检查链接实际上是外部的?
通常,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++标准的一部分,并且一些编译器即使在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 删除了(。
在哪些情况下,对未命名命名空间中的名称进行显式外部链接很有用?
我想不出任何有用的情况。
根据标准[3.5/2]:
当一个名字有外部链接时,它所表示的实体可以是 由其他翻译单位范围的名称或来自 同一翻译单元的其他范围
和
当一个名字有内部链接时,它所表示的实体可以是 由同一翻译单元中其他范围的名称引用。
所以基本上,如果你可以在翻译单元中引用与定义这个东西不同的翻译单元中的东西,那么它就有外部链接,否则它有内部链接。所以给出了问题中的注释:
我们尽管未命名命名空间中的实体可能具有外部链接, 他们实际上通过翻译中独有的名称来限定 单位,因此从任何其他翻译单元中都看不到。
实际上有一种情况,我们有一个名字,但我们不知道它,因此无论我们多么努力,我们都不能从不同的翻译单元引用它。它使它与具有内部链接的那个无法区分。所以,在我看来,这只是一个杂耍词——如果你无法区分一种情况和另一种情况,那么它们是一样的。