gcc 编译时动态静态库的问题

一般来说,动态静态库的问题,可以参考这个文章,http://blog.csdn.net/ast_224/a…

gcc生成静态库和动态库

一、库文件简介

简单地说,库(Library)就是一组已经写好了的函数和变量、经过编译代码,是为了能够提高开发效率和运行效率而设计的。库分为静态库(Static Library)和共享库(Shared library)两类。静态库文件的扩展名是.a,共享库文件的扩展名是.so(在CYGWIN环境下,分别叫做.o和.dll)。共享库现在常常被叫做动态库,是由于很多人借用了MS Windows的DLL(Dynamic Linked Library)这个词。
(1)静态库
静态是指每个用到该库的应用程序都拥有一份自己的库拷贝;应用程序运行的时候,即使将库删除也没有问题,因为应用程序自己已经有了自己的拷贝。
(2)共享库
一个共享库有可能被多个所有应用程序共享。因此,对每个应用程序来说,即使不再使用某个共享库,也不应将其删除。此外,应用程序需要正确的环境变量设置(LD_LIBRARY_PATH),从而找到共享库所在的位置,否则,应用程序运行时会报告找不到这个库。

二、关于使用库的问题

如果库是已经编译好的,那么如何在开发、运行应用程序时使用呢?头文件和库文件所在的路径,必须通过适当的方式通知给编译器、链接器和相关的应用程序。
对于静态库来说,主要涉及开发工具,如gcc。例如,用gcc编译、链接时,需要通过适当的路径找到头文件和静态库文件;实现的方法有两种:
gcc的命令行参数(-I, -L)
shell的环境变量(C_INCLUDE_PATH, LIBRARY_PATH
对于共享库来说,程序在运行时,如果用到了动态库,也需要找到对应的动态库文件;实现的方法:
shell的环境变量(LD_LIBRARY_PATH)
1) gcc命令行参数(-I, -L)
默认情况下,gcc会自动搜索下面的路径:
对头文件:
/usr/local/include/
/usr/include/
对库文件:
/usr/local/lib/
/usr/lib/
但是由于系统管理员对系统安装路径有不同的配置,或者对于如64位系统等情况,上述路径对于一台具体的计算机来说可能不同。如果开发者还有自己工程所需的头文件和库文件,就要用gcc的-I和-L来指定对应的路径。如果需要链接库,还要用-l选项。
例如:如果工程涉及到GDBM(GNU DataBase Management)包,需要libgdbm库,而系统中安装GDBM的路径是:
头文件:/opt/gdbm-1.8.3/include
库文件:/opt/gdbm-1.8.3/lib/
那么,gcc的命令参数是:
$gcc … -I/opt/gdbm-1.8.3/include -L/opt/gdbm-1.8.3/lib –lgdbm
注意:为保证兼容性,必须坚决杜绝在C/C++源文件的#include语句中或者其他相关语句中使用上述路径。
2) shell环境变量(Environmental Variable)
除了用命令行参数,还可以用环境变量来指示gcc搜索适当的路径。而由于Shell的不同,环境变量的设置方法也不同。常用的Shell有Bash, Csh和Tcsh。
(1)Bash
对于Bash来说,除了由系统管理员配置的内容以外,每个用户的用户目录($HOME)下,有个.bash_profile文件。可在该文件内,增加下面的两个语句来设置GDBM头文件路径的环境变量:
C_INCLUDE_PATH=/opt/gdbm-1.8.3/include
export C_INCLUDE_PATH
类似地,在该文件内用下面的两个语句来设置库文件路径的环境变量:
LIBRARY_PATH=/opt/gdbm-1.8.3/lib
export LIBRARY_PATH
在.bash_profile中有了上述语句以后,就不用再使用-I和-L来搜索特定包的路径了。但是链接库的时候,还是要用-l选项。
$gcc … –lgdbm
在Bash下,要检查有什么样的环境变量,可用env命令。
$env
(2)Csh和Tcsh
如果是Csh或Tcsh,对环境变量的设置方法就不同了。在用户的($HOME)目录下,相关的一些文件如下:
.cshrc 每次进入Csh时的启动(Startup)文件
.tcshrc 每次进入Tcsh时的启动(Startup)文件(在Tcsh下,如果没有这个文件,系统会用.cshrc文件代替)
.login 每次登录Shell时的启动(Startup)文件
在Csh和Tcsh下,分为Shell变量和环境变量;前者是用来设置Shell本身的,而后者则是供其他程序使用的。一般习惯是:Shell变量在.cshrc中定义,而环境变量则在.login文件中定义。
定义Shell变量的方法是在.cshrc或.tcshrc中用set语句:
set history = 20
定义环境变量的方法是在.login文件中用setenv语句。对于上面关于GDBM的例子:
setenv C_INCLUDE_PATH /opt/gdbm-1.8.3/include
setenv LIBRARY_PATH /opt/gdbm-1.8.3/lib
在Csh和Tcsh下,可以用setenv命令来查看设置了哪些环境变量(如果要看Shell变量,要用set命令)。
注意:
– 设置Shell变量时要用“=”号;
– 设置环境变量时,变量名与实际值(这里是真实路径)之间没有“=”号;
– 不需要export。
3) 使用共享库
使用共享库的应用程序,要通过环境变量LD_LIBRARY_PATH找到对应的共享库文件。与其他环境变量一样,对LD_LIBRARY_PATH也要根据shell的种类和库文件的实际路径进行设置。但是,必须注意的是,与一般的环境变量不同,LD_LIBRARY_PATH的值,是已经安装了的所有共享库的路径,因此,在Bash下,不能简单地用下面的办法:
LD_LIBRARY_PATH=/opt/gdbm-1.8.3/lib 错误!
export LD_LIBRARY_PATH
而必须用:
LD_LIBRARY_PATH=/opt/gdbm-1.8.3/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH
这样,就把其他共享库的路径也一起加入进来了。同样地,在Csh和Tcsh下,
setenv LD_LIBRARY_PATH /opt/gdbm-1.8.3/lib:$LD_LIBRARY_PATH

