The C++98 language is ABI-compatible with the C++11 language, but several places in the library break compatibility. This makes it dangerous to link C++98 objects with C++11 objects. If you can recompile your code in matching versions of the language, you should do that. This page explains what to do if recompiling is very difficult.

This page covers libstdc++ up to version 4.7.

How to get a list of symbols your library exports

First, get a list of symbols from your .a or .so:

  $ (find . -name '*.a'|xargs nm -f posix; find . -name '*.so' | xargs nm -f posix -D)|cut -f1 -d' '|LANG=C sort -u > all_symbols
  $ grep '^_Z' all_symbols | c++filt|sort > demangled_c++_symbols

(There may be better ways to get this list.)

If demangled_c++_symbols is empty, and none of the library's functions take or return pointers to C++ objects, the library has an all-C interface and is compatible.

If demangled_c++_symbols doesn't include the string "std::", and none of the functions exposed in header files take or return references to objects in the standard library, the library is very likely to be compatibile.

If the library exposes functions from the standard library, or takes or returns objects from the standard library, consult the list of ABI changes below for each symbol you find.

For example, you might find:

  std::_List_base<FooBar, std::allocator<FooBar> >::_M_clear()

Since std::_List_base::_M_clear() destroys nodes, it's affected by the addition of the _M_size field in GCC 4.7.0 and 4.7.1 and can't be used by C++11 code if it was compiled for C++98, or vice versa. If it's possible that code outside the library would use this instance of _List_base, then you have to recompile the library. On the other hand, if FooBar is a type used only inside this library, then code using the library is safe. If FooBar is defined by the library but exposed in one of the library's headers, then the library still needs to be recompiled, since code using it could wind up including the other version's implementation.

ABI Changes

`std::list, std::_List_impl, and std::_List_base`

[In gcc-4.7.0 and gcc-4.7.1 only]

New _M_size member, meaning any method that adds or removes nodes or inspects the size has a new ABI.

This is unsafe if the signatures are exported or if a std::list is passed to or returned from an exposed function.


[In gcc-4.7.0 and gcc-4.7.1 only]

The move constructor was non-trivial, which altered the calling convention for functions with std::pair arguments or return types.

This is unsafe if a std::pair is passed to or returned from an exposed function.

`complex::{real,imag}(), std::{real,imag}(complex)`

[From gcc-4.4 to gcc-4.7]

The non-const overloads go from returning _Tp& to _Tp.

This is unsafe if the signatures are exported, but safe if a std::complex is just passed or returned.

Starting with GCC 4.8.0 the signatures are mangled differently in C++11 mode, so the new signatures are created as different symbols and so are safe.

Maps, sets, and trees

[From gcc-4.5 to gcc-4.8.1]

map::erase(iterator), multimap::erase(iterator), set::erase(const_iterator), set::erase(const_iterator, const_iterator), multiset::erase(const_iterator), multiset::erase(const_iterator, const_iterator):

Return type changes from void to iterator.

_Rb_tree<T, T>::erase(const_iterator), _Rb_tree<T,T>::erase(const_iterator, const_iterator), _Rb_tree<T,pair>::erase(iterator):

Return type changes from void to iterator. These are instantiated from map and set.

Probably safe: map::erase(iterator, iterator), multimap::erase(iterator, iterator)

C++11 uses const_iterator, which doesn't collide. Other versions of gcc are unlikely to have defined this overload in C++98 mode, and C++11 is unlikely to have defined the iterator version.

These are unsafe if the signatures are exported, but safe if a std::map, etc. is just passed or returned.

Starting with GCC 4.8.2 the signatures are mangled differently in C++11 mode, so the new signatures are created as different symbols and so are safe.

[From gcc-4.9.0 to gcc-5.1.0]

On some architectures (including x86, ppc, arc, epiphany) the _Rb_tree_node<T> structure is not correctly aligned in C++11 mode for some template arguments types of T. One specific case that is affected is std::set<long long> on 32-bit x86, where the contained long long elements have a stricter alignment in C++11 mode than their (correct) alignment in C++03 mode. This bug was fixed for gcc-5.2.

`vector::data()`'s return type changes from `pointer` to `_Tp*`

[Since gcc-4.6]

This is compatible with most allocators, but any allocator that defines a non-default pointer typedef will be incompatible.

If the allocator's pointer is not _Tp*, then this is unsafe if the signature is exported, but safe if a std::vector is just passed or returned.

`std::operator-(reverse_iterator)` and `std::operator-(__normal_iterator)`

[Since gcc-4.4]

May return a different type if reverse_iterator<_IteratorL>::difference_type (respectively, __normal_iterator<_IteratorL, _Container>::difference_type) isn't accurate. For types where difference_type is accurate, this is compatible.

Probably safe: `istreambuf_iterator::reference` changes from `_CharT&` to `_CharT`.

[Since gcc-4.7]

This could affect return types if they mention reference, but the standard library appears not to mention it when istreambuf_iterator is involved.

Probably safe: Types with node allocators, like `deque` and tree

C++11 uses _M_get_Node_allocator().construct(node, ...), while C++98 uses get_allocator().construct(node->_M_value_field, ...). The node's constructor forwards to the value_field's constructor, so this works by default. This can cause compilation failures if a user-defined C++03-style allocator is used, but should not cause ABI problems. All containers have now been fixed to use allocator_traits::construct which will work with any allocator (this was done in gcc-4.8 for forward_list, in gcc-4.9 for associative and unordered containers, and in gcc-5 for deque and list).

I found these differences by grepping for GXX_EXPERIMENTAL_CXX0X inside libstdc++, and examining each instance to see if there was an #else clause that had different behavior in C++11 vs C++98.

ABI non-changes

libstdc++'s binary component is nearly ABI-compatible between C++98 and C++11. Most incompatibilities are in the templates defined in headers, but the complex<> stream operators call the real() and imag() methods that change return type. ( If these calls are inlined, and doesn't export definitions of them, then you can use the same with both C++98 and C++11 code. If they're not inlined, then your will only work with the language version it was compiled with (probably C++98).

There have been some claims that the change in the definition of POD types causes an ABI incompatibility, but apparently it doesn't in practice:

None: Cxx11AbiCompatibility (last edited 2015-06-14 13:08:59 by JonathanWakely)