Can You Mix C++ Compiled with Different Versions of the Same Compiler

Can you mix c++ compiled with different versions of the same compiler

Different generations of the same compiler sometimes can be compatible with each other, but not always. For example, GCC 4.7.0 changed its C/C++ ABI, meaning libraries compiled with 4.7.0+ and 4.7.0- are not likely to be compatible with each other (so in your example, the library compiled with 4.6 will not be compatible with the library compiled with 4.9). There can also be ABI bugs within a given compiler release, as happened in GCC 4.7.0/4.7.1:

GCC versions 4.7.0 and 4.7.1 had changes to the C++ standard library which affected the ABI in C++11 mode: a data member was added to std::list changing its size and altering the definitions of some member functions, and std::pair's move constructor was non-trivial which altered the calling convention for functions with std::pair arguments or return types. The ABI incompatibilities have been fixed for GCC version 4.7.2 but as a result C++11 code compiled with GCC 4.7.0 or 4.7.1 may be incompatible with C++11 code compiled with different GCC versions and with C++98/C++03 code compiled with any version.

The GCC ABI Policy and Guidelines page indicates they try to maintain forward compatibility, but not backward compatibility:

Versioning gives subsequent releases of library binaries the ability to add new symbols and add functionality, all the while retaining compatibility with the previous releases in the series. Thus, program binaries linked with the initial release of a library binary will still run correctly if the library binary is replaced by carefully-managed subsequent library binaries. This is called forward compatibility.

The reverse (backwards compatibility) is not true. It is not possible to take program binaries linked with the latest version of a library binary in a release series (with additional symbols added), substitute in the initial release of the library binary, and remain link compatible.

That page also has some fairly lengthy explanations on the versioning system GCC uses to mark different versions of given components, as well as an explanation for the versioning behind GCC itself:

Allowed Changes

  • The following will cause the library minor version number to increase, say from "libstdc++.so.3.0.4" to "libstdc++.so.3.0.5".

  • Adding an exported global or static data member

  • Adding an exported function, static or non-virtual member function

  • Adding an exported symbol or symbols by additional instantiations

  • Other allowed changes are possible.

Prohibited Changes

The following non-exhaustive list will cause the library major version number to increase, say from "libstdc++.so.3.0.4" to "libstdc++.so.4.0.0".

  • Changes in the gcc/g++ compiler ABI

  • Changing size of an exported symbol

  • Changing alignment of an exported symbol

  • Changing the layout of an exported symbol

  • Changing mangling on an exported symbol

  • Deleting an exported symbol

  • Changing the inheritance properties of a type by adding or removing base classes

  • Changing the size, alignment, or layout of types specified in the C++ standard. These may not necessarily be instantiated or otherwise exported in the library binary, and include all the required locale facets, as well as things like std::basic_streambuf, et al.

  • Adding an explicit copy constructor or destructor to a class that would otherwise have implicit versions. This will change the way the compiler deals with this class in by-value return statements or parameters: instead of passing instances of this class in registers, the compiler will be forced to use memory. See the section on Function Calling Conventions and APIs of the C++ ABI documentation for further details.

Note the bolded bit. In a perfect world, GCC versions with the same major release number would be binary-compatible. This isn't a perfect world, so test very very carefully before you go mixing compiler versions like this, but in general you'll probably be okay.

Is it safe to link C++17, C++14, and C++11 objects

Which combinations of these objects is it and isn't it safe to link into a single binary? Why?

For GCC it is safe to link together any combination of objects A, B, and C. If they are all built with the same version then they are ABI compatible, the standard version (i.e. the -std option) doesn't make any difference.

Why? Because that's an important property of our implementation which we work hard to ensure.

Where you have problems is if you link together objects compiled with different versions of GCC and you have used unstable features from a new C++ standard before GCC's support for that standard is complete. For example, if you compile an object using GCC 4.9 and -std=c++11 and another object with GCC 5 and -std=c++11 you will have problems. The C++11 support was experimental in GCC 4.x, and so there were incompatible changes between the GCC 4.9 and 5 versions of C++11 features. Similarly, if you compile one object with GCC 7 and -std=c++17 and another object with GCC 8 and -std=c++17 you will have problems, because C++17 support in GCC 7 and 8 is still experimental and evolving.

On the other hand, any combination of the following objects will work (although see note below about libstdc++.so version):

  • object D compiled with GCC 4.9 and -std=c++03
  • object E compiled with GCC 5 and -std=c++11
  • object F compiled with GCC 7 and -std=c++17