三、关于库生成的问题
我们通常把一些公用函数制作成函数库,供其它程序使用。函数库分为静态库和动态库两种。静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。

(1)静态库
简单地说,静态库是一个目标文件的简单集合。因此,首先要解决目标文件。
第一步:将各函数代码所在的源文件编译成目录文件。
例如,对于myfunc.c, myproc.c
gcc -c myfunc.c myproc.c
将得到myfunc.o和myproc.o。
第二步:由ar(archive,归档的意思)把多个目标文件集合起来。
$ar -r libmyjob.a myfunc.o myproc.o
通常,静态库的命名方式应遵守libXXXXX.a格式。应用程序在使用静态库的时候,通常只需要把命名中的XXXXX部分传递给gcc即可。例如:
$gcc –o mywork –lmyjob …
意为让gcc(实际上是gcc调用ld)去连接一个名字为libmyjob.a(或者libmyjob.so)的库。如果库的命名不遵循libXXXXX.a的格式就找不到相应文件。
例子:创建静态库
hello.h为该函数库的头文件。hello.c是函数库的源程序,其中包含公用函数hello,该函数将在屏幕上输出”
hello XXX!”。main.c为测试库文件的主程序,在主程序中调用了公用函数hello。
程序1:
//hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
#endif
程序2:
//hello.c
#include
void hello(const char *name)
{
printf(“hello %s! /n”,name);
}
程序3:
//main.c
#include “hello.h”
int main()
{
hello(“everyone”);
return 0;
}

实现步骤:
第一步:必须将源程序hello.c通过gcc先编译成.o文件,生成hello.o(静态库/动态库,都是由.o文件创建的);
第二步:由.o文件创建静态库,生成libmyhello.a(静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a)创建静态库用ar命令;
第三步:在程序中使用静态库;(只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件)
第四步:删除静态库文件,程序照常运行,静态库中的公用函数hello已经连接到目标文件main中了。
运行:
[root@localhost moduletest]# ls
hello.c hello.h main.c
[root@localhost moduletest]# gcc -c hello.c
[root@localhost moduletest]# ls
hello.c hello.h hello.o main.c
[root@localhost moduletest]# ar crv libmyhello.a hello.o
a – hello.o
[root@localhost moduletest]# ls
hello.c hello.h hello.o libmyhello.a main.c
[root@localhost moduletest]# gcc main.c libmyhello.a -o main
[root@localhost moduletest]# ./main
hello everyone!
[root@localhost moduletest]# rm -f libmyhello.a
[root@localhost moduletest]# ls
hello.c hello.h hello.o main main.c
[root@localhost moduletest]# ./main
hello everyone!
[root@localhost moduletest]#

