内联变量如何工作?


124

在2016年Oulu ISO C ++标准会议上,标准委员会将名为Inline Variables的提案投票选入C ++ 17。

用外行的话来说,什么是内联变量,它们如何工作以及它们有什么用?内联变量应如何声明,定义和使用?


@jotik我想等效的操作将用它的值替换任何出现的变量。通常,仅当变量为时才有效const
melpomene

5
这不是inline关键字对函数所做的唯一事情。inline当将关键字应用于函数时,它具有另一个至关重要的作用,它直接转换为变量。inline假定在头文件中声明的函数在链接时不会导致“重复符号”错误,即使头#include由多个转换单元获得d 也是如此。将该inline关键字应用于变量时,将具有完全相同的结果。结束。
Sam Varshavchik

4
^就“用该代码的原位副本替代对此函数的任何调用”而言,inline这只是对优化器的弱,无约束力的请求。编译器可以自由地不内联请求的函数和/或内联您未注释的函数。相反,inline关键字的实际目的是为了规避多个定义错误。
underscore_d

Answers:


121

提案的第一句话:

inline说明符可以被应用到变量以及给函数。

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

因此,实际上,(现在已接受)的提议允许您使用inline关键字在头文件中定义外部链接const名称空间范围变量或任何static类数据成员,以便在将头包含在其中时产生多个定义。链接器可以使用多个翻译单元-只需选择其中一个即可。

为了支持static类模板中的变量,直到C ++ 14为止(包括C ++ 14),内部机制一直存在,但是没有使用该机制的便捷方法。一个不得不诉诸于诸如

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!

…在头文件中。

该提案包括措词

内联静态数据成员可以在类定义来定义,并且可以小号pecify一支柱或-等于初始值设定。如果使用说明constexpr符声明了该成员,则可以在没有初始化程序的命名空间范围内对其进行重新声明(不建议使用此用法-参见DX)。其他静态数据成员的声明不应指定括号或等号的初始化器

……这使得上述内容可以进一步简化为

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

……正如TC在对此答案的评论中指出的那样。

此外,说明  ​constexpr符还暗示  inline 了静态数据成员和功能。


注意:
¹对于一个函数,它inline也具有优化的提示作用,编译器应首选直接替换该函数的机器代码来替换该函数的调用。此提示可以忽略。


2
同样,const限制仅适用于名称空间范围变量。类范围的(例如Kath::hi)不必是const。
TC

4
较新的报告表明该const限制已完全删除。
TC

2
@尼克:由于理查德·史密斯(现任C ++委员会“项目编辑”)是两位作者之一,而且由于他是“ Clang C ++前端的代码所有者”,Clang猜到了。并在Godbolt上使用clang 3.9.0进行了编译。它警告说内联变量是C ++ 1z扩展。我发现没有办法共享源代码和编译器的选择以及选项,因此该链接仅指向该网站,抱歉。
干杯和健康。-Alf

1
为什么在类/结构声明中需要内联关键字?为什么不简单地允许static std::string const hi = "Zzzzz...";呢?
sasha.sochka

2
@EmilianCioca:不,您会违反静态初始化顺序fiasco。单身本质上是避免这种情况的设备。
干杯和健康。-Alf

15

内联变量与内联函数非常相似。它向链接器发出信号,即使在多个编译单元中都可以看到该变量,该变量仅应存在一个实例。链接器需要确保不再创建更多副本。

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

例如,一种解决方法是将Meyer的单例与内联函数一起使用:

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

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

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

inline T global;

除了仅标头库外,在其他情况下,内联变量也可以提供帮助。尼尔·弗里德曼(Nir Friedman)在CppCon的演讲中谈到了这个话题:C ++开发人员应该了解有关全局变量(以及链接程序)的知识。有关内联变量和解决方法的部分从18m9s开始

简而言之,如果您需要声明在编译单元之间共享的全局变量,则在头文件中将它们声明为内联变量很简单,并且可以避免C ++ 17之前的变通方法带来的问题。

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


11

最小的可运行示例

这个很棒的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上游

另请参阅:内联变量如何工作?

内联变量的C ++标准

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

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

cppreference https://zh.cppreference.com/w/cpp/language/inline解释说,如果static未给出,则具有外部链接。

GCC内联变量实现

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

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 nmu

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

因此我们看到有专门的ELF扩展程序。

C ++ 17之前的版本: extern const

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

缺点inline是:

  • constexpr使用这种技术不可能创建变量,仅inline允许这样做:如何声明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上游

C ++ 17之前的标头仅替代方法

这些不如 extern解决方案,但是它们可以工作并且仅占用一个内存位置:

一个constexpr函数,因为constexpr隐含inlineinline 允许(强制)定义出现在每个翻译单元上

constexpr int shared_inline_constexpr() { return 42; }

我敢打赌,任何不错的编译器都会内联该调用。

您还可以使用constconstexpr静态整数变量,如下所示:

#include <iostream>

struct MyClass {
    static constexpr int i = 42;
};

int main() {
    std::cout << MyClass::i << std::endl;
    // undefined reference to `MyClass::i'
    //std::cout << &MyClass::i << std::endl;
}

但是您不能做诸如获取其地址之类的事情,否则它会变得奇怪,请参见:https : //en.cppreference.com/w/cpp/language/static “恒定静态成员”和定义constexpr静态数据成员

C

在C中,情况与C ++ pre C ++ 17相同,我在以下位置上载了一个示例:“静态”在C中是什么意思?

唯一的区别是在C ++中const隐含static全局变量,但在C中却不隐含:“静态常量”与“常量”的C ++语义

有什么办法可以完全内联吗?

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

就像预处理器一样。

这将需要某种方式:

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

有关:

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


2
inline尽管有单词本身,但与内联几乎无关,无论是函数还是变量。inline不告诉编译器内联任何东西。它告诉链接器确保只有一个定义,这一直是程序员的工作。那么,“有什么方法可以完全内联?” 至少是一个完全不相关的问题。
不是用户的用户,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.