How Do Inline Variables Work

How do inline variables work?

The first sentence of the proposal:

The ​inline specifier can be applied to variables as well as to functions.

The ¹guaranteed effect of inline as applied to a function, is to allow the function to be defined identically, with external linkage, in multiple translation units. For the in-practice that means defining the function in a header, that can be included in multiple translation units. The proposal extends this possibility to variables.

So, in practical terms the (now accepted) proposal allows you to use the inline keyword to define an external linkage const namespace scope variable, or any static class data member, in a header file, so that the multiple definitions that result when that header is included in multiple translation units are OK with the linker – it just chooses one of them.

Up until and including C++14 the internal machinery for this has been there, in order to support static variables in class templates, but there was no convenient way to use that machinery. One had to resort to tricks like

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`.

From C++17 and onwards I believe one can write just

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

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

… in a header file.

The proposal includes the wording

​An inline static data member can be defined in the class definition and may s‌​pecify a ​brace­-or­-equal­-initializer. If the member is declared with the constexpr specifier, it may be redeclared in namespace scope with no initializer (this usage is deprecated; see‌​ D.X). Declarations of other static data members shall not specify a ​brace­-or­-equal­-in‌​itializer

… which allows the above to be further simplified to just

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

… as noted by T.C in a comment to this answer.

Also, the ​constexpr​ specifier implies  inline for static data members as well as functions.



Notes:

¹ For a function inline also has a hinting effect about optimization, that the compiler should prefer to replace calls of this function with direct substitution of the function's machine code. This hinting can be ignored.

Are inline variables unique across boundaries?

This is how I interpret the standard. According to basic.link/1:

A program consists of one or more translation units linked together.

It doesn't say anything about static linking nor dynamic linking. A program is translation units linked together. It doesn't matter if the linking is done in two steps (first create a .dll/.so, and then the dynamic linker links all dynamic libs + executable together).

So, in my interpretation, it doesn't matter whether a program is dynamically or statically linked, the implementation should behave the same: a class static variable should be unique (no matter whether it's inline or not).

On Linux, this is true.

On Windows, this doesn't work in all circumstances, so in my interpretation, it violates the standard in these circumstances (if you create a separate .dll, which contains the static, non-inline variable, and all other .dll's and the exe refers to this variable, it works).

Why is inline required on static inline variables?

Without inline, it's explicitly stated as only a declaration. As specified in [class.static.data]/2

The declaration of a non-inline static data member in its class
definition is not a definition
and may be of an incomplete type other
than cv void. The definition for a static data member that is not
defined inline in the class definition shall appear in a namespace
scope enclosing the member's class definition.

The rationale is most probably to keep legacy code intact and valid. Recall that we could initialize integral constants in the class definition itself since about forever. But odr-using them still required an out-of-class definition in some translation unit.

So to makes such variables implicitly inline could be problematic in existing codebases. The committee is always thinking about backwards compatibility when core language features are added.

For instance, consider this valid C++03 class definition:

struct foo {
static const int n = 3;
double bar[n];
};

n can be used as a constant expression to define the extent of bar, and it's not considered an odr-use. Nowadays we'd write it as constexpr1, however that above is still valid. But there may be cases were n would have to be odr-used (imagine its address taken, or a reference bound to it, etc). They are probably not many, and probably not common, but certain API's have crazy requirements that would end up necessitating this

const int foo::n;

to appear in some translation unit.

Now, if static inline int i = 8; was suddenly implicitly inline, the definition above (that is in an existing code base) would be an odr-violation. Now previously well-formed code, is ill-formed. So it's best to allow only explicit inline to take effect here, since only new code will actually have it.



1 One could argue that static constexpr variables may have the same issue (and yet they are implicitly inline). But IIRC their original wording allowed this change without potentially breaking existing code. It was essentially already "inline" by everything but name.

When are inline variables in static storage initialized?

See [basic.start.dynamic] p1:

Dynamic initialization of a non-local variable with static storage duration is unordered if the variable is an implicitly or explicitly instantiated specialization, is partially-ordered if the variable is an inline variable that is not an implicitly or explicitly instantiated specialization, and otherwise is ordered.

Therefore, the type of variable you are describing has "partially-ordered initialization". According to p2:

Dynamic initialization of non-local variables V and W with static storage duration are ordered as follows:

  • ...
  • If V has partially-ordered initialization, W does not have unordered initialization, and V is defined before W in every translation unit in which W is defined, then
    • if the program starts a thread (4.7) other than the main thread (6.6.1), the initialization of V strongly happens before the initialization of W;
    • otherwise, the initialization of V is sequenced before the initialization of W.
  • ...

So to summarize, assuming that there are no instantiated templates in the picture:

  • If you have two namespace-scope inline variables V and W such that V is defined before W in every translation unit, then V is initialized before W.
  • If only V is inline, and W is some non-inline namespace-scope variable defined in exactly one translation unit, V will be initialized before W as long as V's definition precedes W's in that one translation unit.
  • If the non-inline variable is defined before the inline variable, their initialization order cannot be guaranteed.

An intuitive way to think about initialization order is that, just as in C++14, the compiler initializes each translation unit in order, with the relative ordering of different translation units unspecified (and they can be interleaved with each other), but an inline variable with external linkage is considered to be initialized the first time the implementation "hits" one of its definitions which may be in any translation unit.

Also see p5:

It is implementation-defined whether the dynamic initialization of a non-local inline variable with static
storage duration is sequenced before the first statement of main or is deferred. If it is deferred, it strongly
happens before any non-initialization odr-use of that variable. It is implementation-defined in which threads
and at which points in the program such deferred dynamic initialization occurs.

semantics of inline variable [duplicate]

Normally functions and variables have to be defined (space allocated for them, either space for a variable or the code for a function body) exactly once. Inline functions relax that to allow multiple definitions (as the definitions are pulled into multiple modules through include files), all of which must be identical. If the function's actually referenced, rather than copied in-line, the multiple definitions will be merged by discarding all but one copy and making that copy the official one in the binary.

Inline variables do the equivalent thing for eg. static class members. Rather than having to create a class body source file whose only contents would be the definition of the single member variable, you would be able to define the static member variable in the class header file and the resulting multiple definitions (one in each module that included the class header) would be merged into a single instance in the binary without causing compiler or linker errors.

This paper discusses it in technical detail.

Can an 'inline' variable be inlined like inline functions?

both links fail to mention whether an inline variables are inline in sense that they could be inlined by the compiler just like inline functions.

Most likely that is because there is no reason to mention such a thing. You are basing your expectation on a false premise. The inline keyword has no bearing on whether or not a function can be inlined by the compiler. A compiler can choose to inline functions not marked inline, and it can choose to not inline functions marked inline. This has been the case from the beginning, even when the inline keyword was first introduced.

There was a time when the inline keyword was a hint to the compiler, but even then it was only a hint. The compiler was always free to make its own decisions about which functions to inline. Since that time, compilers have become better than programmers when it comes to deciding which functions are efficient to inline. So most modern compilers ignore the supposed hint (at full optimization).

that is replaced with the actual value on call site.

This cannot be done in general. A function call can be replaced by the function's content (its body) by the compiler because the compiler knows what the function's content is. In contrast, a variable's content (its value) is not known by the compiler. That is, after all, often the point of declaring a variable. Since the compiler does not know the "actual value", there is no way for the compiler to use that value as a replacement for fetching the value from the variable's location in memory.

This can be done, though, in the specific case where the compiler can deduce what the value of the variable will be. As with functions, it is up to the compiler to decide whether to use that value directly in the machine instructions or to retrieve that value from the variable's memory location. The use of keywords has no bearing on this decision (although at full optimizations, I would expect the value to be used as long as it fits in a register). The only keyword that is relevant is constexpr, which makes it much easier for the compiler to deduce what the value of the variable will be.



Related Topics



Leave a reply



Submit