类有外部链接吗?

Posted

技术标签:

【中文标题】类有外部链接吗?【英文标题】:Do classes have external linkage? 【发布时间】:2011-06-24 08:37:09 【问题描述】:

我有 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 时,我得到了重新定义错误,这让我相信类具有内部链接。

我错过了什么吗?

【问题讨论】:

你是如何构建代码的? 你可能会发现this很有用 如果编译器两次看到同一个类,但定义完全相同,它不会抱怨。这种情况不会算作重新定义 @junjames:不正确。单一定义规则不关心内容。如果一个名称被定义了两次,则违反了规则,即使新定义与旧定义完全相同。 @DavidHammen 错了。请参阅 Praxeolitic 答案中的引用(后来补充说,IMO 在底部太远了,到 Alok 的) 【参考方案1】:

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

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]

如果满足以下条件,则翻译单元中只需要一个类的定义 该类的使用方式要求类类型为 完成。

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

【讨论】:

【参考方案2】:

正如 Maxim 指出的那样,从技术上讲,链接适用于符号,而不适用于 他们表示的实体。但是符号的链接是部分的 由它表示的内容决定:命名类的符号 命名空间范围有外部链接,w 表示同一个实体 在A.cppB.cpp 中。

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

【讨论】:

谢谢詹姆斯。我相信它回答了我的问题,即使修改了其中一个 .cpp 文件中 w 类的定义(仅引入了一个变量),以便两个定义中的标记序列不同,程序编译时也不会出错。 是的。这是未定义的行为;大多数实现不会检测到错误。但是,根据不同之处以及您使用类的方式,您可能会在运行时遇到意外或不愉快的行为。【参考方案3】:

声明

class w

public:
    w();
;

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

w::w()

  // object initialization goes here

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

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

    如果两个类相同,则仅将定义放入一个 .cpp 文件中。 如果您需要两个不同的、外部链接的 class w 实现,请将它们放入不同的 namespaces。 通过将定义放入匿名命名空间来避免外部链接。
namespace

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

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

    通过定义内联方法避免创建符号:
inline w::w()

  // object initialization goes here

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

【讨论】:

这个好像错了很多点。 (2) 好吧,当然,但是它们不会是相同的class w。 (3a) 但是如果你尝试过这个,你会意识到它不起作用,因为该类不是/必须也被声明在同一个匿名/未命名的命名空间中——根据定义,它会给它内部链接,从而击败点... (3b) 什么? static 类方法是允许的。 (4a) 我不认为inline 做你认为它做的事;具体来说,定义必须仍然相同,&inline 被定义为赋予具有外部链接的符号... (4b) LTO 可能可以删除代码重复 所以没有别的办法……比如……定义一个类……然后以某种方式导出它? 链接!=链接【参考方案4】:

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

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

程序实际上违反了One Definition Rule,但是编译器很难检测到错误,因为它们在不同的编译单元中。甚至链接器似乎也无法将其检测为错误。

C++ 允许通过使用 命名空间来绕过单一定义规则。

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

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

【讨论】:

谢谢大家。这意味着仅当同一翻译单元中有多个定义时,ODR 才适用于类。但这让我想知道为什么我们会对课程进行这种特殊处理。我的意思是我们总是可以通过使用命名空间的概念来选择类/符号的多个定义,但在这种情况下,我们绕过了 ODR 而不使用命名空间。 关于语句“..给定这样一个名为 D 的实体定义在多个翻译单元中,那么 D 的每个定义都应包含相同的标记序列..”我修改了在其中一个 .cpp 文件中定义 w 类(仅引入了一个变量),因此两个定义中的标记序列不同,但我仍然能够无错误地编译程序。 编译器无法检测到违规,因为正如您所注意到的,违规发生在单独的编译单元中。链接器无法检测到错误,因为定义的唯一符号是 A::w()(加上类 A 的免费默认构造函数、复制构造函数和析构函数),并且它们都是内联的。如果 A::w() 的定义被移到类外并且没有被 inline 限定,链接器会报错。 @Vivek:正如我所提到的,编译器很难检测到错误,因为它们位于不同的编译单元中。似乎是编译器不足以检测到的黑暗模糊边界线案例之一。 这个答案是错误的。 @Vivek每个翻译单元必须包含用作完整类型的每个类的定义,以便他们可以知道它在内存中的大小。不用作完整类型的类,例如翻译单元为其声明指向实例的指针的翻译单元不需要定义,因为翻译单元不需要知道内存中的大小。【参考方案5】:

类没有迂腐的联系。

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

【讨论】:

名字是有联系的。 C++ 标准中的“符号”没有特殊含义。【参考方案6】:

由于您不能在类上使用static,因此提供“类”静态链接的唯一方法是在anonymous namespace 中定义类型。否则,它将具有外部链接。我将类放在引号中,因为作为类型的类没有链接,而是指在类范围中定义的符号的链接(而不是使用类创建的对象的链接)。这包括静态成员和方法以及非静态方法,但不包括非静态成员,因为它们只是类类型定义的一部分,不会另外声明/定义实际符号。

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

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

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

【讨论】:

以上是关于类有外部链接吗?的主要内容,如果未能解决你的问题,请参考以下文章

微信公众平台加超链接能加外部的链接吗?

外部链接的链接样式

小程序外部链接怎么分享

scrapy仅遵循一个深度的外部链接

包含在 HTML 中的带有“img”标签的 SVG 可以链接到带有“image”标签的外部图像吗?

hbuilder mui 开发app, 打开外部链接,如何返回