(2)共享库
共享库的构造复杂一些,通常是一个ELF格式的文件。可以有三种方法生成:
$ld -G
$gcc -shared
$libtool
用ld最复杂,用gcc -share就简单的多,但是-share并非在任何平台都可以使用。GNU提供了一个更好的工具libtool,专门用来在各种平台上生成各种库。
用gcc的-shared参数:
gcc –shared –o libmyjob.so myjob.o
这样,就通过myjob.o生成了共享库文件libmyjob.so。
特别地,在CYGWIN环境下,仍需要输出符合Windows命名的共享库(动态库),即libXXXXX.dll。如:
gcc –shared –o libmyjob.dll myjob.o

例子:创建动态库(延用上面的程序1,2,3)
实现步骤:
第五步:由.o文件创建动态库文件(命令:gcc -shared -fPCI -o libmyhello.so hello.o);
第六步:在程序中使用动态库;(在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。程序在运行时,会在/usr/lib和/lib等目录中查找需要的动态库文件。若找到,则载入动态库,否则将提示错误信息而终止程序运行。要将文件libmyhello.so复制到目录/usr/lib中)
运行:
[root@localhost moduletest]# ls
hello.c hello.h hello.o main.c
[root@localhost moduletest]# gcc -shared -fPIC -o libmyhello.so hello.o
[root@localhost moduletest]# ls
hello.c hello.h hello.o libmyhello.so main.c
[root@localhost moduletest]# gcc main.c libmyhello.so -o main
[root@localhost moduletest]# ls
hello.c hello.h hello.o libmyhello.so main main.c
[root@localhost moduletest]# ./main
./main: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
[root@localhost moduletest]# mv libmyhello.so /usr/lib
可以:
[root@localhost moduletest]# ls
hello.c hello.h hello.o main main.c
[root@localhost moduletest]# ./main
hello everyone!
[root@localhost moduletest]#
或者:
[root@localhost moduletest]# rm -f main
[root@localhost moduletest]# ls
hello.c hello.h hello.o main.c
[root@localhost moduletest]# gcc -Wall -g main.c -lmyhello -o main
[root@localhost moduletest]# ls
hello.c hello.h hello.o main main.c
[root@localhost moduletest]# ./main
hello everyone!
[root@localhost moduletest]#
注意:
当静态库和动态库同名时, gcc命令将优先使用动态库。

(3)库生成以后的配置
如果要把自己开发的库文件安装到操作系统中,需要有管理员权限:
(a) 把库文件复制到适当的目录:
可以把自己开发的动态连接库放到/usr/local/lib(或者/usr/lib),或放到其他目录,但不论放在那里,都必须与LIBRARY_PATH的值、LD_LIBRARY_PATH的值相一致。
(b) 修改相关的系统配置文件:
修改/etc/ld.so.conf,然后利用/sbin/ldconfig来完成。

Note:
编译参数解析
最主要的是GCC命令行的一个选项:
-shared 该选项指定生成动态连接库(让连接器生成T类型的导出符号表,有时候也生成弱连接W类型的导出符号),不用该标志外部程序无法连接。相当于一个可执行文件
l -fPIC:表示编译为位置独立的代码,不用此选项的话编译后的代码是位置相关的所以动态载入时是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
l -L.:表示要连接的库在当前目录中
l -ltest:编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
l LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
l 当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,不过如果没有root权限,那么只能采用输出LD_LIBRARY_PATH的方法了。
调用动态库的时候有几个问题会经常碰到,有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。

