2021-03-14

20: C++ 'inline': Unreliable Behavior Like Ignored Modifications

<The previous article in this series | The table of contents of this series | The next article in this series>

C++ 'inline' is abhorrent, not because it does not guarantee to really do inlining, but because it does not even pretend to be doing inlining.

Topics


About: C++

The table of contents of this article


Starting Context



Target Context


  • The reader will know what 'inline' may do and its consequences.

Orientation


There is an article that addresses what C++ 'template' really is and how to use it, including inlining.

There is an article that expounds on the intent and the usage of header files in C++.


Main Body

Stage Direction
Hypothesizer 7 soliloquies.


1: Abhorrent 'inline': an Example in Which a Modification Is Ignored


Hypothesizer 7
I have a project which has an 'inline' function, like this.

theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.hpp

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_inlineTest1_ClassA_hpp__
#define __theBiasPlanet_coreUtilitiesTests_inlineTest1_ClassA_hpp__

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace inlineTest1 {
			class ClassA {
				private:
					int i_memberVariableA0;
				public:
				    inline ClassA ();
					inline int inlineMethodA0 ();
			};
		}
	}
}

#endif

theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.ipp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.hpp"
#include <iostream>

using namespace ::std;

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace inlineTest1 {
			inline ClassA::ClassA () {
			}
			
			inline int ClassA::inlineMethodA0 () {
				cout << "### ClassA::inlineMethodA0 version: 1" << endl << flush;
				return i_memberVariableA0;
			}
		}
	}
}

Note that it is '.ipp', because the file is not any source file to be compiled but is a file to be included (but "i" in ".ipp" is for 'inline', not for 'included').

Let me create a new source file that calls the 'inline' function, but before that, I modify the implementation of the 'inline' function.

theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.ipp

@C++ Source Code
			~
			inline int ClassA::inlineMethodA0 () {
				cout << "### ClassA::inlineMethodA0 version: 2" << endl << flush;
				return i_memberVariableA0;
			}
			~

theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassC.hpp

@C++ Source Code
#ifndef __theBiasPlanet_coreUtilitiesTests_inlineTest1_ClassC_hpp__
#define __theBiasPlanet_coreUtilitiesTests_inlineTest1_ClassC_hpp__

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace inlineTest1 {
			class ClassC {
				private:
					int i_memberVariableC0;
				public:
				    ClassC ();
					int methodC0 ();
			};
		}
	}
}

#endif

theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassC.cpp

@C++ Source Code
#include "theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassC.hpp"
#include <iostream>
#include "theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.ipp"

using namespace ::std;

namespace theBiasPlanet {
	namespace coreUtilitiesTests {
		namespace inlineTest1 {
			ClassC::ClassC () {
			}
			
			int ClassC::methodC0 () {
				ClassA l_classA;
				return l_classA.inlineMethodA0 ();
			}
		}
	}
}

I compile (only) the new source file, do linking to have a program, and execute the program, but the function call executes the old implementation, showing "ClassA::inlineMethodA0 version: 1".


2: What 'inline' Is Supposed to Do


Hypothesizer 7
'inline' is supposed to replace the function call with the body of the function, which I call 'really inlining'.

Any really 'inline' function is a blueprint, not any object, which means that it does not exist as any entity in any object file.


3: 'inline' Probably Will Not Do What It Is Supposed to Do, but That Is Not the Problem


Hypothesizer 7
It is not just that 'inline' does not guarantee to do what is supposed to do, but probably, it will not do what is supposed to do.

In fact, for all of my simple test cases, my GCC and Visual C++ never really have done inlining.

That itself is not particularly bad, if it is a matter that can be dismissed just as an optimization issue.

In fact, if the compiler judges that not really inlining is better performance-wise, I have no intention to complain about that.

But I insist that such optimization should be done unobtrusively to the programmer.

What does that mean? Well, the behavior of the program should not change just because the compiler made an optimization decision.

In other words, the function at least should outwardly look as though it is 'inline', even if it really is not.

That is what I require from any optimization.

Is that an excessive request? I do not think so.


4: What 'inline' Probably Will Do: Creating Doppelgängers


Hypothesizer 7
The calls of an 'inline' function in a source file probably will create a doppelgänger in the object file.

What does that mean? If the function is not really inlined, the calls have to remain function calls. . . . Calls to what? Of course, to a function object. But where is the function object? . . . As the function implementation is included in the source file of the function calls (via including the '.ipp' file), the compiler will create a function object in the object file of the source file. . . . But what if another source file calls the same 'inline' function? . . . The compiler will create another function object of the same function in the object file. . . . So, multiple function objects of the same function (doppelgängers) are scattered in multiple object files.

There are some people who think that 'inline' is just a hint that may be just ignored by the compiler, that is not so: even if function calls are not really inlined, 'inline' will have the effect that makes function objects doppelgängers (if such function objects did not become doppelgängers, a duplicate-object link error would occur).


5: Decisions of the Compiler Are Being Obtrusive to the Programmer, Which Is the Problem


The problem is that each of such doppelgängers does not become object-file-local.

Huh? Are such multiple doppelgängers visible globally? Yes, they are.

Is not a function call guaranteed to call the local doppelgänger? No, it is not.

Then, which doppelgänger is called for each call? . . . The one in the object file that is the first in the linking order among the competing object files.

The problem is that the decision by the compiler of whether the 'inline' function is really inlined obtrusively changes the behavior of the program: if the function is really inlined, the implementation of the compile time of the source file will be used, while if not, another implementation may be used.

My insistence is that if a function is declared to be 'inline', it has to at least behave like being 'inline', and the present behavior is not so.


6: Why and How Does the Above Example Exhibit Unreliable Behavior


Hypothesizer 7
Now I understand why the modification of the 'inline' function in the above example is ignored in the new function call.

1st, obviously, the function call is not really inlined.

2nd, the modification is certainly reflected in the new doppelgänger, but the doppelgänger is not used because there is a competing object file that is precedent in the linking order.

If the new object file is moved to be the 1st in the linking order among the competing object files, all the calls to the function will be to the new doppelgänger.

Or if an object file is eliminated from the project, all the calls to the function may unexpectedly change the behavior.

Or if the compiler secretly changes its mind and starts really inlining, the behavior will suddenly change.


7: Forcing GCC to Really Do Inlining


Hypothesizer 7
In fact, GCC can be forced to really do inlining, like this.

theBiasPlanet/coreUtilitiesTests/inlineTest1/ClassA.hpp

@C++ Source Code
					~
					inline int inlineMethodA0 () __attribute__((always_inline));
					~

As I have changed the above example code so, the program have changed the behavior to look as though the function is really inlined (in fact, it IS really inlined).


References


<The previous article in this series | The table of contents of this series | The next article in this series>