Your Browser is not longer supported

Please use Google Chrome, Mozilla Firefox or Microsoft Edge to view the page correctly
Loading...

{{viewport.spaceProperty.prod}}

extern inline vs. static inline

&pagelevel(4)&pagelevel

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 local static (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. No auto variable can be used here because the memory is released when the value is returned. However, a local static 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.