但是,奇葩的事情总是有的,我们现在面临的问题是,主程序是一个框架类型的可执行,这个框架跑起来之后,会用 dlopen 的方式,打开一个 so,业务逻辑在这个 so 里面,那么,自然的,这个 so 就是要用动态的方式来编译了,但是,这个 so 又依赖于 google pb,在部署的时候,环境并不保证有 pb,或者 pb 的版本并不保证匹配,那么,我们希望,把这个 pb 的库,静态的链接进来,也就是这个文章说道的,http://www.cnblogs.com/nobugto…

动态库(.so)链接静态库(.a)的情况总结
一般来说在链接时想要使用静态库有三种方法:
1、link时加上 -static 选项;当加上 -static选项后,gcc会把所有用到的库都做静态连接。
2、link时直接指定想要静态连接的.a文件的绝对路径。优点是除非.a文件不存在,否则肯定有效;缺点也是很明显,拿到其他机器上编译时,.a文件也必须在相同的路径下存放。
3、在要静态连接的库前指定-Bstatic ,在要动态连接的库前指定-Bdynamic选项。连接器在看到-Bstatic时会优于去找静态库,如果找不到再去找动态库。 -Bdynamic也是同样的情况。(可是我实验的结果是这两个选项根本不起作用,我的环境是centos 6.2 gcc4.7.2, 仔细看了gcc 文档,这两个选项是针对VxWorks平台的,所以不起作用。)

当我们要编译一个so提供给外部使用,这个so本身依赖一些第三方库。但是我们却希望so的使用者不用关心该so对其他库的依赖。很自然的是会想到在编译so的时候把依赖到的第三方库静态链接进来。
我在这样做的时候碰到了问题:指定-static选项时,link失败,错误提示说要用到的object文件应该用-fPIC选项重新编译才行(也就是说,只有用-fPIC选项编译的object文件能被link到.so里);当直接给出.a的绝对路径的时候link成功,但是.so里却并没有直正包含所用到的符合连接。针对碰到的问题,我做了一些实验。实验如下:
static.c

#include

const char* sz_static = “i’m a static str.”;

void print_niuzai_said()
{
printf(“in static lib, niu zai said, i’m happy!\n”);
}
dynamic.c

#include
#include “static.h”

void print_papa_said()
{
print_niuzai_said();
printf(“in dynamic lib, papa said, niu zai is wonderful!\n”);
}

main.c

#include
#include “dynamic.h”

int main(int argc, char** argv)
{
print_papa_said();
return 0;
}
分别用两组命令编译出了两个.a 文件
1、gcc -o static.o -c static.c
ar -r libstatic.a static.o
2、gcc -o static_shared.o -shared -fPIC -c static.c
ar -r libstatic_shared.a static_shared.o

然后用此命令 “gcc -o dynamic.o -c dynamic.c” 编译出dynamic.o文件
接下来就是本文的主题了,链接生成libdynamic.so.此时有这么几种情况:
1、使用 “gcc -o libdynamic.so -shared -fPIC -L. -lstatic dynamic.o “,连接成功,但.so里实际上没有static.o里的内容。
2、使用”gcc -o libdynamic.so dynamic.o -shared -fPIC -L. -lstatic”,连接失败
3、使用 “gcc -o libdynamic.so -shared -fPIC -L. -lstatic_shared dynamic.o”,连接成功,但.so里实际上没有static.o里还是没有的内容。

4、使用 “gcc -o libdynamic.so dynamic.o -shared -fPIC -L. -lstatic_shared”,连接成功,.a的内容被连接到了.so里面。

另外,链接静态生成可执行程序时,静态库是不是用 “-shared -fPIC” 选项编译产生的没有影响。都能正常生成可执行程序。
综合以上情况,总结如下:
1、动态连接库中用到的object文件必须是用 “-shared -fPIC”选项编译产生的,否则连接时要么报错,要么被忽略。
2、静态库中的object文件最好也用”-shared -fPIC”选项编译,这样静态库就可以同时被连接到.so 或者可执行性文件中。
3、动态库只能连接用”-shared -fPIC”选项编译出来的静态库(和第1点是同一件事)
4、连接选项的顺序对连接器的行为有重要影响!

