为内联函数定义多次,怎么可能?

时间:2018-11-29 16:27:58

标签: c++ compiler-errors linker inline

c ++入门书中的以下引用使我非常困惑

  

与其他函数不同,可以定义内联和constexpr函数   在程序中多次。毕竟,编译器需要   定义,而不仅仅是声明,以扩展代码。   但是,给定的内联或constexpr的所有定义都必须   完全匹配。结果,内联和constexpr功能正常   在标题中定义。    -C ++引物第五版,240 pp

“可能在程序中定义了多次” 这句话使我非常困惑。据我了解,声明可以进行多次,但是定义只需要一次。

有人可以给我举一个为什么有多个定义的例子。

3 个答案:

答案 0 :(得分:3)

在头文件(叫它foo.h)中,您可以拥有

inline int foo() { /* do stuff */ }

现在,如果在几个cpp文件中包含foo.h,则将在每个文件中定义foo,这将是一个多定义错误。由于foo被标记为inline,所以可以,因为所有定义都相同。

  

据我了解,声明可以多次进行,但定义只需一次

编译器在翻译单元(基本上是cpp文件)上工作,并且可以在其中进行各种优化,但函数内联和constexpr要求编译器知道函数的定义。这意味着每个翻译单元都需要在其中定义功能。我们使用inline可以做到这一点,否则将是多定义错误。

答案 1 :(得分:2)

我认为问题在于,“定义”可以指多种含义。当您在头文件中编写内联函数时,在源代码中只有一个具有该名称的函数的意义上,它只是“定义”一次。

但这不是编译器和链接器如何看待世界的方式。如果从 class XmlMove { private List<XElement> elements { get; set; } private int index = -1; public XmlMove(XDocument doc, string elementName) { elements = doc.Descendants(elementName).ToList(); index = 0; } public XElement GetNext() { if (index == -1 || index >= elements.Count - 1) return null; return elements[++index]; } public XElement GetPrevious() { if (index <= 0 ) return null; return elements[--index]; } public XElement GetCurrent() { if (index == -1) return null; return elements[index]; } } foo调用的头文件中有内联函数a.cpp,则该函数的完整编译版本将同时包含在b.cpp和{ {1}}。链接器仅选择要包含在最终二进制文件中的那些编译版本中的一个即可解决该问题。

请注意,我在这里掩盖了重要的细节,但这只是一般性的想法(也是我认为您的教科书所无法理解的。)

答案 2 :(得分:1)

作为一个例子。此版本无效。

// main.cpp
inline int square(int num) {
    return num * num;
}

inline int square(int num) {
    return num * num;
}

int main()
{
    return square(2);
}

https://godbolt.org/z/nlSbxg

但是当您将其包含在多个.cpp文件(也称为翻译单元)中时,这是可以的,因为它现在是链接器的工作,可以做正确的事情。

// b.cpp
inline int square(int num) {
    return num * num;
}

// main.cpp
inline int square(int num) {
    return num * num;
}

int main()
{
    return square(2);
}

内部版本:gcc main.cpp b.cpp #include的工作方式相同,它将代码全部放在这些.cpp文件中。

当然,如果函数的主体是内联的,则无需链接,所以没有问题:)

如果编译器决定执行脱机版本,则最终您将获得多个具有相同“内联”功能定义的目标文件(.o)。这样的定义将被标记。

借助该标记链接器,不会导致它发现了多个定义,而只是选择了找到的第一个定义。

因此,如果所有定义都完全相同,那就好!如果我们的职能不同,我们将陷入困境。文件b.cpp

中的示例
// b.cpp
inline int square(int num) {
    return 1;
}

同一内联函数具有多个不同的定义是未定义的行为。当然可以编译,但是我们得到了什么?这取决于链接器的选择:D