首页 > 解决方案 > 对内联行为 C++ 的困惑

问题描述

我正在学习 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 中使用将 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++

解决方案


包含防护 with#ifndef/#define/#endif或 with#pragma once仅防止包含每个翻译单元。

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

#pragma once

void Do() {}

和两个*.cpp包含在内的文件。如果你现在编译

g++ source1.cpp source2.cpp 

你会得到一个

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

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

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

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

如果函数已定义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,它的定义必须在它被使用之前对每个翻译单元都是可见的!


推荐阅读