An inline function can be interpreted as static inline or extern inline. This question is important when local static variables are used in the function or when the address of such a function is used in a comparison.
Examples 1 and 2 at the end of this section illustrate the problem.
From the programmer’s viewpoint, a static inline function behaves in exactly the same way as a static function. The function itself and all the elements declared in the function are local to the current compilation unit. The function itself does not interact in any way with elements of other compilation units; these must be produced by other (external) functions.
An extern inline function behaves like a normal external function. The body of the function must occur in every compilation unit which uses it. It must, however, always be identical. The behavior can be described by means of a comparison: it is as if the body were the only definition in an additional compilation unit and this function were called in all the other compilation units.
In addition to the definition above, the optimizer is also informed that an inline expansion is desirable. However, the meaning must be retained when this optimzation is implmented.
The view of the C++ standard
If a function is declared with the keyword inline
, it is implicitly regarded as extern inline. A static inline must be declared with the keywords static
and inline
.
A member function which is defined within a class is implicitly regarded as extern inline.
Implementation in language mode C++ 2017 or C++ 2020
The language modes C++ 2017 and C++ 2020 support extern inline functions as required by the standard, see "extern inline functions".
Implementation in language modes Cfront C++ and C++ V3
In the language modes Cfront C++ and C++ V3 the compiler does not know the concept of an extern inline function. It interprets all inline functions as static inline.
This affects both the functions declared with the keyword inline
and also the member functions defined within a class.
The following sections apply only to the language modes Cfront-C ++ and C ++ V3.
The problematical constructs
In many cases it is irrelevant whether a function is interpreted as static inline or extern inline. To permit this, one of three constructs must be used. The situation is problematical only when the function is used in multiple compilation units.
The functions affected are those which are conceived as extern inline (see Example 3). These functions are almost always contained in a header file, together with a body. Templates or member functions can be concerned here.
In the case of templates it must be borne in mind that the C/C++ compiler includes other sources if this function is not disabled. In this context, see the options //MODIFY-SOURCE-PROPERTIES IMPLICIT-INCLUDE = and –K implicit_include
which are active in the default case (see Example 4).
Local static variables
In such a situation a local
static
variable exists several times over in the compiler implementation, while the standard requires a single occurrence. Whether this leads to a problem depends on the reason why the variable was defined as localstatic
(see EExample 5).
If a single occurrence is indeed expected, a problem exists. Whether this will result in runtime errors depends very much on the actual usage.
The situation is different if the reason is the lifetime of the memory. A great deal of code sets up a string internally in order to return it as the return value. Noauto
variable can be used here because the memory is released when the value is returned. However, a localstatic
remains valid until the function is called again. This is frequently used when texts are edited for output. In this case the variable is duplicated, but this has practically no effect on the program run.Comparison of addresses
An extern inline function has an unambiguous address, a static inline function one address per compilation unit. This is not relevant as long as the addresses are only used to call the function.
Things become critical when such addresses are compared with each other. However, this tends to happen only rarely. It would be conceivable when registering callback functions in which each function should only be entered once.String literal
This construct only returns a problem in exceptional cases. The standard does not define whether string literals with the same content also have the same address. It is also very unusual to use the address of a string literal in comparisons or similar functions.
Example 1: A problematical situation
// file bsp.h #include <stdio.h> class BSP { public: inline int mf1(void); }; inline int BSP::mf1(void) { static int i = 1; return i++; } extern void f(); // file bsp1.c #include “bsp.h” void f() { BSP bsp; printf (“Value 1: %d\n”, bsp.mf1() ); } // file bsp2.c #include “bsp.h” void g() { BSP bsp; printf (“Value 2: %d\n”, bsp.mf1() ); } int main() { f(); g(); return 0; }
Explanation
The function mf1
is critical, as is the variable i
which it contains. According to the standard this should only exist once. The two calls in bsp1.c
and bsp2.c
should therefore apply to the same variable. The required output is
Value 1: 1
Value 2: 2
However, in the implementation of the C/C++ compiler the function is processed separately in bsp1.c
and bsp2.c
. A different variable is addressed with the call in bsp2.c
from the call in bsp1.c
. Both variants are initialized with 1.
The output is then
Value 1: 1
Value 2: 1
Example 2: A problematical situation with templates
// file tmpl.h #include <stdio.h> template <class T> inline int tmpl(T t) { static int i = 1; return i++; } extern void f(); // file tmpl1.c #include “tmpl.h” void f() { printf (“Value 1: %d\n”, tmpl(5) ); } // file tmpl2.c #include “tmpl.h” void g() { printf (“Value 2: %d\n”, tmpl(7) ); } int main() { f(); g(); return 0; }
Explanation
The same notes apply as for Example 1, but a template is involved here rather than a member function. The output expected by the standard is
Value 1: 1
Value 2: 2
The output supplied by the C/C++ compiler is
Value 1: 1
Value 2: 1
Example 3: Functions affected
// file bf.h inline int f1(T) { } // affected int f2(T); // not affected static inline int f3(T) { } // not affected // as explicitly static template <class T> inline int tf1(T) { } // affected template <class T> int tf2(T); // not affected template <class T> static inline int tf3(T) { } // not affected // as explicitly static class BSP { public: inline int mf1(void); // affected int mf2(void) { } // affected int mf3(void); // not affected }; inline int BSP::mf1(void) { }
Example 4: Implicit include
// file ii.h template <class T> inline int ii(T); // file ii.c template <class T> inline int ii(T) // this function is affected, // although it is contained in a // .c file { static int i; return i++; }
Example 5: Problematical static variables
Usage is only presented as problematical/not problematical here. To become a real problem, the function must be a problematical inline function.
inline void no_recursion (void) { static int active = 0; // problematical active ++; if (active > 1) { illegal_recursion (); } else { do_something (); } active --; }
This is problematical because the recursion is not reliably detected.
inline char * debug_text () { static char buffer [200]; // not problematical sprintf (buffer, "...", ...); return buffer; }
This is not problematical because the content of the buffer is always used before the next call. There is normally nobody who saves and accepts the address, which means that its content changes reliably.
inline void f (void) { static int actions = 0; // (see below) actions++; if (actions > 200) { actions = 0; optimize_datastructures (); // Only cleaning up, no relevant // change } do_something (); }
Whether this is problematical depends very much on the optimize_datastructures
function. If it is really thae case that only optimization is being performed there, the program runs correctly despite actions being duplicated.
However, the performance is different because optimize_datastructures
is no longer called on a regular basis.