jmmv ([info]jmmv) wrote,
@ 2005-10-02 15:18:00
Previous Entry  Add to memories!  Tell a Friend  Next Entry
C++: Inlined code and the ABI

There are many development libraries that provide inline functions (or macros) as part of their public API. This is often done for efficiency reasons, although some times it's done because developers don't know the consequences of doing such things (this last thing is just a guess, but it can perfectly happen). Providing such inlined functions breaks the whole idea of encapsulation and shared libraries. Let's see why.

Consider the following simple class:

 /* In foo.h. */

class foo {
    int m_value;

public:
    int get_value(void) { return m_value; }
    void set_value(int v);
    /* ... */
};

/* In foo.c; this is _not_ inlined. */
void
foo::set_value(int v)
{
    m_value = v;
}

Now imagine that this class belongs to a shared library, say libbar.so.1.0. Given this, our Joe user does this in his code, which is perfectly legal:

foo a;
a.set_value(5);
/* ... do whatever with 'a' ... */
int b = a.get_value();

When this code is compiled, the compiler replaces the call to foo::get_value() with the method's code, avoiding a function call, a return and all the stack set up; all the action takes place in the user's code, not in the library. Typically, getting a value from a structure means reading a concrete position of memory within it, described by its offset from the beginning. OTOH, the call to foo::set_value() is correctly made into a regular function call inside the shared library's text.

Some time later, the libbar developers decide to change the internal representation of the foo class for whatever reason. According to the encapsulation principle used in object oriented designs, they should be able to, after all. Let's suppose they add a new integer before the m_value field, called m_id. Unwillingly, the developers have just changed the ABI of their library and, if they don't take care to update the library's major number, seriuos problems will arise. But, why?

Our Joe user again sees a new release of libbar, say 1.1, so he rebuilds and updates it in his machine, replacing libbar.so.1.0 with libbar.so.1.1; these two libraries typically share the same soname, libbar.so.1, because they are compatible in theory. According to how shared libraries work, he oughtn't rebuild his application.

The set_value() call will continue to work correctly because the application will call the new function in the updated shared library. However, the execution of get_value() will be broken; oops! Remember the sample code shown above? It was compiled as an offset within the class, which is now different! This getter will return an incorrect value, no matter what he does. He'll be forced to rebuild his application to adjust to the new ABI.

Conclusion: be very careful when defining inlined methods and macros. If you need to fix a mistake or modify the internal representation of your code in the future, you will be unable to. Personally, I avoid inlined code in all public interfaces, despite this introduces a small performance degradation; however, they are perfectly fine for internal code.

It's a pity that careless C++ developers make so intensive use of such inlined code. BTW, note that although this has focused on C++, the same is true for, e.g., C99, which provides an inline keyword.

Edit (Oct 3rd): Based on this reply, I've removed some (really minor) references to templated code from the article; they certainly didn't belong here.




(3 comments) - (Post a new comment)


[info]fellow_traveler
2005-10-02 06:49 pm UTC (link)
Your example demonstrates the problem and your proposed practice of avoiding inline functions solves it, true.

However, I don't think templates can be casually thrown in as a sub-problem of the problem demonstrated by the given example. Templates are their own beast.

If I call a templated function, the function is instantiated for the types I use and compiled into my code. If I use a templated class, same deal; the parameterized types create a memory layout that is more or less unique to the program I'm compiling. Neither of these compiles much information into a shared library unless I explicitly instantiate some specific types to do so, and limiting one's use of templates to such pre-selected types pretty much defeats the purpose of templates.

Where templates are involved, there is no opportunity to apply your solution to the example problem; there are no functions that we could make non-inline in order to make application code call into the .so for arbitrary user-defined types. A template-based library is basically just incompatible with shared libraries and requires recompilation in order to use the new code in existing programs.

(Not that I have any reason to believe I'm telling you anything you don't already know)

(Reply to this) (Thread)


[info]jmmv
2005-10-02 07:25 pm UTC (link)
You're right in everything you say, and I agree that templates cannot be compared to inlined functions. I just mentioned them because they are a kind of "inlining" stuff in your code rather than keeping it in a shared library, thus requiring recompilation to take advantage of any changes done in the original template code.

I guess the concept of templates could be implemented in such a way that it was "shared-library-friendly". I'm wondering now how do Java 1.5 templates compare to this...?

Thanks for your comments.

(Reply to this) (Parent)

new order male inhansment medication supper disk
(Anonymous)
2007-12-11 11:32 am UTC (link)
hay!!
good project :)
senks :)

(Reply to this)


(3 comments) - (Post a new comment)

Create an Account
Forgot your login or password?
Login w/ OpenID
English • Español • Deutsch • Русский…