I've encountered a case where I may want to use a C++ shared object library compiled with one version of gcc with some code that will be compiled with another version of gcc. In particular, I want to use methods that return some STL containers like std::string
and std::map
.
The gcc website and many old stackoverflow posts (e.g. here) discuss this issue. My current understanding is that
Most of the concern and most of the posts on this issue are about cross-compatibility between .so files and .dll files. This is very difficult, due to different compiler ABIs.
For cross-compatibility between .so files compiled with different versions of gcc (at least with gcc version >= 3.4), all you need to ensure is that the standard library API hasn't changed (and, if it has, there is dual ABI support).
My question has to do with how this works at a machine level. It seems like it is possible that gcc can change the header implementing std::string
, even if the library API has not changed, in order to make it more efficient or for other reasons. If so, then two different pieces of code are compiled with two different std::string
headers, and are basically defining two different classes with the same name. How can we be guaranteed that, when we pass a std::string
from code that uses one header to code that uses another, the object won't be mangled or misread somehow?
For example, suppose that I have the following files:
// File a.h:
#ifndef FILE_A
#define FILE_A
#include <string>
class X {
public:
std::string f();
};
#endif // FILE_A
// File a.cpp:
#include "a.h"
std::string X::f() {
return "hello world";
}
// File b.cpp:
#include <iostream>
#include <string>
#include "a.h"
int main() {
std::string x = X().f();
std::cout << x << std::endl;
}
(The only purpose of the class X
here is to introduce a bit more name-mangling into the shared object library while I am testing how this works.)
Now I compile these as follows:
/path/to/gcc/version_a/bin/g++ -fPIC -shared a.cpp -o liba.so
/path/to/gcc/version_b/bin/g++ -L. -la -o b b.cpp
When I execute b
, then b
has a definition of std::string
that comes from the header in version_b
. But the object that is produced by X().f()
relies on machine code that was compiled using a copy of the header that came from version_a
of gcc.
I don't understand very much about the low-level mechanics of compilers, linkers, and machine instructions. But it seems to me like we are breaking a fundamental rule here, which is that the definition of a class has to be the same every time it is used, and if not, we have no guarantee that the scenario above will work.
Edit: I think that the main resolution to my confusion is that the phrase "library API" means something much more general in this context than it does in the uses of the term "API" that I am used to. The gcc documentation seems to indicate, in a very vague way, that pretty much any change to the include files that implement the standard library can be considered a change in the library API. See the discussion in the comments on Mohan's answer for details.