静态库/动态库 与 单线程/多线程。

假如你正在写一个C/C++的程序,那么通常一定需要链接到libc或glibc或mscvrt.采用哪种方式链接?静态?动态?

微软这无耻小儿,原本他提供了8种crt,静态的,动态链接的,单线程的,多线程的,调试的,非调试的,并且干脆禁止混联。可微软在vc 2005及后续版本中完全删除了单线程的crt,强迫大家在多线程的环境下工作。vc并且警告大家不要在dll中静态链接crt,理由是crt的静态变量在每个实例中都有一份,比如你在1.dll中调用了srand(0),那么在2.dll中还得srand一次。谁在写exe的时候不顺手扔两个dll进去呢?于是大多数情况下,对于crt,仅剩的选择就是“debug版的多线程dll”和"release版的多线程dll"?还有,如果我在用C++标准库(vc的全称是vc++,它是一个C++编译器而不是C编译器),那么它强迫我打开RTTI。可是如果一个C的程序员批判C++慢,他一定会先从RTTI开口。

solaris也很激进,从SunOS 5.10开始它放弃了libc.a(这个比我年龄还大的东西),并把libpthread合并到libc中。
放弃静态库的理由是:
1、为了使用线程库,必须在main函数开始之前进行一些必要的初始化工作,这个被放在了动态链接的init阶段。静态库没有这个阶段。
2、静态链接的程序不知道自身是否处于多线程的运行环境中,于是编译器不知道以何种方式去实现TSS(thread-specific storage),为此,编译的时候还得加上-D_MT -DXXXXXX
3、如果一个静态链接的程序用dlopen打开一个动态链接库,前者已经装载了libc中pthread的一部分,而后者需要用到pthread于是又要载入libc.so,那么对于符号的解析将产生很大的困难。
4、如果一个单线程的静态库突然dlopen一个需要使用多线程环境的so,那么那些为了多线程而准备的初始化函数怎么办?何时执行它们?

单线程自然有单线程的好处,假如我要写一个tcp echo server这样的简单服务,那么我只需要单线程就够了,并且,大多数shell命令也只需要单线程,干嘛引进多余的复杂度?静态链接自然也有静态链接的好处,让chroot变得容易,当系统崩溃时也不至于连个sh都得不到(如果sh在/bin下而libc.so被放在了/usr/lib下但是/usr挂载失败)。

那么,聪明的freebsd是怎么做的?

在thr_init.c中定义了一个有趣的变量:

extern int _thread_autoinit_dummy_decl;
int _thread_autoinit_dummy_decl = 0;

一般而言,程序最终必然会需要调用libc的exit。而在libc的exit中放了一个这样的外部声明

extern int _thread_autoinit_dummy_decl;

由于_thread_autoinit_dummy_decl的存在,会强制的把thr_init.c中的一些东西链接进去。

于是,如果编译的时候没有加-pthread,那么_thread_autoinit_dummy_decl是一个weak object,否则,它会被放入到BSS中。(因为初始值是0,初始值不是0的才会被放入.data中)

同时,thr_init.c还放了些弱引用的声明。(_thread_autoinit_dummy_decl的存在是为了使得linker必然处理这些弱引用声明??)

pthread中的函数可以大致分为这么两类:
1、_pthread_attr_init 这样的函数。如果是多线程的程序,编译的时候一般都会定义-DPIC并链接libpthread,那么 _pthread_attr_init 这样的函数自然可以被顺利解析到。否则,thr_init.c中采用弱引用的方式
__asm (".globl " _pthread_attr_init)
这样定义一个符号。等待libpthread被link进来的时候替换掉。
2、至于_pthread_create这样的函数,在单线程的程序中肯定用不到。所以,如果静态链接一个单线程的程序,符号表中是没有_pthread_create的。至于初始化,则是在调用pthread相关函数的时候判断_thr_initial是否为0 来决定是否初始化线程库。

啊,还有很多很多细节我还不明白。但是快4点了,明天还得上班,先睡了

此博客中的热门博文

少写代码,多读别人写的代码

在windows下使用llvm+clang

tensorflow distributed runtime初窥