The C++98 language is ABI-compatible with the C++11 language, but several places in the Standard Library break compatibility. This makes it potentially 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 6.1.

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

The differences below were found by grepping for GXX_EXPERIMENTAL_CXX0X inside libstdc++ (circa GCC 4.7), and examining each instance to see if there was an #else clause that had different behavior in C++11 vs C++98. Other items have been added as ABI issues were discovered (and/or fixed).

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

[In gcc-4.7.0 and gcc-4.7.1 only]

New _M_size member only present in C++11 mode, 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.

Starting with GCC 4.7.2 the new member has been removed, removing the incompatibility between C++98 and C++11.

Starting with GCC 5.1.0 there is a new implementation of std::list available for both C++98 and C++11, which adds a size member but mangles differently, so is safe.


[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.

Starting with GCC 4.7.2 the move constructor is trivial again.

`complex::real()` and `complex::imag()`

[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 because they define pointer as a typedef for _Tp* anyway, 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 std::vector::data() 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 isn't accurate i.e. if subtracting _IteratorL objects returns something other than iterator_traits<_IteratorL>::difference_type. For types where difference_type is accurate, this is compatible.

The same is true for __normal_iterator<_IteratorL, _Container>::difference_type but since that is an internal library type all uses should be safe.

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

[Since gcc-4.7]

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

Types with potentially overlapping subobjects of type istreambuf_iterator<C, T> and another iterator that derives from iterator<input_iterator_tag, C, ...> could have a different layout in C++98 compared to later standards. See Bug 92285 for more details.

Since GCC 10.1 the type is always _CharT even in C++98 mode. This removes any potential incompatibility between C++98 and later standards, but means that C++98 code compiled with previous versions might be incompatible with C++98 code compiled with gcc-10 and later. The likelihood of any actual impact is believed to be very low.

Probably safe: Types with node allocators, like `deque` and lists, maps and sets

While C++11 support was incomplete node-based containers used _M_get_Node_allocator().construct(node, ...) in C++11 mode (where the node's constructor forwarded to the value_field's constructor), while C++98 uses get_allocator().construct(node->_M_value_field, ...). This could cause compilation failures if a user-defined C++03-style allocator was used in C++11 mode, but does 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).

ABI non-changes

libstdc++'s binary component (the and libstdc++.a libraries) is ABI-compatible between C++98 and C++11. Most of the incompatibilities described on this page are in the templates defined in headers, and are not compiled into the libraries. describes a potential problem with GCC 4.7 and earlier, related to the complex<> stream operators that are exported, and make calls to real() and imag() which changed definition as described above. If those 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). With the default build settings the calls get inlined in all known cases, so there is no problem. The issue doesn't exist at all for GCC 4.8.0 and later.

There were some claims that the change in the definition of POD types in C++11 causes an ABI incompatibility, but it doesn't, because a slightly different definition is used for ABI purposes, and that different definition didn't change:

None: Cxx11AbiCompatibility (last edited 2020-11-04 12:44:17 by JonathanWakely)