C++ Project Compiled with Modern Compiler, But Linked Against Outdated Libstdc++

C++ project compiled with modern compiler, but linked against outdated libstdc++

Anyway, the resulting artifacts appear to be linked with system default version of libstdc++:

Yes. The devtoolset-6-gcc-c++ package provides a custom version of GCC that uses a special linker script instead of a dynamic library for libstdc++.so. That means the binaries it produces do not depend on the newer libstdc++.so.6 and can be run on other CentOS machines that don't have devtoolset installed (i.e. they only have the older libstdc++ library from GCC 4.8).

Is this build environment configuration valid?

Yes. What you're seeing is completely normal, and how it's supposed to work.

The pieces of the newer C++ runtime from GCC 6.4.0 get statically linked into your binary, and at runtime it only depends on the old libstdc++.so which every CentOS system has installed.

That's the whole point of the devtoolset version of GCC.

Why is C++ executable running so much faster when linked against newer libstdc++.so?

rustyx was correct. It was the use of auto t1 = Clock::now(); in the loop that was causing the poor performance. Once I moved the timing to outside the loop (time the total time taken) then they run equally fast:

    const size_t numIterations = 10000000;
auto t1 = Clock::now();

for (size_t i = 0; i < numIterations; ++i) {
auto similarity = computeDotProductOptomized(v1, v2);
}

auto t2 = Clock::now();

std::cout << "Total Time Taken: " << std::chrono::duration_cast<std::chrono::milliseconds>(t2 - t1).count() << " ms\n";

Linking g++ 4.8 to libstdc++

When you link with your own gcc you need to add an extra run-time linker search path(s) with -Wl,-rpath,$(PREFIX)/lib64 so that at run-time it finds the shared libraries corresponding to your gcc.

I normally create a wrapper named gcc and g++ in the same directory as gcc-4.8 and g++-4.8 which I invoke instead of gcc-4.8 and g++-4.8, as prescribed in Dynamic linker is unable to find GCC libraries:

#!/bin/bash
exec ${0}SUFFIX -Wl,-rpath,PREFIX/lib64 "$@"

When installing SUFFIX and PREFIX should be replaced with what was passed to configure:

cd ${PREFIX}/bin && rm -f gcc g++ c++ gfortran
sed -e 's#PREFIX#${PREFIX}#g' -e 's#SUFFIX#${SUFFIX}#g' gcc-wrapper.sh > ${PREFIX}/bin/gcc
chmod +x ${PREFIX}/bin/gcc
cd ${PREFIX}/bin && ln gcc g++ && ln gcc c++ && ln gcc gfortran

(gcc-wrapper.sh is that bash snippet).


The above solution does not work with some versions of libtool because g++ -Wl,... -v assumes linking mode and fails with an error.

A better solution is to use specs file. Once gcc/g++ is built, invoke the following command to make gcc/g++ add -rpath to the linker command line (replace ${PREFIX}/lib64 as necessary):

g++ -dumpspecs | awk '/^\*link:/ { print; getline; print "-rpath=${PREFIX}/lib64", $0; next } { print }' > $(dirname $(g++ -print-libgcc-file-name))/specs

Why can some libraries built by older compilers link against modern code, and others cannot?

I will try to answer some integral parts, but be aware this answer could be incomplete. With more information from peers we will maybe be able to construct a full answer!

The simples kind of linking is linking towards a C library. Since there is no concept of classes and overloading function names, the compiler creators are able to create entry points to functions by their pure name. This seems to be pretty much quasi-standardized since, I myself, haven't encountered a pure C library not at least linkable to my projects. You can select this behaviour in C++ code by prepending a function declaration with extern "C". (This also makes it easy to link against a library from C# code) Here is a detailed explanation about extern "C". But as far as I am aware this behaviour is not standardized; it is just so simple - it seems - there is just one sane solution.

Going into C++ we start to encounter function, variable and struct names repeating. Lets just talk about overloaded functions here. For that compiler creators have to come up with some kind of mapping between void a(); void a(int x); void a(char x); ... and their respective library representation. Since this process also is not standardized (see this thread) and this process is far more complex than the 1 to 1 mapping of C, the ABIs of different compilers or even compiler versions can differ in any way.

