Function-like Macros vs. Inline Functions

A write up that tries to highlight some of the pros and cons of both these exceptional features provided in the object oriented arena of C++.

Introduction

“The question of whether computers can think is like the question of whether submarines can swim.” - Edsger Dijkstra

Computers or rather compilers, for us software folks, cannot think totally on its own. The obligation is thus on us, to furnish the best information to the compilers to derive the best out of them. A common instance where we tend to misfire is in the use of Function-like Macros and Inline Functions. This write up is to try and highlight some of the pros and cons of both these exceptional features provided in the object oriented arena of C++.

Though most of us are very much aware of what function-like macros and inline functions are, let us revisit the same to brush up our thoughts.

Function-like Macros

Macros are most frequently used to define names for constants that occur repeatedly in a program. The more preferred way of defining these constants is by using the keyword const. Since the current focus is not on which is the best way to define constants, let us leave it alone. The #define directive is quite powerful and hence allows the macro name to have arguments and thus behave like a function. Such forms of macros are referred to as function-like macros. Each time the macro name is encountered with arguments, the arguments used in its definition are replaced by the actual arguments found.

On compilation of the program, the "a" and "b" in the macro definition will be replaced by 10 and 20 respectively. Though this looks like a piece of cake, it has its own potholes which we will visit in the later sections.

Inline Functions

Inline functions are an imperative feature of C++ and are frequently used with classes. These are no different from the normal functions except for the fact that they are never actually called. These functions are, as their name suggests, expanded in line at each point of invocation. All that needs to be done to enable or cause a function to expand at the point of invocation is to add the inline keyword before the function definition.

This seems so similar to the function-like macros, isn’t it? Hold on till we get into the depth.

Function-like Macros vs. Inline Functions

Macros are more often used to define constants that can be effectively used instead of const or non const variables. Since the macros are interpreted at compile time, they gain the advantage of being non replaceable.

Function-like macros pack more surprises than one can imagine and have to be very careful.

Function-like macros are immensely beneficial when the same block of code needs to be executed umpteen numbers of times. For example, suppose one needs to make entries to a log file, function-like macros will be of great help.

WriteToFile is a function. Now, one can use this macro to write the details in the log file along with a message, file name and line number as depicted below:

int main()
{
LOG("Inside Main");
return0;
}

Though not wanting to sound biased, one cannot help but view function-like macros with some wariness in spite of the great deal of advantages that they present. Function-like macros when used always expand at the point of usage. The function-like macros are pre-processed at compile time, and as a result, there is really no macro existing at runtime. This is why there is an increase in the size of the binary files.

Yet another major setback of the function-like macros is the lack of type checking leading to a high level of type insecurity. The arguments passed on to a macro are never preprocessed. Looks totally harmless, doesn’t it?

This can cause a whole lot of chaos.

Consider the example below which depicts the disadvantage of the lack of type checking. In this example, the result is always the value passed in as the second argument.

A similar combination which could tamper the result is that of the parenthesis used within the function-like macros. Remember the very first example that was used to depict the use of a macro. Now, when we remove the enclosing braces of the statement, the output of the macro goes completely berserk. It is always going to give the result of the conditional statement.

Function-like macros need additional handling of arguments that have side effects. Expressions supplied as arguments may not always be evaluated before entering the body of the function-like macros. The increment or decrement operators when passed as arguments would behave as depicted in the subsequent example.

Function-like macros, notwithstanding the shortcomings, still have their own benefits, and one cannot totally term as hazardous. This is well augmented by the extensive use of function-like macros in MFC.

The point where inline functions primarily score over the function-like macros is perhaps the feasibility to step through the code. This perhaps is the one single motivation that causes developers to be biased if at all towards inline functions.

Inline functions when used effectively increase the performance but then nothing comes for free. The enhanced performance will be at the cost of increased compile time and possibly size. Inline is nothing but a request to the compiler. The onus shifts to the compiler which decides on the optimization once the request has been issued. The compiler would then decide upon the optimization. So, the focus when using inline functions should be on the cost and the benefits, since these are directly coupled with the expansion.

Inline functions, though declared that way, are not always inlined. It is not always necessary to declare a function as inline. Any function whose body is defined within the class body will be implicitly treated as inline. But there is a catch to this. The implicit treatment depends on the complexity and length of the function. Remember who is the boss when it comes to inline functions? Inline is just a request and it is the compiler who is vested with the authority to accept or reject the inline request.

In the ensuing example, there are two functions of which one has been explicitly declared as inline while the other isn’t. In this case, both are treated by the compiler as inline since their definition is within the class body.

The inline directive will be totally of no use when used for functions which are:

recursive

long

composed of loops

In a class, generally the constructor, copy constructor, destructor, and assignment operator overloading are all inline by default.

Inline functions are always on the verge. They might be good or bad, really bad. Let us see why.

Inline functions might make the process smaller and/or faster. The compiler often generates a lot of low level code to push and pop registers or parameters. This generally is more than the code required for inline-expanding the function's body. This happens with all functions, irrespective of the size. The optimizer is able to remove a lot of redundant code through procedural integration - that is, when the optimizer is able to make the large function small. This procedural integration will remove a bunch of unnecessary instructions, which might make the whole process run faster.

Inline functions might make the process larger and/or slower. A process with a number of inline functions will end up having a large code size. This might cause "thrashing” - a computer activity that makes little or no progress, as a result of memory or other resources becoming exhausted or too limited to perform needed operations - on demand-paged virtual-memory systems. To put it in other words, an increase in the executable size may result in more disk operation as the system will end up spending most of its time fetching the next chunk of code from the disk.

