内联变量如何工作?

时间:2016-06-26 21:39:37

标签: c++ c++17

在2016年奥卢ISO C ++标准会议上,标准委员会将一项名为Inline Variables的提案投票给C ++ 17。

在外行人的术语中,什么是内联变量,它们如何工作以及它们对什么有用?如何声明,定义和使用内联变量?

3 个答案:

答案 0 :(得分:98)

提案的第一句话:

  

inline说明符可以应用于变量和函数。

应用于函数的inline的保证效果是允许在多个翻译单元中以外部链接相同地定义函数。对于实践,这意味着在标题中定义函数,可以包含在多个翻译单元中。该提案将这种可能性扩展到变量。

因此,实际上,(现在接受的)提议允许您使用inline关键字来定义外部链接const命名空间范围变量或任何static类数据成员,在头文件中,因此当多个转换单元中包含该头时产生的多个定义对于链接器是可以的 - 它只选择一个

直到并包括C ++ 14,为了支持类模板中的static变量,内部机制已经存在,但是没有方便的方法来使用该机制。一个人不得不采取像

这样的技巧
template< class Dummy >
struct Kath_
{
    static std::string const hi;
};

template< class Dummy >
std::string const Kath_<Dummy>::hi = "Zzzzz...";

using Kath = Kath_<void>;    // Allows you to write `Kath::hi`.

从C ++ 17开始,我相信人们可以只写

struct Kath
{
    static std::string const hi;
};

inline std::string const Kath::hi = "Zzzzz...";    // Simpler!

...在头文件中。

该提案包括措辞

  

可以在类定义中定义内联静态数据成员,并且可以指定大括号或等于初始化程序。如果使用constexpr说明符声明成员,则可以在没有初始化程序的命名空间作用域中重新声明该成员(不推荐使用此用法;请参阅D.X)。其他静态数据成员的声明不应指定大括号中的括号或等号

...允许将上述内容进一步简化为

struct Kath
{
    static inline std::string const hi = "Zzzzz...";    // Simplest!
};

......正如T.C在a comment中对此答案所述。

此外,​constexpr说明符对静态数据成员和函数隐含inline

注:
¹对于函数inline也有一个关于优化的提示效果,编译器应该更喜欢直接替换函数的机器代码来替换此函数的调用。这种暗示可以忽略不计。 功能

答案 1 :(得分:10)

内联变量与内联函数非常相似。它通知链接器只有一个变量实例应该存在,即使在多个编译单元中看到变量也是如此。链接器需要确保不再创建副本。

内联变量可用于在仅头文件库中定义全局变量。在C ++ 17之前,他们必须使用变通办法(内联函数或模板黑客)。

例如,一种解决方法是使用带有内联函数的Meyer's单例:

inline T& instance()
{
  static T global;
  return global;
}

这种方法存在一些缺点,主要是在性能方面。模板解决方案可以避免这种开销,但很容易弄错。

使用内联变量,您可以直接声明它(不会出现多重定义链接器错误):

inline T global;

除了标题库之外,还有其他内联变量可以提供帮助的情况。 Nir Friedman在CppCon的演讲中介绍了这个话题:What C++ developers should know about globals (and the linker)。有关内联变量和变通方法的部分starts at 18m9s

长话短说,如果你需要声明在编译单元之间共享的全局变量,在头文件中将它们声明为内联变量是很简单的,并避免了前C ++ 17解决方法的问题。

(例如,如果您明确希望进行延迟初始化,那么Meyer的单例仍有用例。)

答案 2 :(得分:4)

最小的可运行示例

这项令人敬畏的C ++ 17功能使我们能够:

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}

编译并运行:

g++ -c -o notmain.o -std=c++17 -Wall -Wextra -pedantic notmain.cpp
g++ -c -o main.o -std=c++17 -Wall -Wextra -pedantic main.cpp
g++ -o main -std=c++17 -Wall -Wextra -pedantic main.o notmain.o
./main

GitHub upstream

另请参阅:How do inline variables work?

内联变量的C ++标准

C ++标准保证地址相同。 C++17 N4659 standard draft 10.1.6“内联说明符”:

  

6具有外部链接的内联函数或变量在所有翻译单元中应具有相同的地址。

cppreference https://en.cppreference.com/w/cpp/language/inline解释说,如果未提供static,则它具有外部链接。

内联变量实现

我们可以观察到它是如何实现的:

nm main.o notmain.o

其中包含:

main.o:
                 U _GLOBAL_OFFSET_TABLE_
                 U _Z12notmain_funcv
0000000000000028 r _ZZ4mainE19__PRETTY_FUNCTION__
                 U __assert_fail
0000000000000000 T main
0000000000000000 u notmain_i

notmain.o:
0000000000000000 T _Z12notmain_funcv
0000000000000000 u notmain_i

man nm说说u

  

“ u”该符号是唯一的全局符号。这是对ELF符号绑定的标准集合的GNU扩展。对于这样的符号,动态链接器将确保在整个过程中                  只有一个使用此名称和类型的符号。

所以我们看到有一个专用的ELF扩展。

C ++之前的17:extern const

在C ++ 17之前和C中,我们可以使用extern const达到非常相似的效果,这将导致使用单个内存位置。

inline的缺点是:

  • 使用这种技术无法创建变量constexpr,只有inline允许:How to declare constexpr extern?
  • 它不太优雅,因为您必须在头文件和cpp文件中分别声明和定义变量

main.cpp

#include <cassert>

#include "notmain.hpp"

int main() {
    // Both files see the same memory address.
    assert(&notmain_i == notmain_func());
    assert(notmain_i == 42);
}

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif

GitHub upstream

可以完全内联吗?

TODO:有什么方法可以完全内联变量,而无需使用任何内存?

非常类似于预处理器。

这需要某种方式:

  • 禁止或检测变量地址是否被占用
  • 将该信息添加到ELF对象文件中,然后让LTO对其进行优化

相关:

在Ubuntu 18.10,GCC 8.2.0中进行了测试。