Policies/Binary Compatibility Examples: Difference between revisions
(Add a section on changing the type of global data) |
(Add the mangled names and explanations on what goes wrong if you do what this document tells you not to do) |
||
Line 20: | Line 20: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': the symbols for the class above are not added to the exported symbols list of the library, so other libraries and applications cannot see them. | |||
== Change the class hierarchy == | == Change the class hierarchy == | ||
Line 53: | Line 55: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': the size and/or order of member data in the class changes, causing existing code to allocate too much or too little memory, read/write data at the wrong offsets. | |||
== Change the template arguments of a template class == | == Change the template arguments of a template class == | ||
Line 75: | Line 79: | ||
</code> | </code> | ||
|} | |} | ||
<code cppqt> | |||
// GCC mangling before: _Z3foo15MyTemplateClassIiE | |||
// after: _Z3foo15MyTemplateClassIivE | |||
void foo(MyTemplateClass<int>); | |||
</code> | |||
'''Reason''': the mangling of the functions related to this template type change because its template expansion changes too. This can happen both for member functions (for example, the constructor) as well as functions that take it as a parameter. | |||
== Unexport a function == | == Unexport a function == | ||
Line 100: | Line 112: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': the symbols for the functions above are not added to the exported symbols list of the library, so other libraries and applications cannot see them. | |||
== Inline a function == | == Inline a function == | ||
Line 160: | Line 174: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': when a function is declared inline and the compiler does inline it at its call point, the compiler does not have to emit an out-of-line copy. Code that exists and was calling this function will therefore not be able to resolve the function anymore. Also, when compiling with GCC and <tt>-fvisibility-inlines-hidden</tt>, if the compiler does emit an out-of-line copy, it will be hidden (not added to the exported symbols table) and therefore not accessible from other libraries. | |||
== Change the parameters of a function == | == Change the parameters of a function == | ||
Line 168: | Line 184: | ||
|- | |- | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: _Z11doSomethingii | |||
// MSVC mangling: ?doSomething@@YAXHH@Z | |||
void doSomething(int i1, int i2); | void doSomething(int i1, int i2); | ||
</code> | </code> | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: _Z11doSomethingis | |||
// MSVC mangling: ?doSomething@@YAXHF@Z | |||
void doSomething(int i1, short i2); | void doSomething(int i1, short i2); | ||
</code> | </code> | ||
|- | |- | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: _Z11doSomethingii | |||
// MSVC mangling: ?doSomething@@YAXHH@Z | |||
void doSomething(int i1, int i2); | void doSomething(int i1, int i2); | ||
</code> | </code> | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: _Z11doSomethingiii | |||
// MSVC mangling: ?doSomething@@YAXHHH@Z | |||
void doSomething(int i1, int i2, int i3 = 0); | void doSomething(int i1, int i2, int i3 = 0); | ||
</code> | |||
|- | |||
| <code cppqt> | |||
// GCC mangling: _Z11doSomethingRi | |||
// MSVC mangling: ?doSomething@@YAXABH@Z | |||
void doSomething(int &i1); | |||
</code> | |||
| <code cppqt> | |||
// GCC mangling: _Z11doSomethingRKi | |||
// MSVC mangling: ?doSomething@@YAXAAH@Z | |||
void doSomething(const int &i1); | |||
</code> | </code> | ||
|- | |- | ||
Line 185: | Line 220: | ||
</code> | </code> | ||
| <code cppqt> | | <code cppqt> | ||
void doSomething(const int i1); // breaks with | void doSomething(const int i1); // breaks with Sun CC | ||
</code> | </code> | ||
|- | |- | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: _Z11doSomethingPc | |||
// MSVC mangling: ?doSomething@@YAXPAD@Z (32-bit) | |||
void doSomething(char *ptr); | void doSomething(char *ptr); | ||
</code> | </code> | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: _Z11doSomethingPKc | |||
// MSVC mangling: ?doSomething@@YAXPBD@Z (32-bit) | |||
void doSomething(const char *ptr); | void doSomething(const char *ptr); | ||
</code> | </code> | ||
Line 203: | Line 242: | ||
|- | |- | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: _Z8positionv | |||
// MSVC mangling: ?position@@YA_JXZ | |||
qint64 position(); | |||
</code> | |||
| <code cppqt> | |||
// GCC mangling: _Z8positionv | |||
// MSVC mangling: ?position@@YAHXZ | |||
int position(); | |||
</code> | |||
|- | |||
| <code cppqt> | |||
// GCC mangling: _Z4namev | |||
// MSVC mangling: ?position@@YAVQByteArray@@DXZ | |||
QByteArray name(); | |||
</code> | |||
| <code cppqt> | |||
// GCC mangling: _Z4namev | |||
// MSVC mangling: ?position@@YAVQString@@XZ | |||
QString name(); | |||
</code> | |||
|- | |||
| <code cppqt> | |||
// GCC mangling: _Z4namev | |||
// MSVC mangling: ?position@@YAPBDXZ | |||
const char *name(); | |||
</code> | |||
| <code cppqt> | |||
// GCC mangling: _Z4namev | |||
// MSVC mangling: ?position@@YAVQString@@XZ | |||
QString name(); | |||
</code> | |||
|- | |||
| <code cppqt> | |||
// GCC mangling: _Z12createDevicev | |||
// MSVC mangling: ?createDevice@@YAPAVQTcpSocket@@XZ | |||
QTcpSocket *createDevice(); | QTcpSocket *createDevice(); | ||
</code> | </code> | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: _Z12createDevicev (unchanged) | |||
// MSVC mangling: ?createDevice@@YAPAVQIODevice@@XZ | |||
QIODevice *createDevice(); | QIODevice *createDevice(); | ||
</code> | |||
|- | |||
| <code cppqt> | |||
// GCC mangling: _ZNK10QByteArray2atEi | |||
// MSVC mangling: ?at@QByteArray@@QBA?BDH@Z | |||
const char QByteArray::at(int) const; | |||
</code> | |||
| <code cppqt> | |||
// GCC mangling: _ZNK10QByteArray2atEi (unchanged) | |||
// MSVC mangling: ?at@QByteArray@@QBADH@Z | |||
char QByteArray::at(int) const; | |||
</code> | </code> | ||
|} | |} | ||
'''Reason''': changing the return type changes the mangled name of the function names in some compilers (GCC notably does not encode the return type). However, even if the mangling doesn't change, the convention on how the return types are handled may change. In the first example above, the return type changed from a 64- to a 32-bit integer, which means on some architectures, the upper half of the return register may contain garbage. In the second example, the return type changed from QByteArray to QString, which are two incompatible types, whereas in the third example, the return type changed from a simple integer (a POD) to a QString -- in this case, the compiler usually needs to pass a hidden implicit first parameter, which won't be there. | |||
== Change the access rights == | == Change the access rights == | ||
Line 220: | Line 309: | ||
{ | { | ||
protected: | protected: | ||
// GCC mangling: _ZN7MyClass11doSomethingEv | |||
// MSVC mangling: ?doSomething@MyClass@@IAAXXZ | |||
void doSomething(); | void doSomething(); | ||
}; | }; | ||
Line 227: | Line 318: | ||
{ | { | ||
public: | public: | ||
// GCC mangling: _ZN7MyClass11doSomethingEv (unchanged) | |||
// MSVC mangling: ?doSomething@MyClass@@QAAXXZ | |||
void doSomething(); | void doSomething(); | ||
}; | }; | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': some compilers encode the protection type of a function in its mangled name. | |||
== Change the CV-qualifiers of a member function == | == Change the CV-qualifiers of a member function == | ||
Line 242: | Line 337: | ||
{ | { | ||
public: | public: | ||
// GCC mangling: _ZNK7MyClass9somethingEv | |||
// MSVC mangling: ?something@MyClass@QBAHXZ | |||
int something() const; | int something() const; | ||
}; | }; | ||
Line 249: | Line 346: | ||
{ | { | ||
public: | public: | ||
// GCC mangling: _ZN7MyClass9somethingEv | |||
// MSVC mangling: ?something@MyClass@QAAHXZ | |||
int something(); | int something(); | ||
}; | }; | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': compilers encode the constness of a function in the mangled name. The reason they all do that is because the C++ standard allows overloading of functions that differ only by the constness. | |||
== Change the type of global data == | == Change the type of global data == | ||
Line 261: | Line 362: | ||
|- | |- | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: data (undecorated) | |||
// MSVC mangling: ?data@@3HA | // MSVC mangling: ?data@@3HA | ||
int data = 42; | int data = 42; | ||
</code> | </code> | ||
| <code cppqt> | | <code cppqt> | ||
// GCC mangling: data (undecorated) | |||
// MSVC mangling: ?data@@3FA | // MSVC mangling: ?data@@3FA | ||
short data = 42; | short data = 42; | ||
Line 273: | Line 376: | ||
{ | { | ||
public: | public: | ||
// GCC mangling: _ZN7MyClass4dataE | |||
// MSVC mangling: ?data@MyClass@@2HA | // MSVC mangling: ?data@MyClass@@2HA | ||
static int data; | static int data; | ||
Line 281: | Line 385: | ||
{ | { | ||
public: | public: | ||
// GCC mangling: _ZN7MyClass4dataE | |||
// MSVC mangling: ?data@MyClass@@2FA | // MSVC mangling: ?data@MyClass@@2FA | ||
static short data; | static short data; | ||
Line 286: | Line 391: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': some compilers encode the type of the global data in its mangled name. Especially note that some compilers mangle even for simple data types that would be allowed in C, meaning the <tt>extern "C"</tt> qualifier makes a difference too. | |||
== Change the CV-qualifiers of global data == | == Change the CV-qualifiers of global data == | ||
Line 335: | Line 442: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': some compilers encode the CV-qualifiers of the global data in its mangled name. Especially note that a static const value declared in the class itself can be considered for "inlining" -- that is, the compiler doesn't need to generate an external symbol for the value since all implementations are guaranteed to know it. | |||
== Add a virtual member function to a class without any == | == Add a virtual member function to a class without any == | ||
Line 356: | Line 465: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': a class without any virtual members or bases is guaranteed to be exactly like a C structure, for compatibility with that language (that is a POD structure). On some compilers, structures/classes with bases that are POD themselves are also POD. However, as soon as there's one virtual base or virtual member function, the compiler is free to arrange the structure in a C++ manner, which usually means inserting a hidden pointer at the beginning or the end of the structure, pointing to the virtual table of that class. This changes the size and offset of the elements in the structure. | |||
== Add new virtuals to a non-leaf class == | == Add new virtuals to a non-leaf class == | ||
Line 381: | Line 492: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': the addition of a new virtual function to a class that is non-leaf (that is, there is at least one class deriving from this class) changes the layout of the virtual table (the virtual table is basically a list of function pointers, pointing to the functions that are active in this class level). To accommodate the new virtual, the compiler must add a new entry to this table, but existing derived classes won't know about it and will not have the entry in their virtual tables. | |||
== Change the order of the declaration of virtual functions == | == Change the order of the declaration of virtual functions == | ||
Line 407: | Line 520: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': the compiler places the pointers to the functions implementing the virtual functions in the order that they are declared in the class. By changing the order of the declaration, the order of the entries in the virtual table changes too. | |||
Note: the order is inherited from the parent classes, so overriding a virtual will allocate the entry in the parent's order. | |||
== Override a virtual that doesn't come from a primary base == | == Override a virtual that doesn't come from a primary base == | ||
Line 447: | Line 564: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': this is a tricky case. When dealing with multiple-inheritance of classes with virtual tables, the compiler must create multiple virtual tables to guarantee polymorphism works (that is, when your MyClass object is stored in a PrimaryBase or SecondaryBase pointer). The virtual table for the primary base is shared with the class's own virtual table, because they have the same layout at the beginning. However, if you override a virtual coming from a non-primary base, it is the same as adding a new virtual, since that primary base did not have the virtual by that name. | |||
== Override a virtual with a covariant return with different top address == | == Override a virtual with a covariant return with different top address == | ||
Line 494: | Line 613: | ||
</code> | </code> | ||
|} | |} | ||
'''Reason''': this is another tricky case, like the above one and also for the same reason: the compiler must add a second entry to the virtual table, just as if a new virtual function had been added, which changes the layout of the virtual table and breaks derived classes. | |||
Covariant calls happen when the function overriding a virtual from a parent class returns a class different from the parent (this is allowed by the C++ standard, so the code above is perfectly valid and calling <tt>p->get()</tt> with p of type <tt>BaseClass</tt> will call <tt>MyClass::get</tt>). If the more-derived type doesn't have the same top-address such as <tt>Complex1</tt> and <tt>Complex2</tt> above, when compared to <tt>Data1</tt>, then the compiler needs to generate a stub function (usually called a "thunk") to adjust the value of the pointer returned. It places the address to that thunk in the entry corresponding to the parent's virtual function in the virtual table. However, it also adds a new entry for calls made which return the new top-address. |
Revision as of 10:02, 15 July 2009
This page is meant as examples of things you cannot do in C++ when maintaining binary compatibility.
Unexport or remove a class
Before | After |
---|---|
|
|
Reason: the symbols for the class above are not added to the exported symbols list of the library, so other libraries and applications cannot see them.
Change the class hierarchy
Before | After |
---|---|
|
|
|
|
Reason: the size and/or order of member data in the class changes, causing existing code to allocate too much or too little memory, read/write data at the wrong offsets.
Change the template arguments of a template class
Before | After |
---|---|
|
|
// GCC mangling before: _Z3foo15MyTemplateClassIiE
// after: _Z3foo15MyTemplateClassIivE
void foo(MyTemplateClass<int>);
Reason: the mangling of the functions related to this template type change because its template expansion changes too. This can happen both for member functions (for example, the constructor) as well as functions that take it as a parameter.
Unexport a function
Before | After |
---|---|
|
|
|
|
Reason: the symbols for the functions above are not added to the exported symbols list of the library, so other libraries and applications cannot see them.
Inline a function
Before | After |
---|---|
|
|
|
|
|
|
|
|
Reason: when a function is declared inline and the compiler does inline it at its call point, the compiler does not have to emit an out-of-line copy. Code that exists and was calling this function will therefore not be able to resolve the function anymore. Also, when compiling with GCC and -fvisibility-inlines-hidden, if the compiler does emit an out-of-line copy, it will be hidden (not added to the exported symbols table) and therefore not accessible from other libraries.
Change the parameters of a function
Before | After |
---|---|
|
|
|
|
|
|
|
|
|
|
Change the return type
Before | After |
---|---|
|
|
|
|
|
|
|
|
|
|
Reason: changing the return type changes the mangled name of the function names in some compilers (GCC notably does not encode the return type). However, even if the mangling doesn't change, the convention on how the return types are handled may change. In the first example above, the return type changed from a 64- to a 32-bit integer, which means on some architectures, the upper half of the return register may contain garbage. In the second example, the return type changed from QByteArray to QString, which are two incompatible types, whereas in the third example, the return type changed from a simple integer (a POD) to a QString -- in this case, the compiler usually needs to pass a hidden implicit first parameter, which won't be there.
Change the access rights
Before | After |
---|---|
|
|
Reason: some compilers encode the protection type of a function in its mangled name.
Change the CV-qualifiers of a member function
Before | After |
---|---|
|
|
Reason: compilers encode the constness of a function in the mangled name. The reason they all do that is because the C++ standard allows overloading of functions that differ only by the constness.
Change the type of global data
Before | After |
---|---|
|
|
|
|
Reason: some compilers encode the type of the global data in its mangled name. Especially note that some compilers mangle even for simple data types that would be allowed in C, meaning the extern "C" qualifier makes a difference too.
Change the CV-qualifiers of global data
Before | After |
---|---|
|
|
|
|
|
|
Reason: some compilers encode the CV-qualifiers of the global data in its mangled name. Especially note that a static const value declared in the class itself can be considered for "inlining" -- that is, the compiler doesn't need to generate an external symbol for the value since all implementations are guaranteed to know it.
Add a virtual member function to a class without any
Before | After |
---|---|
|
|
Reason: a class without any virtual members or bases is guaranteed to be exactly like a C structure, for compatibility with that language (that is a POD structure). On some compilers, structures/classes with bases that are POD themselves are also POD. However, as soon as there's one virtual base or virtual member function, the compiler is free to arrange the structure in a C++ manner, which usually means inserting a hidden pointer at the beginning or the end of the structure, pointing to the virtual table of that class. This changes the size and offset of the elements in the structure.
Add new virtuals to a non-leaf class
Before | After |
---|---|
|
|
Reason: the addition of a new virtual function to a class that is non-leaf (that is, there is at least one class deriving from this class) changes the layout of the virtual table (the virtual table is basically a list of function pointers, pointing to the functions that are active in this class level). To accommodate the new virtual, the compiler must add a new entry to this table, but existing derived classes won't know about it and will not have the entry in their virtual tables.
Change the order of the declaration of virtual functions
Before | After |
---|---|
|
|
Reason: the compiler places the pointers to the functions implementing the virtual functions in the order that they are declared in the class. By changing the order of the declaration, the order of the entries in the virtual table changes too.
Note: the order is inherited from the parent classes, so overriding a virtual will allocate the entry in the parent's order.
Override a virtual that doesn't come from a primary base
class PrimaryBase
{
public:
virtual ~PrimaryBase();
virtual void foo();
};
class SecondaryBase
{
public:
virtual ~PrimaryBase();
virtual void bar();
};
Before | After |
---|---|
|
|
Reason: this is a tricky case. When dealing with multiple-inheritance of classes with virtual tables, the compiler must create multiple virtual tables to guarantee polymorphism works (that is, when your MyClass object is stored in a PrimaryBase or SecondaryBase pointer). The virtual table for the primary base is shared with the class's own virtual table, because they have the same layout at the beginning. However, if you override a virtual coming from a non-primary base, it is the same as adding a new virtual, since that primary base did not have the virtual by that name.
Override a virtual with a covariant return with different top address
struct Data1 { int i; };
class BaseClass
{
public:
virtual Data1 *get();
};
struct Data0 { int i; };
struct Complex1: Data0, Data1 { };
struct Complex2: virtual Data1 { };
Before | After |
---|---|
|
|
|
|
Reason: this is another tricky case, like the above one and also for the same reason: the compiler must add a second entry to the virtual table, just as if a new virtual function had been added, which changes the layout of the virtual table and breaks derived classes.
Covariant calls happen when the function overriding a virtual from a parent class returns a class different from the parent (this is allowed by the C++ standard, so the code above is perfectly valid and calling p->get() with p of type BaseClass will call MyClass::get). If the more-derived type doesn't have the same top-address such as Complex1 and Complex2 above, when compared to Data1, then the compiler needs to generate a stub function (usually called a "thunk") to adjust the value of the pointer returned. It places the address to that thunk in the entry corresponding to the parent's virtual function in the virtual table. However, it also adds a new entry for calls made which return the new top-address.