This is because C++03 support is stable in all three compiler versions used, and so the C++03 components are compatible between all the objects. C++11 support is stable since GCC 5, but object D doesn't use any C++11 features, and objects E and F both use versions where C++11 support is stable. C++17 support is not stable in any of the used compiler versions, but only object F uses C++17 features and so there is no compatibility issue with the other two objects (the only features they share come from C++03 or C++11, and the versions used make those parts OK). If you later wanted to compile a fourth object, G, using GCC 8 and -std=c++17 then you would need to recompile F with the same version (or not link to F) because the C++17 symbols in F and G are incompatible.

The only caveat for the compatibility described above between D, E and F is that your program must use the libstdc++.so shared library from GCC 7 (or later). Because object F was compiled with GCC 7, you need to use the shared library from that release, because compiling any part of the program with GCC 7 might introduce dependencies on symbols that are not present in the libstdc++.so from GCC 4.9 or GCC 5. Similarly, if you linked to object G, built with GCC 8, you would need to use the libstdc++.so from GCC 8 to ensure all symbols needed by G are found. The simple rule is to ensure the shared library the program uses at run-time is at least as new as the version used to compile any of the objects.

Another caveat when using GCC, already mentioned in the comments on your question, is that since GCC 5 there are two implementations of std::string available in libstdc++. The two implementations are not link-compatible (they have different mangled names, so can't be linked together) but can co-exist in the same binary (they have different mangled names, so don't conflict if one object uses std::string and the other uses std::__cxx11::string). If your objects use std::string then usually they should all be compiled with the same string implementation. Compile with -D_GLIBCXX_USE_CXX11_ABI=0 to select the original gcc4-compatible implementation, or -D_GLIBCXX_USE_CXX11_ABI=1 to select the new cxx11 implementation (don't be fooled by the name, it can be used in C++03 too, it's called cxx11 because it conforms to the C++11 requirements). Which implementation is the default depends on how GCC was configured, but the default can always be overridden at compile-time with the macro.

Mixing C++ flavours in the same project

In practice, maybe.

It is relatively common for the standard library of C++11 and C++03 to disagree about what the layout of std namespace objects is. As an example, sizeof(std::vector<int>) changed noticeably over various compiler versions in MSVC land. (it got smaller as they optimized it)

Other examples could be a different heap on each side of the compiler fence.

So you have to carefully "firewall" between the two source trees.