静态库使用-shared -fPIC 连接命令 连接执行结果 动态库结果 最终结果
否 gcc -o libdynamic.so -shared -fPIC -L. -lstatic dynamic.o 成功 静态库内容没有被连接 失败
否 gcc -o libdynamic.so dynamic.o -shared -fPIC -L. -lstatic 失败 失败
是 gcc -o libdynamic.so -shared -fPIC -L. -lstatic_shared dynamic.o 成功 静态库内容没有被连接 失败
是 gcc -o libdynamic.so dynamic.o -shared -fPIC -L. -lstatic_shared 成功 静态库内容被连接 成功

直接链接 lib.a 的文件,会报错,要求 fPIC,看到这个文章,http://hi.baidu.com/88908775/i…,里面提供了两种方式,一个改 configure,一个是改 Makefile,改 Makefile 在我这个尝试了不行,改 configure 可以

环境:
gcc (GCC) 4.1.2 20070115 (prerelease) (SUSE Linux) 64位

google protobuf 2.4.1

问题:
在使用libprotobuf.a文件时,编译错误
libprotobuf.a(common.o): relocation R_X86_64_32S against `std::basic_string, std::allocator >::_Rep::_S_empty_rep_storage’ can not be used when making a shared object; recompile with -fPIC

原因:
-fPIC 是个神马东东呢?
选项 -fPIC
PIC是Position-Independent-Code的缩写。在计算机系统中,PIC和PIE(Position-IndependentExecutable)是可以在主存中不同位置执行的目标代码。PIC经常被用在共享库中,这样就能将相同的库代码为每个程序映射到一个位置,不用担心覆盖掉其他程序或共享库。

因为so动态库编译的时候加上了 -fPIC,但是连接的 libprotobuf.a文件并不是 -fPIC生成的,所以就报错了。
那就是说连接的 libprotobuf.aa 文件,也需要加上 -fPIC 选项进行编译了。

解决方法:

以文本形式打开google buff代码目录下的configure文件,在把第2575至2578行修改为如下:
if test “x${ac_cv_env_CFLAGS_set}” = “x”; then : CFLAGS=”-fPIC” fi if test “x${ac_cv_env_CXXFLAGS_set}” = “x”; then : CXXFLAGS=”-fPIC”
再次执行configure:
configrue –disable-shared
make
make install
编译完成后,使用libprotobuf.a文件,以上问题解决。

或者修改 google protobuf的 Makefile文件, 加上 -fPIC 选项,重新编译生成 libprotobuf.a文件

参考资料:

详细说明-fPIC的工作原理: http://blog.csdn.net/chrisniu1…

-DPIC 是一个宏定义预处理标识 http://gcc.gnu.org/ml/gcc-help…

http://www.lingcc.com/2009/11/…
http://canlynet.blog.163.com/b…
编译动态库时遇到如下错误:

… relocation R_X86_64_32 against `a local symbol’ can not be used when making a shared object; recompile with -fPIC
… could not read symbols: Bad value

解决办法编译器已经提示了:recompile with -fPIC
但是我们应该重新编译谁带上这个参数呢?经过我几番折腾,发现如下情况:
1、编译.o文件的时候,没有加参数-fPIC,这问题个在gcc version 3.4.6版本没有发生,可能那个编译器默认都给加上吧。
2、当前程序中有用到某个静态库,那个静态库编译.o文件的时候没有加上-fPIC(静态库其实就是.o文件打包)。补充一点:我发现手写Makefile时即使不加-fPIC也没有这个问题,这一点很郁闷,这个只在用automake工具编译出.a文件时才出现过。
知道原因了,解决办法自然有了,保证你编译.o文件的时候,都加上-fPIC,这样你才能编译出动态库来。

http://bbs.chinaunix.net/threa…
从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见如下

`-shared’
Produce a shared object which can then be linked with other
objects to form an executable. Not all systems support this
option. For predictable results, you must also specify the same
set of options that were used to generate code (`-fpic’, `-fPIC’,
or model suboptions) when you specify this option.(1)

Leave a Reply

Your email address will not be published. Required fields are marked *