The first is the solution. This will work:
g++ -o one one.cpp -Wall -std=c++11 -O3 -static -pthread \ -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
When you use -pthread , the compiler already references pthread (and depending on the platform it defines additional macros such as -D_REENTRANT , see this question for more details).
So, if -pthread means -lpthread , why do you need to specify -lpthread when linking statically? And what does Wl,--whole-archive do Wl,--whole-archive ?
Understanding weak characters
Unix uses the ELF file format, which has the concept of weak and strong characters . To quote the Wikipedia page:
By default, without annotation, the character in the object file is strong . During linking, a strong character can override a weak character with the same name. In contrast, two strong characters that have a name result in a communication error during the link.
There is a subtle difference between dynamic and static libraries. In static libraries, the linker stops at the first character, even if it is weak, and stops looking for strong ones. To make it look at all the characters (as would be done for a dynamically linked library), ld supports the --whole-archive option.
For a quote from man ld :
--whole-archive: For each archive specified on the command line after the -whole-archive option, include each object file in the archive in the link, and not search the archive for the necessary object files. This is typically used to turn an archive file into a shared library, forcing each object to be included in the resulting shared library. This option can be used several times.
The following explains that from gcc you must pass the option as -Wl,--whole-archive :
Two notes when using this option from gcc: firstly, gcc does not know about this option, so you need to use -Wl, -all-archive. Second, remember to use -Wl, -no-whole-archive after the list of archives, because gcc will add its own list of archives to your link, and you may not want this flag to affect them either.
And this explains how to disable it again:
--no-whole-archive: Disable the effect of the -whole-archive option for subsequent archive files.
Weak characters in pthread and libstdc ++
One option for using weak characters is the ability to replace implementations with optimized ones. Another is the use of plugs, which can subsequently be replaced if necessary.
For example, fputc ( conceptually used by printf ), POSIX is required to be thread safe and must be synchronized, which is expensive. In a single-threaded environment, you do not want to pay expenses. Thus, an implementation can implement synchronization functions as empty stubs and declare functions as weak characters.
Later, if a multi-threaded library is linked (e.g. pthread), it becomes apparent that single-thread support is not intended. When linking the multithreading library, the linker can then replace the stubs with actual synchronization functions (defined as strong characters and implemented by the thread library). On the other hand, if a multithreaded library is not connected, the executable will use stubs for the synchronization function.
glibc (providing fputc ), and pthreads seem to use this particular trick. See this question about using weak characters in glibc for more details. The example above is taken from this answer .
nm allows you to view it in detail, which is similar to the quoted answer above:
$ nm /usr/lib/libc.a 2>/dev/null | grep pthread_mutex_lock w __pthread_mutex_lock ... (repeats)
"w" means "weak", so the statically linked libc library contains __pthread_mutex_lock as a weak character. The statically linked pthread library contains it as a strong character:
$ nm /usr/lib/libpthread.a 2>/dev/null | grep pthread_mutex_lock U pthread_mutex_lock pthread_mutex_lock.o: 00000000000006a0 T __pthread_mutex_lock 00000000000006a0 T pthread_mutex_lock 0000000000000000 t __pthread_mutex_lock_full
Back to the sample program
By examining the shared library dependencies of a dynamically linked executable, I get almost the same ldd output on my machine:
$ ldd one linux-vdso.so.1 (0x00007fff79d6d000) libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x00007fcaaeeb3000) libm.so.6 => /usr/lib/libm.so.6 (0x00007fcaaeb9b000) libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fcaae983000) libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fcaae763000) libc.so.6 => /usr/lib/libc.so.6 (0x00007fcaae3bb000) /lib64/ld-linux-x86-64.so.2 (0x00007fcaaf23b000)
Listing library calls using ltrace yields the following output:
$ ltrace -C ./one std::ios_base::Init::Init()(0x563ab8df71b1, 0x7ffdc483cae8, 0x7ffdc483caf8, 160) = 0 __cxa_atexit(0x7fab3023bc20, 0x563ab8df71b1, 0x563ab8df7090, 6) = 0 operator new(unsigned long)(16, 0x7ffdc483cae8, 0x7ffdc483caf8, 192) = 0x563ab918bc20 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80) = 0 operator new(unsigned long)(16, 0x7fab2f6a1fb0, 0, 0x800000) = 0x563ab918bd70 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80) = 0 operator new(unsigned long)(16, 0x7fab2eea0fb0, 0, 0x800000) = 0x563ab918bec0 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80test start ) = 0 operator new(unsigned long)(16, 0x7fab2e69ffb0, 0, 0x800000) = 0x563ab918c010 std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)())(0x7ffdc483c990, 0x7ffdc483c998, 0x7fab2fa52320, 0x7fab2fa43a80test start test start ) = 0 std::thread::join()(0x7ffdc483c9a0, 0x7fab2de9efb0, 0, 0x800000test start test release test release test release test release test end test end test end test end ) = 0 std::thread::join()(0x7ffdc483c9a8, 0x7fab2eea19c0, 0x7fab2f6a2700, 0) = 0 std::thread::join()(0x7ffdc483c9b0, 0x7fab2e6a09c0, 0x7fab2eea1700, 0) = 0 std::thread::join()(0x7ffdc483c9b8, 0x7fab2de9f9c0, 0x7fab2e6a0700, 0) = 0 +++ exited (status 0) +++
As an example, std::thread::join is called, which is most likely to use pthread_join internally. This symbol can be found in the (dynamically linked) libraries listed in the ldd output, namely libstdc++.so.6 and libpthread.so.0 :
$ nm /usr/lib/libstdc++.so.6 | grep pthread_join w pthread_join $ nm /usr/lib/libpthread.so.0 | grep pthread_join 0000000000008280 T pthread_join
In a dynamically linked executable, the linker replaces weak characters with strong characters. In this example, we must apply the same semantics to statically linked libraries. That's why -Wl,--whole-archive -lpthread -Wl,--no-whole-archive is required -Wl,--whole-archive -lpthread -Wl,--no-whole-archive .
Finding this out is a bit of a trial error. At least I did not find clear documentation on this. I suppose this is because the static linking to Linux has become more of a cornerstone , while dynamic linking is often the canonical approach to using libraries (see Static linking and dynamic linking for a comparison). The most extreme example I've seen and personally struggled for a while to get it to work is to bind TBB statically .
Application: Workaround for Autotools
If you use autotools as a build system, you need a workaround since automake does not allow you to set parameters in LDADD. Unfortunately, you cannot write:
(Makefile.am) mytarget_LDADD = -Wl,--whole-archive -lpthread -Wl,--no-whole-archive
As a workaround, you can avoid checking by specifying the flags in the configure.ac file and using them as follows:
(configure.ac) WL_WHOLE_ARCHIVE_HACK="-Wl,--whole-archive" WL_NO_WHOLE_ARCHIVE_HACK="-Wl,--no-whole-archive" AC_SUBST(WL_WHOLE_ARCHIVE_HACK) AC_SUBST(WL_NO_WHOLE_ARCHIVE_HACK) (Makefile.am) mytarget_LDADD = @ WL_WHOLE_ARCHIVE_HACK@ -lpthread @ WL_NO_WHOLE_ARCHIVE_HACK@