Now, some compilers seek to minimize such binary compatibility changes, even at the cost of violating the standard. I believe std::list without a size counter might be an example of that (which violates C++11, but I recall that at least one vendor provided a standards-non-compliant std::list to maintain binary compatibility -- I don't remember which one).

For the two compilers (and a compiler in C++03 and C++11 are different compilers) you are going to have some ABI guarantees. There is probably a large chunk of the language for which the ABI will agree, and on that set you are relatively safe.

To be reasonably safe, you'll want to treat the other compiler version files as if they are third party DLLs (delay loaded libraries) that do not link to the same C++ standard library. That means any resources passed from one to the other have to be packaged with destruction code (ie, returned to the DLL from whence it came to be destroyed). You'll either have to investigate the ABI of the two standard libraries, or avoid using it in the common header files, so you can pass things like smart pointers between the DLLs.

An even safer approach is to strip yourself down to a C style interface with the other code base, and only pass handles (opaque types) between the two code bases. To make this sane, whip up some header-file only mojo that wraps the C style interface in pretty C++ code, just don't pass those C++ objects between the code bases.

All of this is a pain.

For example, suppose you have a std::string get_some_string(HANDLE) function, and you don't trust ABI stability.

So you have 3 layers.

namespace internal {
// NOT exported from DLL
std::string get_some_string(HANDLE) { /* implementation in DLL */ }
}
namespace marshal {
// exported from DLL
// visible in external headers, not intended to be called directly
void get_some_string(HANDLE h, void* pdata, void(*callback)( void*, char const* data, std::size_t length ) ) {
// implementation in DLL
auto r = ::internal::get_some_string(h);
callback( pdata, r.data(), r.size() );
}
}
namespace interface {
// exists in only public header file, not within DLL
inline std::string get_some_string(HANDLE h) {
std::string r;
::marshal::get_some_string(h, &r,
[](void* pr, const char* str, std::size_t length){
std::string& r = *static_cast<std::string*>(pr);
r.append( str, length );
}
);
return r;
}
}

So the code outside the DLL does an auto s = ::interface::get_some_string(handle);, and it looks like a C++ interface.

The code inside the DLL implements std::string ::internal::get_some_string(HANDLE);.

The marshal's get_some_string provides a C-style interface between the two, which provides better binary compatibility than relying on the layout and implementation of std::string to remain stable between the DLL and the code using the DLL.

The interface's std::string exists completely within the non-DLL code. The internal std::string exists completely within the DLL-code. The marshal code moves the data from one side to the other.

Is it safe to link compiled object files from different GCC versions?

So After doing a research on the web, and reading several GCC release notes, it seems that GCC is backward compatible if there is no ABI change.
In general, this would be stated in the release notes.

I also did some experiment using different GCC compilers and GCC linkers (by different meaning from different versions of GCC) and I got linker errors when it was incompatible (different ABI versions).

C++ with 2 compilers

It is an excellent idea. In the past, I've found a bunch of bugs in my code that I could only see after switching compilers.

Can different GCC dialects be linked together?

A priori, no. The safest solution is to assume that all of the compiler options are identical, except when the compiler specifically documents that the option doesn't affect binary compatibility. (Documentation which is sorely lacking in most compilers.) In practice, in the lack of documentation, it seems a safe bet that options which control warnings (-W... in g++) won't affect binary compatibility, and that options which affect code generation (language level, etc.) might: g++ generally maintains compatibility across different levels of optimization, where as VC++ doesn't.

Another real problem is defining preprocessor symbols in the command line. Again, the safest bet is that all of the defines be identical, but also again, some common sense is in order: one can hardly expect the standard library to have been compiled with preprocessor symbols which are used in your project (things like MYPROG_CONFIG_FILE_LOCATION, say). On the other hand, be aware that preprocessor definitions of _GLIBCXX_DEBUG and _GLIBCXX_DEBUG_PEDANTIC will affect binary compatibility (although g++ ensures that you will get a library version which works with them if you use them consistently).

With regards to your question: I would not expect to much impact on binary compatibility due to standard version, but it would hardly surprise me if the choice affects some pre-defined preprocessor symbols, in a way that would break binary compatibility in the library, much as if you'd compiled some of the modules with _GLIBCXX_DEBUG, and some without. It might work, but I wouldn't count on it.

Different versions of C/C++

How do I check what version of C and C++ I have?

C++ compiler should define a preprocessor's const __cplusplus, you can do std::cout<<__cplusplus<<'\n' to see in which Standard compiler is running.

how do I check what C/C++ compiler I am using?

Check in your IDE.

what is the relationship between different versions of C/C++ and different versions of compilers for C/C++?

Newer compiler may be adapted to newest Standards.

Does each compiler correspond to a different version of C/C++?

Does MS Office open only files made in newest version? Nope.

compiling source code on 2 different versions of gcc

That's a duplicate of: Why "Redefinition of typedef" error with GCC 4.3 but not GCC 4.6?

The answer is that gcc was changed to modify this check.

http://gcc.gnu.org/git/?p=gcc.git;a=commitdiff;h=ce3765bf44e49ef0568a1ad4a0b7f807591d6412

Sometimes, warnings for behavior defined in the language, but considered bad practice, are considered too strict because certain useful and/or harmless use cases are included in the warning. Compiler developers then try to fix in the opposite way, that is, reducing the amount of warnings. In this case, the change made the warning appear only when the second definition changed the typedef to a different, but compatible type.

Other current example is -Wshadow in gcc 4.8 just announced. In the release note, it says that -Wshadow won't warn anymore if a function name is shadowed by another.

See:
http://gcc.gnu.org/gcc-4.8/changes.html

Edit: how you can avoid this: either delete one of the definitions, or move it to a separate include file and delete both other definitions.

Same code compiled with different versions provides different result

It is obvious from your test() method that there are multiple execution threads involved. test() is in one execution thread, and the callbacks get invoked by the other execution thread.

Setting the bool flags in the other execution thread, and reading the same flags in the test() execution thread are not sequenced with each other.

The usual solution is to either use std::atomic_bool; or use a std::mutex to implement sequencing, and accessing the variables, either to set them in one execution thread, or read their current values in the other execution thread, only while the mutex is locked.



Related Topics



Leave a reply



Submit