我想我有一个答案。我会写下一般的链接主题。
C 标准说:
在翻译单元集合中,每个声明都有一个特定的
带有外部链接的标识符表示相同的实体(对象或函数)。
在一个翻译单元中,标识符的每个声明都带有
内部链接表示同一实体。
C++ 标准说:
当名称具有外部链接时,它所表示的实体可以是
由来自其他翻译单元范围的名称或来自
同一翻译单元的其他范围。当一个名字有内部
链接,它表示的实体可以通过其他名称引用
同一个翻译单元中的作用域。
这有两个含义:
- 在翻译单元集合中,我们不能有多个不同的同名外部实体(C++ 中的重载函数除外),因此表示单个外部实体的每个声明的类型应该一致。我们可以检查类型是否在一个翻译单元内一致,这是在编译时完成的。我们无法在编译时和链接时检查不同翻译单元之间的类型是否一致。
从技术上讲,在 C++ 中,我们可以在没有函数重载的情况下违反 in the set of translation units we cannot have multiple distinct external entities with the same name 规则。由于 C++ 具有对类型信息进行编码的名称修饰,因此可以有多个具有相同名称和不同类型的外部实体。例如:
file-one.cpp:
int a; // C decorated name: _a
// C++ decorated name (VC++): ?a@@3HA
//------------------------------------------------
file-two.cpp:
float a; // C decorated name: _a
// C++ decorated name (VC++): ?a@@3MA
而在 C 中,这实际上是一个外部实体,第一单元中的代码会将其视为 int,而第二单元中的代码会将其视为 float。
- 在一个翻译单元中,我们不能有多个不同的同名内部实体(C++ 中的重载函数除外),因此表示单个内部实体的每个声明的类型应该一致。我们检查翻译单元内的类型是否一致,这是在编译时完成的。
现在我们将更接近这个问题。
C++ 标准说:
在块范围内声明的函数的名称和一个
由块范围外部声明声明的变量具有链接。如果
存在具有链接的实体的可见声明
相同的名称和类型,忽略在最里面声明的实体
封闭命名空间范围,块范围声明声明
相同的实体并接收前一个声明的链接。如果
有不止一个这样的匹配实体,程序是
格式不正确。否则,如果没有找到匹配的实体,则块范围
实体接收外部链接。
// C++
int a; // external linkage
void f()
{
extern float a; // external linkage
}
这里我们没有先前声明具有相同名称 (a) 和类型 (float) 的实体,因此 extern float a 的链接是外部的。由于我们在这个翻译单元中已经有带有外部链接的int a 并且名称相同,因此类型应该一致。在这种情况下他们没有,因此我们有编译时错误。
// C++
static int a; // internal linkage
void f()
{
extern float a; // external linkage
}
这里我们没有先前声明具有相同名称 (a) 和类型 (float) 的实体,因此 extern float a 的链接是外部的。这意味着我们必须在另一个翻译单元中定义float a。请注意,我们在一个翻译单元中具有具有外部和内部链接的相同标识符(我不知道为什么 C 会考虑这种未定义的行为,因为我们可以在不同的翻译单元中拥有同名的内部和外部实体)。
// C++ (example from standard)
static int a; // internal linkage
void f()
{
int a; // no linkage
{
extern int a; // external linkage
}
}
这里之前的声明int a 没有链接,所以extern int a 有外部链接。这意味着我们必须在另一个翻译单元中定义int a。
C 标准说:
对于使用存储类说明符 extern 声明的标识符
该标识符的先前声明可见的范围,31)
如果先前的声明指定了内部或外部链接,则
在后面的声明中标识符的链接与
在先前声明中指定的链接。如果没有事先声明
可见,或者如果先前的声明没有指定链接,则
标识符有外部链接。
所以我们可以看到在 C 中只考虑名称(不考虑类型)。
// C
int a; // external linkage
void f()
{
extern float a; // external linkage
}
这里之前声明的标识符a有外部链接,所以extern float a的链接是一样的(外部的)。由于我们在这个翻译单元中已经有了带有外部链接的int a,并且名称相同,因此类型应该一致。在这种情况下他们没有,因此我们有编译时错误。
// C
static int a; // internal linkage
void f()
{
extern float a; // internal linkage
}
这里之前声明的标识符a有内部链接,所以extern float a的链接是一样的(内部的)。由于我们在这个翻译单元中已经有了带有内部链接的static int a,并且名称相同,因此类型应该一致。在这种情况下他们没有,因此我们有编译时错误。而在 C++ 中,这段代码很好(我认为添加类型匹配要求时考虑了函数重载)。
// C
static int a; // internal linkage
void f()
{
int a; // no linkage
{
extern int a; // external linkage
}
}
这里之前声明的标识符a没有链接,所以extern int a有外部链接。这意味着我们必须在另一个翻译单元中定义int a。然而 GCC 决定以 variable previously declared 'static' redeclared 'extern' 错误拒绝此代码,可能是因为根据 C 标准我们有未定义的行为。