TITLE: extern inlines by default [ I have abridged this newsletter with permission. It has two articles you'll find interesting. The first, covered in this tip, is on extern inlines. -adc ] Issue #015 September, 1996 Contents: Introduction to Exception Handling Part 1 - A Simple Example Testing C++ Compilers Notes From ANSI/ISO - Extern Inlines By Default Introduction to STL Part 2 - Vectors, Lists, Deques Using C++ as a Better C Part 15 - Bit Field Types INTRODUCTION TO EXCEPTION HANDLING PART 1 - A SIMPLE EXAMPLE [snip] TESTING C++ COMPILERS [snip] NOTES FROM ANSI/ISO - EXTERN INLINES BY DEFAULT Jonathan Schilling, jls@sco.com In the original C++ language, inline functions always had internal linkage. Then a change was made to allow the possibility of external linkage. Now a change has been made to make external linkage the default. This may alter the behavior of some existing code. The linkage of a global inline function doesn't really matter if it is in fact inlined (unless it contains static local variables; see below), but in real life the inline specifier is sometimes not honored for one reason or another (see C++ Newsletter #007). When this happens, linkage matters. Consider the following: file1.C: inline int f(int x, int y) { return 2 * x - y; } ... f(3, 5) ... file2.C: inline int f(int x, int y) { return 3 * x - y; } ... f(3, 5) ... That the two definitions of f() are different may be an accident or may be an intentional (but probably poor) coding practice. If the compiler does inline the function calls, the call to f() in file1 will return 1, while the apparently identical call to f() in file2 will return 4. If the functions are for some reason not inlined, but inline functions have internal linkage, then the f() calls will still return 1 and 4. This is because the compiler will generate a separate, local function body in each object file that has a call to the function, using the definition that is available for that compilation; no language rules violation occurs. In 1994 the C++ ANSI/ISO committee made a change to the language to allow inline functions to be declared with the "extern" specifier, giving such functions external linkage extern inline int f(int x, int y) { ... } but the default for global inline functions was still kept as internal. (Inline member functions get the linkage of the class they are declared in, which usually means external). The meaning of "extern inline" is that if calls to a function are not generated inline, then a compiler should make just one copy of the definition of the function, to be shared across all object files. For example, if code in more than one object file takes the address of the above function f() and prints it out, the same address value should appear each time. Otherwise, extern inline functions are like static inline functions: the function definition is compiled multiple times, once for each source file that calls it. Implementing this sharing requires additional linker support on some platforms, which may be part of the reason why "extern inline" is not yet supported in some C++ compilers. Additionally, this sharing does not always have to be done; if an inline function does not contain static local variables or have its address taken, there is no way to tell whether the definition is shared or not, and a compiler is free to not share it (at the cost of increasing program size). And there is even some compiler sleight-of-hand that can avoid sharing when these conditions are present. But what happens if, as in the original example, an extern inline function that is not inlined has different definitions in the different places it is used? In this case, there is a violation of C++'s One Definition Rule. This means that the program's behavior is considered undefined according to the language standard, but that neither the compiler nor the linker is required to give a diagnostic message. In practice, this means that, depending on how the implementation works, the compiler or linker may just silently pick one of the definitions to be used everywhere. For the above example, both of the calls to f() might return 1 or both might return 4. But the 1994 change did not risk altering the meaning of any existing code, because an extern specifier would have to be added to the source to trigger these new semantics. However, at the most recent standards meeting in Stockholm in July, a further change was made to make external linkage the default for non-member inline functions. (The immediate motivation for this change was a need of the new template compilation model that was adopted at the same meeting; but more generally it was felt that changing the default was an idea whose time had come, and the change was approved unanimously in both ANSI and ISO). With this latest change, all non-member inline functions that do not explicitly specify "static" will become external, and thus it is possible that existing code will now function differently. To help cope with this, compilers may provide a compatibility option to give inline functions their old linkage. It is also possible for users to force the old behavior by use of the preprocessor #define inline static inline but this only works if there are no member functions declared with "inline", and as a preprocessor-based solution is not recommended. Note that change of behavior may occur even when there is a single source definition for a function. For example, assume that the following function is defined in a header file somewhere: file3.h: inline int g(int x, int y) { #ifndef NDEBUG cerr << "I'm in g()" << endl; #endif if (x >= y) return h(x, y); else return 2 * x - y; } Even though the source for the function is defined only once, the function can have different semantics depending upon where it is compiled. For example, in one file NDEBUG might be defined, but in another not. Or, the call to function h() might be overloaded and resolve differently in one file from another, depending upon what other functions were visible in each file. These cases are also violations of the One Definition Rule, and may lead to a change in behavior of existing code. Still another way that existing inline function code can have its behavior altered is if it uses local static variables. Consider the following function e() defined in a header file: inline int e() { static int i = 0; return ++i; } When the function previously had internal linkage, there was a separate "i" allocated within each object file that had a call to e(). But now that the function gets external linkage, there is only one copy of e(), and only one "i". This will cause calls to the function to return different values than before. The One Definition Rule is a weakness of C++ where software reliability is concerned; languages with stronger module systems (such as Ada or Java) do not have these kinds of problems. As a general guideline, global inline functions should operate upon their arguments, and avoid static variables, interactions with the surrounding context, and the preprocessor. INTRODUCTION TO STL PART 2 - VECTORS, LISTS, DEQUES [snip] USING C++ AS A BETTER C PART 15 - BIT FIELD TYPES [snip] ACKNOWLEDGEMENTS [snip] SUBSCRIPTION INFORMATION / BACK ISSUES To subscribe to the newsletter, send mail to majordomo@world.std.com with this line as its message body: subscribe c_plus_plus Back issues are available via FTP from: rmii.com /pub2/glenm/newslett or on the Web at: http://www.rmii.com/~glenm There is also a Java newsletter. To subscribe to it, say: subscribe java_letter using the same majordomo@world.std.com address. ------------------------- Copyright (c) 1996 Glen McCluskey. All Rights Reserved. This newsletter may be further distributed provided that it is copied in its entirety, including the newsletter number at the top and the copyright and contact information at the bottom. Glen McCluskey & Associates Professional C++ Consulting Internet: glenm@glenmccl.com Phone: (800) 722-1613 or (970) 490-2462 Fax: (970) 490-2463 FTP: rmii.com /pub2/glenm/newslett (for back issues) Web: http://www.rmii.com/~glenm