The idea of inline is derived from C’s preprocessor macros, which, in turn, is derived from assembly language programming. The objective is to leverage the efficiency of a function without the overhead of a function call, such as placing data on a stack and retrieving the result. Preprocessor macros do not align well with the paradigm of object-oriented programming. This is the key reason why inline got imbibed into C++. Although the legacy of macros is very much alive within its paradigm, inline is a better alternative. This article delves into inline from the perspective how a compiler deals with its intricacies.
During the process of compilation, the declaration system maintains a set of data structures, collectively called the symbol table. In other words, it is a database that contains all the information about functions, their return values, variables’ names, variable types, and so forth. The processes of declaration logic are responsible for adding a record into the database and deleting it according to the scoping rules of the language. Also, the symbol table is a storehouse to hold the function type defined by its prototype, including its name, argument types, and function return value. Not only that, once the function is found to be error-free after parsing, the code for the function body is also brought into the symbol table.
Unlike any function, it is also true for inline as well that when a call to an inline function is made, the compiler first ensures the sanity of the call by checking all the argument types’ IDs to determine whether they are exactly same as a function’s argument list. Otherwise, the compiler must be able to do appropriate type conversion and get back with the correct type of return value.
As we can see, the compiler deals the inline in a similar fashion as if it’s non-different to other normal (non-inline) functions. But, this is where it is quite different from what the preprocessor macros does because the preprocessor cannot check types or make any type conversion.
This means that if all the function type information fits well with the context of inline function call, only the inline code is substituted directly at the place where it is called; otherwise, the compiler simply ignores the inline keyword and treats it as a normal function. So, in a way we can say that the inline keyword is a suggestion to the compiler whether the status is given to it or not is compiler’s prerogative.
Therefore, we can see that the only significant difference between an inline and a normal function in C++ is that inline code is substituted directly for the function call. Also, this is the only similarity between an inline and a macro. An inline eliminates the call overhead. The compiler has a scope for further optimization. In the case of an inline function, if it is a member of a class, the address of the object, or this pointer, is put in the appropriate places. This is not possible with preprocessor macros.
Limitations of Inline
There are situations where inlining is not possible. This only means that when we want a function to be treated as inline, we must be aware of a few things; otherwise, they will be ripped off the inline status even if we may have designated it with an explicit inline specifier. Firstly, the compiler cannot give inline status to a function which is complex. However, this is very specific to a compiler that decides the degree of complexity. But, the point is that there is no efficiency gained by inlining a complicated function. The degree of complexity is estimated from the code statements such as the use of loops in the code. Typically, if there are many lines of code and loop statements, it is obvious that it will take a lot of processing time and hence time complexity increases. It is not a very good idea to make such a function module inline because the possibility of code bloat is higher due to substitution on each inline call. Also, there is almost no improvement in performance, either. The compiler is smart enough to look into such issues to decide whether or not to allow a function to be inline.
Secondly, inlining is not possible if the function address is taken implicitly or explicitly. If that is so, it means that the compiler has to produce an address of the function, allocate storage for the code, and use the resulting address. Also, the compiler does not (at least not completely) inline a recursive function because it may lead to an infinite amount of compiled code.
The crux of the matter is that we must understand that the intent of the inline specifier is to provide a hint to the compiler and do some optimization. The compiler can and most often actually does ignore the inline specifier, whether specified implicitly to explicitly; it does not matter for the sake of optimization. This is in stark contrast with preprocessor macros which are treated as preprocessor macros in all its sense, of course, devoid of any possibilities of compiler optimization.