Now given two compilers (or linkers I couldn't find a resource wich specifies wich one exactly is responsible for the mangling but since this process is not standardized it could be also outsourced to cthulhu) with different name mangling schemes, create following function entry points (simplified):

compiler1
_a_
_a_int_
_a_char_

compiler2
_a_NULL_
_a_++INT++_
_a_++CHAR++_

Different linkers will not understand the output of your particular process; linker1 will try to search for _a_int_ in a library containing only _a_++INT++_. Since linkers can't use fuzzy string comparison (this could lead to a apocalypse imho) it won't find your function in the library. Also don't be fooled by the simplicity of this example: For every feature like namespace, class, method etc. there has to be a method implemented to map a function name to a entry point or memory structure.

Given your example you are lucky you use libraries of the same publisher who coded some logic to detect old libraries. Usually you will get something along the lines of <something> could not be resolved or some other convoluted, irritating and/or unhelpful error message.

Some info and experience dump regarding Visual Studio and libraries in general:

  • In general the Visual C++ suite doesn't support crosslinked libs between different versions but you could be lucky and it works. Don't rely on it.
  • Since VC++ 2015 the ABI of the libraries is guaranteed by microsoft to be compatible as drescherjm commented: link to microsoft documentation
  • In general when using libraries from different suites you should always be cautious as n. 1.8e9-where's-my-share m. commented here (here is your share btw) about dependencies to other libraries and runtimes. In general in general not having the control over how libraries are built is a huge pita

Edit addressing memory layout incompatibilities in addition to Tzigs answer: different name mangling schemes seem to be partially intentional to protect users against linkage against incompatible libraries. This answer goes into detail about it. The relevant passage from gcc docs:

G++ does not do name mangling in the same way as other C++ compilers. This means that object files compiled with one compiler cannot be used with another.

This effect is intentional [...].

CMake uses gcc from devtoolset but links against wrong libgcc and libstd++

This is not a bug, but the feature! :)
RH DevToolset is not just an alternative compiler built on the side. It's libstdc++ is an LD script. The new C++11 (and above) symbols in your binary would be resolved via static library (built specially and shipped w/ DTS). However, old symbols (existed in the OS's shipped libstdc++) going to be resolved dynamically. That is why ldd shows you libstdc++ from the /usr/lib64! Also, it has Old C++ ABI by default... to make your modern C++ code compatible w/ old libstdc++ DSO.

This feature allows you to use modern C++ standards, and produce a binary which can be executed in the original OS (CentOS/RHEL) w/o any additional "redistributable" shared libraries.

JFYI, if you use strip, make sure you use it from DTS and not the "native" one (shipped in binutils from the OS)! Otherwise, really bad things can happen.

When is it necessary to use the flag -stdlib=libstdc++?

On Linux: In general, all commonly available linux distributions will use libstdc++ by default, and all modern versions of GCC come with a libstdc++ that supports C++11. If you want to compile c++11 code here, use one of:

  • g++ -std=c++11 input.cxx -o a.out (usually GNU compiler)
  • g++ -std=gnu++11 input.cxx -o a.out

On OS X before Mavericks: g++ was actually an alias for clang++ and Apple's old version of libstdc++ was the default. You could use libc++ (which included c++11 library support) by passing -stdlib=libc++. If you want to compile c++11 code here, use one of:

  • g++ -std=c++11 -stdlib=libc++ input.cxx -o a.out (clang, not GNU compiler!)
  • g++ -std=gnu++11 -stdlib=libc++ input.cxx -o a.out (clang, not GNU compiler!)
  • clang++ -std=c++11 -stdlib=libc++ input.cxx -o a.out
  • clang++ -std=gnu++11 -stdlib=libc++ input.cxx -o a.out

On OS X since Mavericks: libc++ is the default and you should not pass any -stdlib=<...> flag. Since Xcode 10, building against libstdc++ is not supported at all anymore. Existing code built against libstdc++ will keep working because libstdc++.6.dylib is still provided, but compiling new code against libstdc++ is not supported.

  • clang++ -std=c++11 input.cxx -o a.out
  • clang++ -std=gnu++11 input.cxx -o a.out


Related Topics



Leave a reply



Submit