【问题标题】:Confusion over inline behavior C++对内联行为 C++ 的困惑
【发布时间】:2018-07-24 14:47:11
【问题描述】:

我正在学习 C++,但我对内联行为感到困惑。在cppreference 上,我发现“包含在多个源文件中的函数必须是内联的”。他们的例子如下:

// header file
#ifndef EXAMPLE_H
#define EXAMPLE_H
// function included in multiple source files must be inline
inline int sum(int a, int b) 
{
    return a + b;
}
#endif

// source file #2
#include "example.h"
int a()
{
    return sum(1, 2);
}

// source file #1
#include "example.h"
int b()
{
    return sum(3, 4);
}

这让我有点困惑——我认为 ifndef 守卫正是在做这项工作,即防止多次包含同一个文件时出现问题。无论如何,我想测试一下,所以我准备了以下内容:

// Sum.h
#ifndef SUM_H
#define SUM_H
int sum(int a, int b);
#endif

// Sum.cpp
int sum(int a, int b){
    return a + b;
}

// a.h
#ifndef A_H
#define A_H
int af();
#endif

// a.cpp
#include "sum.h"

int af(){
    return sum(3, 4);
}

// b.h
#ifndef B_H
#define B_H
int bf();
#endif 

// b.cpp
#include "sum.h"

int bf(){
    return sum(1, 2);
}

// main.cpp
#include "sum.h"
#include "a.h"
#include "b.h"
#include <iostream>
int main() {
    std::cout << af() + bf();
}

这可以正常工作。然后我在sum.cpp和sum.h中使用define sum函数内联,编译失败:

"sum(int, int)", referenced from:
      bf() in b.cpp.o
      af() in a.cpp.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

有人可以帮我澄清一下吗?

【问题讨论】:

  • 包含保护防止在一个源文件中多次包含相同的标头。它们不会以任何方式影响包含在多个单独源文件中的标头的行为。
  • 您的示例未在标头中定义函数。如果您需要有关错误的帮助,请显示失败的代码,而不是正常工作的代码。

标签: c++


【解决方案1】:

带有#ifndef/#define/#endif#pragma once 的包含保护仅防止包含每个翻译单元。

假设你有一个像这样的标题:

#pragma once

void Do() {}

还有两个 *.cpp 文件,它们包含在内。如果你现在编译

g++ source1.cpp source2.cpp 

你会得到一个

在xy行/文件中Do()的多重定义

每个源文件都将“单独”编译,因此从第一个翻译单元设置的第二个翻译单元看不到保护。两种翻译(编译)都是完全独立的。因此,在这种情况下,包含守卫不会保护任何东西。

为此,可以将函数定义定义为inline。现在这两个定义都将呈现给链接器,但标记为“弱”。链接器现在没有抱怨这两个定义,只接受其中一个(通常是最后一个!),这并不重要,因为在这种情况下两者都是相同的。

因此,包含保护的意义在于,您可以将一个文件多次包含到一个翻译单元中。这通常仅在您间接包含标头时才会发生。假设 a.h 具有函数定义,并且 b.h 和 c.h 都包含 a.h。如果您的 cpp 文件现在包含 b.h 和 c.h,它们都包含 a.h。所以你有多个定义,没有包含保护。这是包含守卫的用例。

如果函数已定义 inline 但在所有使用翻译单元中不可见,则会出现“未定义符号”的相反问题:

例子:

拥有该文件:

inline void sum() {}
void f1() { sum(); }

并使用-O0 编译生成,nm f1.o|c++filt 的输出

0000000000000000 T f1()
0000000000000000 W sum()

我们看到sum 的符号被定义为弱,因此它可以在链接阶段出现多次。如果没有“看到”定义的第二个翻译单元将毫无问题地链接,也将使用它。

但是使用“-O3”你会得到:

0000000000000000 T f1()

这是特定于编译器的实现!编译器可以提供内联函数。如果正在使用更高的优化级别,通常它们不会。

作为一个规则:如果一个函数被定义为inline,它的定义必须在它被使用之前对每个翻译单元都是可见的!

【讨论】:

  • 您关于链接器的陈述是有问题的,它可能在某些平台上以这种方式工作,但并非必须如此。内联函数可能不会为链接器生成任何函数
  • @Slava Inline 关键字与优化期间的内联函数无关!这是很久以前的情况。在当前标准中, inline 关键字允许对一个符号进行多个定义。如果优化器已经内联函数并且中间文件中不存在符号,则链接阶段不会出现错误,与 inline 关键字无关!
  • 对,但内联函数必须在另一个翻译单元中不可见,无论它是否通过优化内联。所以你关于链接器“允许”多个副本的说法并不完全正确。
  • @Slava:它与另一个翻译单元中的可见性无关。链接器会看到来自不同翻译单元的多个定义。抱歉,我没听懂你的意思!
  • 关于优化 - 对于内联函数(与常规不同)编译器不需要向链接器添加符号是否优化。
【解决方案2】:

然后我在sum.cpp和sum.h中定义sum函数内联,编译失败:

当函数被声明为inline 时,它的定义必须在所有使用该函数的翻译单元中都可用。仅在一个翻译单元中定义函数是不正确的。这就是您看到链接器错误的原因。

将函数的定义移动到 sum.h 以解决链接器错误。

【讨论】:

    【解决方案3】:

    您应该仔细阅读documentation,旁边的声明:

    包含在多个源文件中的函数必须是内联的

    它也这样说:

    2) 内联函数或变量的定义(C++17 起)必须存在于访问它的翻译单元中(不一定在访问点之前)。

    您在示例中违反了该错误。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-21
      • 1970-01-01
      相关资源
      最近更新 更多