c++ - C++ 时,G++ static 链接,导致分割错误,为什么?

  显示原文与译文双语对照的内容
89 5
#include <iostream>
#include <map>
#include <thread>
#define SIZE 1024
#define AMOUNT 100000
#define THREADS 4
class A
{
private:
 char a[SIZE];
};
void test()
{
 std::cout <<"test startn";
 std::map<int, A*> container;
 for(int i=0; i<AMOUNT; i++)
 {
 A* a = new A();
 std::pair<int, A*>p = std::make_pair(i, a);
 container.insert(p);
 }
 std::cout <<"test releasen";
 for(int i=0; i<AMOUNT; i++)
 {
 auto iter = container.find(i);
 delete iter->second;
 container.erase(iter);
 }
 std::cout <<"test endn";
}
int main()
{
 std::thread ts[THREADS];
 for(int i=0; i<THREADS; i++)
 {
 ts[i] = std::thread(test);
 }
 for(std::thread& x: ts)
 {
 x.join();
 }
 return 0;
}

上面是一个简单的C++ 代码。

编译使用:g++ -pthread -o one one.cpp -Wall -std=c++11 -O3

ldd one,有:

 linux-vdso.so.1 => (0x00007ffebafce000)
 libstdc++.so.6 =>/usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fb47352a000)
 libgcc_s.so.1 =>/lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fb473313000)
 libpthread.so.0 =>/lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fb4730f4000)
 libc.so.6 =>/lib/x86_64-linux-gnu/libc.so.6 (0x00007fb472d2a000)
 libm.so.6 =>/lib/x86_64-linux-gnu/libm.so.6 (0x00007fb472a22000)
/lib64/ld-linux-x86-64.so.2 (0x00005654c5112000)

运行 ./one,一切都正常。

然后我尝试一个 static 链接:g++ -pthread -o one one.cpp -Wall -std=c++11 -O3 -static

ldd one,有:

 not a dynamic executable

但当我运行它的时候,有些事情出错了。

test start
Segmentation fault (core dumped)

-g 编译,gdb显示:

wang[00:35][~/test]$ gdb one
GNU gdb (Ubuntu 7.10-1ubuntu2) 7.10
Copyright (C) 2015 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type"show copying"
and"show warranty" for details.
This GDB was configured as"x86_64-linux-gnu".
Type"show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type"help".
Type"apropos word" to search for commands related to"word"...
Reading symbols from one...done.
(gdb) run
Starting program:/home/wang/test/one 
[Thread debugging using libthread_db enabled]
Using host libthread_db library"/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff7ffa700 (LWP 3623)]
test start
[New Thread 0x7ffff77f8700 (LWP 3624)]
test start
[New Thread 0x7ffff6ff7700 (LWP 3625)]
test start
[New Thread 0x7ffff67f6700 (LWP 3626)]
test start
Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in?? ()
(gdb) 

为什么?

英镑的更新英镑

使用 boost::thread 库( boost版本:1.60 )

boost::thread 替换 std::thread,并创建 static 链接,

g++ -pthread -o one1 one.cpp -Wall -std=c++11 -O3 -I/opt/boost/include/-L/opt/boost/lib/-lboost_system -lboost_thread -static

没有出现问题 !

时间:原作者:0个回答

133 2

首先,解决方案这里将工作:

g++ -o one one.cpp -Wall -std=c++11 -O3 -static -pthread 
 -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

在使用 -pthread 时,编译器将链接到 pthread ( 根据平台,它确实定义了额外的MACROS,如 -D_REENTRANT,请参见这个问题。) 。

如果 -pthread 表示 -lpthread,那么在静态链接时为什么需要指定 -lpthread那么 Wl,--whole-archive 做什么?

了解弱符号

在Unix上,使用了文件格式,它具有弱的概念和强符号的概念。要从维基百科Wikipedia引用:

默认情况下,如果没有任何注释,对象文件中的符号为 链接期间,强符号可以重写相同 NAME的符号。相反,共享 NAME的两个强符号在链接时产生链接错误。

在动态和 static 库中有一个细微的差别。在 static 库中,连接器将停止在第一个符号,即使它是一个弱的,并停止寻找强大的。若要强制它查看所有符号( 就像对动态链接库所做的那样),ld 支持 --whole-archive 选项。

要从 man ld 引用:

在服务器的--whole-archive: 选项后面的每个归档中,包含每个对象文件,而不是搜索存档文件中所需的对象文件。通常用于将归档文件转换为共享库,强制将每个对象包括在生成的共享库中。这里选项可以使用多次。

通过解释 gcc,你必须将该选项作为 -Wl,--whole-archive 传递:

从gcc使用这里选项时的两个注释:首先,gcc不知道这个选项,所以你必须使用 -Wl 。-whole-archive 。它的次,不要忘记在归档列表之后使用 -Wl,因为gcc将自己的档案列表添加到链接中。

它还解释了如何把它关掉

--no-whole-archive: 关闭--whole-archive选项对后续存档文件的影响。

pthread和 libstdc+ + 中的弱符号

弱符号的一个用例是能够用优化的方法交换实现。另一种方法是使用存根,如果有必要,以后可以替换。

例如,POSIX要求 fputc ( printf 使用的 conceptionally 。) 是线程安全的,需要同步,这是成昂的。在单一线程环境中,你不希望支付成本。因此实现可以实现同步函数作为空存根,并将函数声明为弱符号。

随后,如果链接了多线程库( 。比如,pthread ),那么很明显,单线程支持不是预期的。链接多线程库时,链接器可以通过实际的同步函数( 定义为强符号并由线程库实现) 替换存根。另一方面,如果没有链接多线程库,则可以执行程序将使用同步函数的存根。

glibc ( 提供 fputc ) 和pthreads似乎使用了这个技巧。有关详细信息,请参阅关于glibc中弱符号用法的这个问题。上面的例子来自于这个答案。

你可以查看 nm,这与上面提到的答案一致。

$ nm/usr/lib/libc.a 2>/dev/null | grep pthread_mutex_lock
w __pthread_mutex_lock
... (repeats)

""代表"弱",所以静态链接的libc库包含 __pthread_mutex_lock 作为弱符号。静态链接的pthread库将它包含为一个强符号:

$ 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

回到示例程序

通过查看动态链接可执行文件的共享库依赖性,我的计算机上的ldd 输出几乎是相同的:

$ 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)

使用 ltrace 打印库调用,将导致以下输出:

$ 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) +++

例如调用 std::thread::join,它很可能在内部使用 pthread_join可以在 ldd 输出中列出的( 动态链接) 库中找到该符号,即 libstdc++.so.6libpthread.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

在动态链接的可执行文件中,链接器将用强符号替换弱符号。在本例中,我们必须为静态链接库强制使用相同的语义。这就是为什么-Wl,--whole-archive -lpthread -Wl,--no-whole-archive是必需的。

找出来有点像 trial-and-error 。至少我没有找到关于这个主题的清晰的文档。这是因为在Linux上链接 static 已经成为了一个边缘案例,而动态链接通常是关于如何使用库的标准方法。我看到和个人使用过一段时间的最极端的例子是链路的静态 is 。

附录:挂载程序的解决方案

如果你使用autotools作为构建系统,你需要解决办法,因为automake不允许你在LDADD中设置选项。不幸的是,你不能写:

(Makefile.am)
mytarget_LDADD = -Wl,--whole-archive -lpthread -Wl,--no-whole-archive

作为解决方法,你可以以通过在 configure.ac, 中定义标志并使用这些方法来避免检查:

(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@
原作者:
...