-static-libstdc++ works on g++ but not on pure gcc?

This is technically not an answer, but a workaround for broken build systems.

I was working with a build system which for some very weird reason, used gcc instead of g++ to drive the build process, even for C++ objects. And I needed to have a binary with no dependency on anything other than glibc libraries. So, I ended up doing this:

$ cat <<EOF > gcc
#!/bin/bash
if [[ \$@ == *"-lstdc++"* ]]; then
    /full/path/to/g++ -static-libgcc -static-libstdc++ \$(echo \$@ | sed 's,-lstdc++,,g')
else
    /full/path/to/gcc -static-libgcc \$@
fi
EOF

$ chmod +x gcc

$ export PATH=$PWD:$PATH

The GCC manual, Link Options says:

-static-libstdc++

When the g++ program is used to link a C++ program, it normally automatically links against libstdc++. If libstdc++ is available as a shared library, and the -static option is not used, then this links against the shared version of libstdc++. That is normally fine. However, it is sometimes useful to freeze the version of libstdc++ used by the program without going all the way to a fully static link. The -static-libstdc++ option directs the g++ driver to link libstdc++ statically, without necessarily linking other libraries statically.

This makes clear that the option -static-libstdc++ is meaningful only to the g++ compiler driver, not gcc or any other.

On the other hand the option -l<name> is meaningful and means the same thing to all GCC compiler drivers. On that basis it is not surprising that:

gcc file.cc -lstdc++ -static-libstdc++

has the same meaning as:

gcc file.cc -lstdc++ 

However, that observation does not truly illuminate why the first of those commandlines dynamically links libstdc++:-

-static-libstdc++ is meaningful only to g++ because only g++ links libstdc++ automatically. So it is only for g++ that the question arises whether the automatically linked libstdc++ will be the dynamic version or the static version. The dynamic version is the default: -static-libstdc++ insists on the static version.

The automatic linking of libstdc++ by g++ means this: g++ silently appends -lstdc++ to whatever linkage options you specify (along with quite a lot of other boiler-plate for a C++ linkage). You can reveal all the boilerplate by requesting verbose linkage (g++ ... -Wl,-v ...).

By itself, the appended -lstdc++ will cause the linker to link the dynamic version of libstdc++, per its default behaviour. The only difference made by -static-libstdc++ is that in the place where -lstdc++ would otherwise be silently passed to the linker, the options:

-Bstatic -lstdc++ -Bdynamic

are silently passed to it instead. These tell the linker:

  • -Bstatic: Do not link dynamic libraries until further notice
  • -lstdc++: Link libstdc++
  • -Bdynamic: Link dynamic libraries until further notice.

You see how that works to secure the static linkage of libstdc++ without out side-effects on the linkage of any other library.

But you can also see that the automatic linkage of libstdc++, whether dynamically or statically, has no retroactive effect on the linkage of any libraries you have specified yourself.

Hence, if your linkage already includes -lstdc++ before any boiler-plate options are silently appended by the compiler driver, then libstdc++ will be linked in just the same way as any -l<name> at that position in the linkage sequence. And if silently appended boiler-plate options result in -lstdc++ reappearing later in the linkage sequence, whether by itself or with the surroundings:

-Bstatic -lstdc++ -Bdynamic

then the later appearance will simply be redundant, because the library has already been linked.

So there is nothing peculiar about gcc that results in:

gcc file.cc -lstdc++ -static-libstdc++

producing a program in which libstdc++ is dynamically linked. So does

g++ file.cc -lstdc++ -static-libstdc++

or indeed:

g++ file.cc -static-libstdc++ -lstdc++

because the generated linker commandline is of the form:

... file.o -lstdc++ ... -Bstatic -lstdc++ -Bdynamic ...

where -Bstatic -lstdc++ -Bdynamic is too late to make any difference.

Check it out:

file.cc

#include <iostream>

int main()
{
    std::cout << "Hello World" << std::endl;
    return 0;
}

Compile and link normally and inspect the dynamic dependencies with ldd:

$ g++ -o prog file.cc
$ ldd prog
linux-vdso.so.1 =>  (0x00007ffede76a000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f42fa74c000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f42fa385000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f42fa07c000)
/lib64/ld-linux-x86-64.so.2 (0x0000558ab42bc000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f42f9e65000)

libstdc++.so is present.

Now just with -static-libstdc++:

$ g++ -o prog file.cc -static-libstdc++
$ ldd prog
linux-vdso.so.1 =>  (0x00007fff448d7000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fe5f7c71000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe5f78aa000)
/lib64/ld-linux-x86-64.so.2 (0x0000556ebf272000)

libstdc++.so is absent.

And finally with -static-libstdc++ -lstdc++:

$ g++ -o prog file.cc -static-libstdc++ -lstdc++
$ ldd prog
linux-vdso.so.1 =>  (0x00007ffd12de9000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fd5a1823000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fd5a145c000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fd5a1153000)
/lib64/ld-linux-x86-64.so.2 (0x000055bbe31c3000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fd5a0f3c000)

libstdc++.so is back.

(This is Linux, of course, but you'll find the same thing on Windows).

So whether you drive your linkage with g++ or gcc, the reason that

{gcc|g++} file.cc -lstdc++ ...

will result in libstdc++ being dynamically linked is simply that

{gcc|g++} file.cc -lfoo ...

will cause libfoo to be dynamically linked, if it can be, regardless of what ... is, provided only that ... does not contain the option -static.