内联变量如何工作?

jot*_*tik 106 c++ c++17

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

通俗地说,什么是内联变量,它们如何工作以及它们对什么有用?如何声明,定义和使用内联变量?

Che*_*Alf 108

提案的第一句话:

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

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

因此,实际上,(现在接受的)提议允许您使用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`.
Run Code Online (Sandbox Code Playgroud)

从C++ 17开始,我相信一个人可以写作

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

inline std::string const Kath::hi = "Zzzzz...";    // Simpler!
Run Code Online (Sandbox Code Playgroud)

...在头文件中.

该提案包括措辞

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

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

struct Kath
{
    static inline std::string const hi = "Zzzzz...";    // Simplest!
};
Run Code Online (Sandbox Code Playgroud)

......正如TC 对此答复的评论所指出的那样.

此外,说明  ?constexpr符暗示  inline 静态数据成员和函数.


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

  • [较新的报道](https://botondballo.wordpress.com/2016/07/06/trip-report-c-standards-meeting-in-oulu-june-2016/)表明`const`限制完全被删除. (4认同)
  • 为什么在类/结构声明中需要内联关键字?为什么不允许简单地`static std::string const hi = "Zzzzz...";`? (4认同)
  • 此外,const限制仅适用于命名空间范围变量.类范围的(如`Kath :: hi`)不必是const. (2认同)
  • @Nick:由于理查德史密斯(现任C++委员会"项目编辑")是两位作者之一,而且因为他是"Clang C++前端的代码所有者",猜猜Clang.在[Godbolt](http://gcc.godbolt.org/#)上使用clang 3.9.0编译的构造.它警告内联变量是C++ 1z扩展.我发现没有办法分享源代码和编译器的选择和选项,所以链接只是对网站一般,抱歉. (2认同)
  • @EmilianCioca:不,你会遇到[静态初始化命令惨败](https://isocpp.org/wiki/faq/ctors#static-init-order).单例本质上是一种避免这种情况的设备. (2认同)

Phi*_*ßen 12

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

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

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

inline T& instance()
{
  static T global;
  return global;
}
Run Code Online (Sandbox Code Playgroud)

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

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

inline T global;
Run Code Online (Sandbox Code Playgroud)

除了头文件库之外,还有其他内联变量可以帮助的情况.Nir Friedman在CppCon的演讲中介绍了这个主题:C++开发人员应该了解全局变量(以及链接器).关于内联变量和变通方法的部分从18m9开始.

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

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

  • 您能详细说明一下迈耶单例的性能问题吗? (2认同)
  • 啊,所以你正在谈论使神奇的静态初始化线程安全的开销,而不是使用模板滚动你自己的非线程安全版本(如果你不使用这些新引入的内联,你会如何去做呢?全局变量?)。感谢您的详细说明。 (2认同)

Cir*_*四事件 8

最小的可运行示例

强大的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);
}
Run Code Online (Sandbox Code Playgroud)

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

inline constexpr int notmain_i = 42;

const int* notmain_func();

#endif
Run Code Online (Sandbox Code Playgroud)

notmain.cpp

#include "notmain.hpp"

const int* notmain_func() {
    return &notmain_i;
}
Run Code Online (Sandbox Code Playgroud)

编译并运行:

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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

其中包含:

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
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

notmain.cpp

#include "notmain.hpp"

const int notmain_i = 42;

const int* notmain_func() {
    return &notmain_i;
}
Run Code Online (Sandbox Code Playgroud)

notmain.hpp

#ifndef NOTMAIN_HPP
#define NOTMAIN_HPP

extern const int notmain_i;

const int* notmain_func();

#endif
Run Code Online (Sandbox Code Playgroud)

GitHub上游

C ++ 17之前的标头仅替代品

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

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

constexpr int shared_inline_constexpr() { return 42; }
Run Code Online (Sandbox Code Playgroud)

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

您还可以使用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;
}
Run Code Online (Sandbox Code Playgroud)

但是您不能做诸如获取其地址之类的事情,否则它会变得奇怪,请参见: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中进行了测试。

  • “inline”与内联几乎没有任何关系,无论是对于函数还是变量,尽管这个词本身都是如此。`inline` 不会告诉编译器内联任何内容。它告诉链接器确保只有一个定义,这传统上是程序员的工作。那么“有什么办法可以完全内联它吗?” 至少是一个完全不相关的问题。 (4认同)