For example, if a system has 100 inline functions, each of this expands to 100 bytes of executable code and is called in 100 places, that’s an increase of 1MB. Is that 1MB going to cause problems? Who knows, but it is possible that, that last 1MB could cause the system to "thrash," and that could slow things down.

Contradictorily, inline functions while leading to thrashing may reduce the page faults. The working set size - number of pages that need to be in memory at once - might go down even if the executable size goes up.

When the InnerFunction is called within the OuterFunction, the code is often on two distinct pages; when the compiler procedurally integrates the code of the InnerFunction into the OuterFunction, the code is often on the same page. This will reduce the possibility of any page faults.

In today’s scenarios, a majority of the systems developed are not CPU-bound but inadvertently either I/O-bound, database-bound, or network-bound. This means that overall performance of the system is more dependent on the file system, the database, or the network, thus making them the bottleneck. This renders the inline function effectively insignificant unless they are used within the bottleneck. They may not be of any relevance to the speed or performance under such circumstances.

If function-like macros were a pack of surprises then the inline functions are the ultimate riddles. There are simply no simple answers as to when or why to use them. The best way to know is to play with it to see what is best.

Finale

To wrap up on which of the two is better, one cannot help but be diplomatic. Though there seems to be a lot of pros and cons for the usage of both function-like macros and inline functions, the truth is that they both do make difference in their own way.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

Comments and Discussions

I have a inline function it will return some digits. this function is in aspx.cs (asp.net codebehind file) . I want to call that function in html disign of the same aspx page. can anybody tell me how to call inline function in aspx (Html) page.
Thanks

How do compilers treat inline functions within
inline functions as in the following example

inline int a(int val)
{
return val + 2;
}

inline int b(int val)
{
return a(val) * 4;
}

int main(...)
{
printf("%d", b(1));
}

Will this expand to

printf("%d", (1+2) * 4);

I've seen this sort of thing not work in Visual Studio 6. Specifically, I once wrote a 3D vector class that had inlined functions, but some of them called other inlined functions in the class for convenience... these ended up not being inlined and were very slow. I had to "manually inline" them. Any experience with other compilers?

Also, even though I try to stay away from using macros, out of curiosity, has anybody ever tried using nested macros? What about recursive macros? That might be a good one for the programming trivia pages.

As you know address of inline functions can not be taken. If this is true then there is no way compiler can put its address in the VTABLE. If at all compiler can put the address in VTABLE the function is not inline.

1. You can't take the address of a macro (I know, taking the address of a inline function makes it impossible to be inlined).
2. Inline functions obey C++ scope rules, macros don't.
3. You can have virtual inline functions (although whether it is treated as a inline function is compiler dependent

When you take address of an inline function compiler will generate a body for it, but it still might consider it for inlining in different places where it is called directly (not via function pointer). One and the same function can be inlined better or worth depending on the context of the call and compiler will make a decision on whether to inline it or not based on the context too, not only based on the function body.

The same applies to virtual inlines: when function is called as virtual it will certainly be called via VTBL and as a result generated body, but when you call it directly (specifying the class CMyClass::VirtualFunc()) then compiler might inline it as well.

As to scope, there is one issue here, where you can't reach the same with inlines as you can with macros. A good example is usage of Microsoft's extension alloca() - function which allocates memory on the stack: suppose I want to write function HEX2TEXT(n) which takes integer n and returns hexadecimal string representation of it. Normally I would either have to pass buffer into function [char* HEX2TEXT(char*, int)] or to delete allocated buffer if it was allocated by the function itself. The first approach makes code not as functional-style as I wanted it to be cause I have to declare a temporary buffer of size that is dependent on function's logic, in the second approach I need to delete returned buffer, which I have to assign to local variable first (again functional style goes away). In the first approach I will also need several buffers if I want call this function several times within one expression. The actual problem that prevents me from inlining usage of alloca() is exactly scope cause even if the function will be inlined, buffer will be destroyed upon exit from function body, while I need it to be destroyed upon exit from caller's body. Macros, that do not take scope into account do their job here:

#define HEX2TEXT(n) itoa(n, (char*)alloca(9), 10)

alloca will be called within the scope I need and will do its job. I don't have to take care of destroying the buffer cause it will be destroyed upon exit from function.

For those who doesn't like Microsoft here is another typical example: I have an object called Tracer which should be instantiated upon entrance to function and as a parameter to which I pass: this pointer, function name (some compilers support macro for this) and some other parameters. The only thing this object should do is to log entry to the method (with dump of arguments) and then exit from method, dumping time spent in it and whether exit was normal or due to to exception. Mentioning all the parameters all the time is quite verbose and most of them are always the same: this, __FUNCTION etc. So we create a macro here that instantiates object and passes all the standard parameters. Again it is important here that object is instantiated without taking scope into account cause otherwise our object will trace something else.

I am sticking to the followinb rules
(1)Macros are the last way out, when there is no other possibility to (reasonably) express a solution using C++
(2) As much of the macro body as possible is reduced to an (often inline, often template) function. The macro does only the part impossible in C++

we are here to help each other get through this thing, whatever it is Vonnegut jr.

Could you provide an example? I don't see the benefit. (Also, why do...while vs. just an embedded block?)

On a related note, though, I feel that putting braces around the statement(s) in the if clause or the else clause is a must ... so much so, in fact, that I always do that even with normal code. (It makes maintenance that much easier.)

(1) sorry, this is an error in translation, it should read "parantheses" of course.
(In german we have one word "Klammern", and the difference between parantheses and braces is "runde Klammern" or
"geschweifte Klammern" respectively)