12

我有 2 个文件 A.cpp 和 B.cpp 看起来像

A.cpp
----------
class w
{
public:
    w();
};


B.cpp
-----------
class w
{
public:
    w();
};

现在我在某处(https://en.cppreference.com/w/cpp/language/static)读到类具有外部链接。因此,在构建时,我期待一个多重定义错误,但相反它就像魅力一样。但是,当我在 A.cpp 中定义类 w 时,我得到了重新定义错误,这让我相信类具有内部链接。

我在这里错过了什么吗?

4

6 回答 6

23

正确答案是肯定的,一个类的名字可能有外部链接。以前的答案是错误的和误导性的。您显示的代码是合法且常见的。

C++03 中的类名可以有外部链接,也可以没有链接。在 C++11 中,类的名称可能还具有内部链接。

C++03

§3.5 [basic.link]

当一个名称可能表示与另一个范围内的声明引入的名称相同的对象、引用、函数、类型、模板、命名空间或值时,就称其具有链接

类名可以有外部链接。

具有命名空间范围的名称如果是

[...]

— 命名类(第 9 条),或在 typedef 声明中定义的未命名类,其中该类具有用于链接目的的 typedef 名称(7.1.3)

类名不能有链接。

这些规则未涵盖的名称没有联系。此外,除非另有说明,在本地范围 (3.3.2) 中声明的名称没有链接。没有链接的名称(特别是在本地范围 (3.3.2) 中声明的类或枚举的名称)不得用于声明具有链接的实体。

在 C++11 中,命名空间范围内的第一个引号更改和类名现在可能具有外部或内部链接。

未命名的命名空间或在未命名的命名空间中直接或间接声明的命名空间具有内部链接。所有其他命名空间都有外部链接。具有命名空间范围的名称没有在 [类名没有] 上方给出内部链接,如果它是

[...]

— 命名类(第 9 条),或在 typedef 声明中定义的未命名类,其中该类具有用于链接目的的 typedef 名称(7.1.3);

第二个引用也发生了变化,但结论是一样的,类名可能没有联系。

这些规则未涵盖的名称没有联系。此外,除非另有说明,在块范围 (3.3.3) 中声明的名称没有链接。当且仅当:

— 它是一个类或枚举类型,被命名(或具有用于链接目的的名称(7.1.3))并且该名称具有链接;或者

— 它是一个未命名的类或具有链接的类的枚举成员;

这里的一些答案将 C++ 标准中链接的抽象概念与称为链接器的计算机程序混为一谈。C++ 标准没有赋予单词符号特殊的含义。符号是链接器在将目标文件组合成可执行文件时解析的内容。形式上,这与 C++ 标准中的链接概念无关。该文档仅在有关字符编码的脚注中处理链接器。

最后,您的示例是合法的 C++ 并且不是 ODR 违规。考虑以下。

C.h
----------
class w
{
public:
    w();
};


A.cpp
-----------
#include "C.h"


B.cpp
-----------
#include "C.h"

也许这看起来很熟悉。在评估预处理器指令后,我们留下了原始示例。Alok Save 提供的 Wikipedia 链接甚至将其声明为一个例外。

有些东西,比如类型、模板和外部内联函数,可以在多个翻译单元中定义。对于给定的实体,每个定义必须相同。

ODR 规则将内容考虑在内。实际上,您显示的内容是翻译单元将类用作完整类型所必需的。

§3.5 [basic.def.odr]

如果以要求类类型完整的方式使用类,则翻译单元中需要类的确切定义。

编辑 - James Kanze 回答的后半部分是正确的。

于 2014-09-26T04:32:23.543 回答
7

正如 Maxim 所指出的,从技术上讲,链接适用于符号,而不是它们表示的实体。但是符号的链接部分取决于它所表示的内容:在命名空间范围内定义的名称类的符号具有外部链接,并且在和w中表示相同的实体。A.cppB.cpp

C++ 有两组关于实体定义的不同规则:一些实体,如函数或变量,在整个程序中只能定义一次。多次定义它们将导致未定义的行为;大多数实现(无论如何,大多数时候)都会给出多重定义错误,但这不是必需的或保证的。其他实体,例如类或模板,需要在每个使用它们的翻译单元中定义,进一步要求每个定义都是相同的:相同的标记序列,以及绑定到相同实体的所有符号,具有非常有限的常量表达式中的符号例外,前提是从不使用地址。违反这些要求也是未定义的行为,但在这种情况下,大多数系统甚至不会发出警告。

于 2011-06-24T09:12:58.273 回答
1

声明

class w
{
public:
    w();
};

不会产生任何代码或符号,因此没有任何东西可以链接并具有“链接”。然而,当你的构造函数 w() 被定义...

w::w()
{
  // object initialization goes here
}

它将具有外部联系。如果在 A.cpp 和 B.cpp 中都定义,会出现名称冲突;然后会发生什么取决于您的链接器。例如,MSVC 链接器将以错误 LNK2005“已定义函数”和/或 LNK1169“找到一个或多个多重定义符号”而终止。GNU g++ 链接器的行为类似。(对于重复的模板方法,它们将消除除一个之外的所有实例;GCC 文档将其称为“ Borland模型”)。

有四种方法可以解决这个问题:

  1. 如果两个类相同,则仅将定义放入一个 .cpp 文件中。
  2. 如果您需要两个不同的、外部链接的实现class w,请将它们放入不同的命名空间中。
  3. 通过将定义放入匿名名称空间来避免外部链接。
namespace
{
  w::w()
  {
    // object initialization goes here
  }
}

匿名命名空间中的每一个都具有内部链接,因此您也可以将其用作static声明的替代品(这对于类方法是不可能的)。

  1. 通过定义内联方法来避免创建符号:
inline w::w()
{
  // object initialization goes here
}

No 4 仅在您的类没有静态字段(类变量)时才有效,并且它将为每个函数调用复制内联方法的代码。

于 2015-04-01T02:41:35.883 回答
0

外部链接意味着符号(函数或全局变量)可以在整个程序中访问,内部链接意味着它只能在一个翻译单元中访问。您可以使用 extern 和 static 关键字显式控制符号的链接,并且对于非常量符号默认链接是 extern,对于 const 符号默认链接是静态(内部)。

具有外部链接的名称表示可以通过在同一范围或同一翻译单元的其他范围中声明的名称引用的实体(就像内部链接一样),或者另外在其他翻译单元中。

该程序实际上违反了单一定义规则,但编译器很难检测到错误,因为它们位于不同的编译单元中。甚至链接器似乎也无法将其检测为错误。

C++ 允许通过使用命名空间绕过单一定义规则的解决方法

[更新]来自 C++03 Standard
§ 3.2 一个定义规则,第 5 节指出:

如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则程序中可以有多个类类型的定义...... 给定这样一个名为 D 的实体在多个翻译单元中定义,那么 D 的每个定义都应包含相同的标记序列。

于 2011-06-24T08:43:34.190 回答
0

类没有迂腐的联系。

联动只适用于symbols,即函数和变量,或者代码和数据。

于 2011-06-24T08:45:24.567 回答
0

看到你不能static在一个类上使用,给出一个“类”静态链接的唯一方法是在匿名命名空间中定义类型。否则,它将具有外部链接。我将类放在引号中,因为作为类型的类没有链接,而是指在类范围中定义的符号的链接(但不是使用类创建的对象的链接)。这包括静态成员和方法以及非静态方法,但不包括非静态成员,因为它们只是类类型定义的一部分,并且不会另外声明/定义实际符号。

具有静态链接的“类”意味着具有外部链接或外部 comdat 链接的成员和方法现在都只有静态链接——它们现在是本地符号,尽管在编译器级别的效果inline仍然适用(即如果在翻译单元中没有引用它,则不会发出符号)——它不再是汇编程序级别的外部 comdat 符号,而是本地符号。即使类的成员或方法是离线定义的并且在匿名命名空间之外,情况也是如此,它仍然具有静态链接。

如果您在匿名命名空间中声明类类型,您将无法在匿名命名空间之外定义该类型,并且它将无法编译。您需要在翻译单元中的同一个匿名命名空间或不同的匿名命名空间中定义它(不同的匿名命名空间无关紧要,因为它们都组合成同一个匿名匿名命名空间名称_GLOBAL__N_1)。

这是更改类/结构的成员或方法的链接的唯一方法,因为static它将使其成为静态成员并且不会更改链接,static将在超出范围的定义中被忽略,并且extern不允许在类成员/函数中使用.

于 2021-03-30T22:43:47.870 回答