gcc 生成 a静态库时,能不能设置默认gcc依赖包库

本文在原文的基础上做一些详细验证,部分内容排版稍有调整
本文地址:
一、基本概念
1.1什么是库
在windows平台和linux平台下都大量存在着库。
本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
由于windows和linux的平台不同(主要是编译器、汇编器和连接器的不同),因此二者库的二进制是不兼容的。
本文仅限于介绍linux下的库。
1.2库的种类
linux下的库有两种:静态库和共享库(动态库)。
二者的不同点在于代码被载入的时刻不同。
静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。
共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。
1.3库存在的意义
库是别人写好的现有的,成熟的,可以复用的代码,你可以使用但要记得遵守许可协议。
现实中每个程序都要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
共享库的好处是,不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
1.4库文件是如何产生的在linux下
静态库的后缀是.a,它的产生分两步
1. 由源文件编译生成一堆.o,每个.o里都包含这个编译单元的符号表
2. ar命令将很多.o转换成.a,成为静态库
动态库的后缀是.so,它由gcc加特定参数编译产生。
具体方法参见后文实例。
1.5库文件是如何命名的,有没有什么规范
在linux下,库文件一般放在/usr/lib和/lib下,
静态库的名字一般为libxxxx.a,其中xxxx是该lib的名称
动态库的名字一般为libxxxx.so.major.minor,xxxx是该lib的名称,major是主版本号, minor是副版本号
1.6如何知道一个可执行程序依赖哪些库
ldd命令可以查看一个可执行程序依赖的共享库,
例如$ ldd /bin/lnlibc.so.6
=& /lib/libc.so.6 (0×)/lib/ld-linux.so.2
=& /lib/ld- linux.so.2 (0×)
可以看到ln命令依赖于libc库和ld-linux库
1.7可执行程序在执行的时候如何定位共享库文件
当系统加载可执行代码时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径。
此时就需要系统动态载入器(dynamic linker/loader)
对于elf格式的可执行程序,是由ld-linux.so*来完成的,它先后搜索elf文件的 DT_RPATH段—环境变量LD_LIBRARY_PATH—/etc/ld.so.cache文件列表—/lib/,/usr/lib目录找到库文件后将其载入内存
如:export LD_LIBRARY_PATH='pwd'
将当前文件目录添加为共享目录
1.8在新安装一个库之后如何让系统能够找到他
如果安装在/lib或者/usr/lib下,那么ld默认能够找到,无需其他操作。
如果安装在其他目录,需要将其添加到/etc/ld.so.cache文件中,步骤如下
1.编辑/etc/ld.so.conf文件,加入库文件所在目录的路径
2.运行ldconfig 目录名字,该命令会重建/etc/ld.so.cache文件
二、用gcc生成静态和动态链接库的示例
我们通常把一些公用函数制作成函数库,供其它程序使用。
函数库分为静态库和动态库两种。
静态库在程序编译时会被连接到目标代码中,程序运行时将不再需要该静态库。
动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入,因此在程序运行时还需要动态库存在。
本文主要通过举例来说明在Linux中如何创建静态库和动态库,以及使用它们。
为了便于阐述,我们先做一部分准备工作。
2.1准备好测试代码
hello.h(见程序1)为该函数库的头文件。
hello.c(见程序2)是函数库的源程序,其中包含公用函数hello,该函数将在屏幕上输出”Hello XXX!”。
main.c(见程序3)为测试库文件的主程序,在主程序中调用了公用函数hello。
测试机器环境:gcc version 4.8.4 ;ubuntu14.04.3
三个程序放在文件夹~/testso中
程序1: hello.h
#ifndef HELLO_H
#define HELLO_H
void hello(const char *name);
程序2:hello.c
#include &stdio.h&
void hello(const char *name) {
printf("Hello %s!\n", name);
程序3:main.c
int main()
hello("everyone");
2.2问题的提出
注意:这个时候,我们编译好的hello.o是无法通过gcc –o 编译的,这个道理非常简单,
hello.c是一个没有main函数的.c程序,因此不够成一个完整的程序,如果使用gcc –o 编译并连接它,GCC将报错。
无论静态库,还是动态库,都是由.o文件创建的。因此,我们必须将源程序hello.c通过gcc先编译成.o文件。
这个时候我们有三种思路:
通过编译多个源文件,直接将目标代码合成一个.o文件。
通过创建静态链接库libmyhello.a,使得main函数调用hello函数时可调用静态链接库。
通过创建动态链接库libmyhello.so,使得main函数调用hello函数时可调用静态链接库。
2.3思路一:编译多个源文件
在系统提示符下键入以下命令得到hello.o文件。
gcc -c hello.c
为什么不使用gcc –o hello hello.c这个道理我们之前已经说了,使用-c是什么意思呢?这涉及到gcc 编译选项的常识。
gcc –o是将.c源文件编译成为一个可执行的二进制代码(-o选项其实是制定输出文件文件名,如果不加-c选项,gcc默认会编译连接生成可执行文件,文件的名称有-o选项指定),这包括调用作为GCC内的一部分真正的C编译器(ccl),以及调用GNU C编译器的输出中实际可执行代码的外部GNU汇编器(as)和连接器工具(ld)。
gcc –c是使用GNU汇编器将源文件转化为目标代码之后就结束,在这种情况下,只调用了C编译器(ccl)和汇编器(as),而连接器(ld)并没有被执行,所以输出的目标文件不会包含作为Linux程序在被装载和执行时所必须的包含信息,但它可以在以后被连接到一个程序。
我们运行ls命令看看是否生存了hello.o文件。
hello.c hello.h hello.o main.c
在ls命令结果中,我们看到了hello.o文件,本步操作完成。
同理编译main
$gcc –c main.c
将两个文件链接成一个.o文件。
$gcc hello.o main.o -o hello
Hello everyone!
2.4思路二:静态链接库
下面我们先来看看如何创建静态库,以及使用它。
静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。例如:我们将创建的静态库名为myhello,则静态库文件名就是libmyhello.a。在创建和使用静态库时,需要注意这点。创建静态库用ar命令。
在系统提示符下键入以下命令将创建静态库文件libmyhello.a。
$ ar rcs libmyhello.a hello.o
我们同样运行ls命令查看结果:
hello.h hello.o libmyhello.a main.c
ls命令结果中有libmyhello.a。
静态库制作完了,如何使用它内部的函数呢?只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件,因此,我们在写需要连接的库时,只写名字就可以,如libmyhello.a的库,只写:-lmyhello
在程序3:main.c中,我们包含了静态库的头文件hello.h,然后在主程序main中直接调用公用函数hello。下面先生成目标程序hello,然后运行hello程序看看结果如何。
$ gcc -o hello main.c -static -L. -lmyhello
Hello everyone!
我们删除静态库文件试试公用函数hello是否真的连接到目标文件 hello中了。
$ rm libmyhello.a
Hello everyone!
程序照常运行,静态库中的公用函数已经连接到目标文件中了。
静态链接库的一个缺点是,如果我们同时运行了许多程序,并且它们使用了同一个库函数,这样,在内存中会大量拷贝同一库函数。这样,就会浪费很多珍贵的内存和存储空间。使用了共享链接库的Linux就可以避免这个问题。
共享函数库和静态函数在同一个地方,只是后缀有所不同。比如,在一个典型的Linux系统,标准的共享数序函数库是/usr/lib/libm.so。
当一个程序使用共享函数库时,在连接阶段并不把函数代码连接进来,而只是链接函数的一个引用。当最终的函数导入内存开始真正执行时,函数引用被解析,共享函数库的代码才真正导入到内存中。这样,共享链接库的函数就可以被许多程序同时共享,并且只需存储一次就可以了。共享函数库的另一个优点是,它可以独立更新,与调用它的函数毫不影响。
2.5思路三、动态链接库(共享函数库)
我们继续看看如何在Linux中创建动态库。我们还是从.o文件开始。
动态库文件名命名规范和静态库文件名命名规范类似,也是在动态库名增加前缀lib,但其文件扩展名为.so。例如:我们将创建的动态库名为myhello,则动态库文件名就是libmyhello.so。用gcc来创建动态库。
在系统提示符下键入以下命令,得到动态库文件libmyhello.so。
$ gcc -shared -fPIC -c hello.c
注:chenjim添加,原文没有,下一行会报错
$ gcc -shared -fPIC -o libmyhello.so hello.o
“PIC”命令行标记告诉GCC产生的代码不要包含对函数和变量具体内存位置的引用,这是因为现在还无法知道使用该消息代码的应用程序会将它连接到哪一段内存地址空间。这样编译出的hello.o可以被用于建立共享链接库。建立共享链接库只需要用GCC的”-shared”标记即可。
我们照样使用ls命令看看动态库文件是否生成。
hello.cpp hello.h hello.o libmyhello.so main.cpp
调用动态链接库编译目标文件。
在程序中使用动态库和使用静态库完全一样,也是在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明动态库名进行编译。我们先运行gcc命令生成目标文件,再运行它看看结果。
如果直接用如下方法进行编译,并连接:
$ gcc -o hello main.c -L. -lmyhello
(使用”-lmyhello”标记来告诉GCC驱动程序在连接阶段引用共享函数库libmyhello.so。”-L.”标记告诉GCC函数库可能位于当前目录)
./hello: error while loading shared libraries: libmyhello.so: cannot open shared object file: No such file or directory
错误提示,找不到动态库文件libmyhello.so。程序在运行时,会查找需要的动态库文件,顺序参考后文介绍。若找到,则载入动态库,否则将提示类似上述错误而终止程序运行。有多种方法可以解决,
(1)我们将文件 libmyhello.so复制到目录/usr/lib中,再试试。
$ sudo mv libmyhello.so /usr/lib
(2)既然连接器会搜寻LD_LIBRARY_PATH所指定的目录,那么我们可以将这个环境变量设置成当前目录:
export LD_LIBRARY_PATH=$(pwd)
(3)sudo ldconfig ~/testso
当用户在某个目录下面创建或拷贝了一个动态链接库,若想使其被系统共享,可以执行一下”ldconfig
目录名”这个命令。此命令的功能在于让ldconfig将指定目录下的动态链接库被系统共享起来,意即:在缓存文件/etc/ld.so.cache中追加进指定目录下的共享库.本例让系统共享了~/tests目录下的动态链接库。
下面的这个错误我没有遇到,不过也记录下,给遇到的人:
这步后我没有成功,报错内容如下:/hello: error while loading shared libraries: /usr/lib/libmyhello.so: cannot restore segment prot after reloc: Permission denied
google了一下,发现是SELinux搞的鬼,解决办法有两个:
1. chcon -t texrel_shlib_t
/usr/lib/libmyhello.so
(chcon -t texrel_shlib_t “你不能share的库的绝对路径”)
2. $vi /etc/sysconfig/selinux file
$gedit /etc/sysconfig/selinux file
修改SELINUX=disabled
这也进一步说明了动态库在程序运行时是需要的。
可以查看程序执行时调用动态库的过程:
$ ldd hello
可以看到它是如何调用动态库中的函数的。
linux-vdso.so.1 =&
(0x00007fffe8f9b000)
libmyhello.so =& /home/chenjw/testso/libmyhello.so (0x00007fbe807d5000)
libc.so.6 =& /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbe)
/lib64/ld-linux-x86-64.so.2 (0x6c2000)
原文中说,使用静态库和使用动态库编译成目标程序使用的gcc命令完全一样,
可能因为我们的环境不一样,上文我多加了一行编译命令gcc -shared -fPIC -c hello.c,
所以原文的验证,那当静态库和动态库同名时,gcc命令会使用哪个库文件呢?
在我的环境没有意义,add by chenjim。
2.6编译参数解析
最主要的是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文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。
2.7 静态库链接时搜索路径顺序:
ld会去找GCC命令中的参数-L
再找gcc的环境变量LIBRARY_PATH
再找内定目录 /lib /usr/lib /usr/local/lib 这是当初compile gcc时写在程序内的
2.8动态链接时、执行时搜索路径顺序:
编译目标代码时指定的动态库搜索路径;
环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
配置文件/etc/ld.so.conf中指定的动态库搜索路径;
默认的动态库搜索路径/lib;
默认的动态库搜索路径/usr/lib。
2.9 有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径
三、linux开发之文件夹和应用程序
1. 应用程序(Applications)
应用程序通常都有固定的文件夹,系统通用程序放在/usr/bin,日后系统管理员在本地计算机安装的程序通常放在/usr/local/bin或者/opt文件夹下。除了系统程序外,大部分个人用到的程序都放在/usr/local下,所以保持/usr的整洁十分重要。当升级或者重装系统的时候,只要把/usr/local的程序备份一下就可以了。
一些其他的程序有自己特定的文件夹,比如X Window系统,通常安装在/usr/X11中,或者/usr/X11R6。GNU的编译器GCC,通常放置在/usr/bin或者/usr/local/bin中,不同的Linux版本可能位置稍有不同。
在C语言和其他语言中,头文件声明了系统函数和库函数,并且定义了一些常量。对于C语言,头文件基本上散落于/usr/include和它的子文件夹下。其他的编程语言的库函数分布在编译器定义的地方,比如在一些Linux版本中,X Window系统库函数分布在/usr/include/X11,GNU C++的库函数分布在/usr/include/g++。这些系统库函数的位置对于编译器来说都是“标准位置”,即编译器能够自动搜寻这些位置。
如果想引用位于标准位置之外的头文件,我们需要在调用编译器的时候加上-I标志,来显式的说明头文件所在文件夹。比如,
$ gcc -I/usr/openwin/include hello.c
会告诉编译器除了标准位置外,还要去/usr/openwin/include看看有没有所需的头文件。详细情况见编译器的使用手册(man gcc)。
3. 库函数(Library Files)
库函数就是函数的仓库,它们都经过编译,重用性不错。通常,库函数相互合作,来完成特定的任务。比如操控屏幕的库函数(cursers和ncursers库函数),数据库读取库函数(dbm库函数)等。
系统调用的标准库函数一般位于/lib以及/usr/lib。C编译器(精确点说,连接器)需要知道库函数的位置。默认情况下,它只搜索标准C库函数。
库函数文件通常开头字母是lib。后面的部分标示库函数的用途(比如C库函数用c标识, 数学库函数用m标示),小数点后的后缀表明库函数的类型:
.a 指静态链接库
.so 指动态链接库
去/usr/lib看一下,你会发现,库函数都有动态和静态两个版本。
与头文件一样,库函数通常放在标准位置,但我们也可以通过-L标识符,来添加新的搜索文件夹,-l指定特定的库函数文件。比如
$ gcc -o x11fred -L/usr/openwin/lib x11fred.c -lX11
上述命令就会在编译期间,链接位于/usr/openwin/lib文件夹下的libX11函数库,编译生成x11fred。
静态链接库(Static Libraries)
最简单的函数库就是一些函数的简单集合。调用库函数中的函数时,需要在调用函数中include定义库函数的头文件。我们用-l选项添加标准函数库之外的函数库。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:66617次
排名:千里之外
原创:31篇
转载:14篇
(3)(1)(1)(4)(7)(2)(4)(4)(1)(1)(1)(1)(1)(3)(1)(1)(1)(4)> gcc 生成.o文件解决方案
gcc 生成.o文件解决方案
mzzbank & &
发布时间: & &
浏览:7 & &
回复:4 & &
悬赏:0.0希赛币
gcc 生成.o文件怎样设置gcc选项,使gcc可以编译出test.o文件。我的test.c引用了lib1.a静态库,如果我的test.c中有main函数,用gcc test.c lib1.a -o test.o命令可以生成test.o文件,但是,如果test.c中没有main函数就报“undefined reference to 'main'”
.o是中间文件啊,不需要main函数gcc test.c lib1.a
n0th1ng & &
14:11:30 & &
& & (0)(1)引用gcc -c test.c
14:11:30 & &
& & (0)(1)引用gcc 生成可执行文件 是需要main的,所以,你如果只是想把 lib.a 和 test.c打包到一起编成动态链接库 .so 应该不会出现这个问题..
wendss2 & &
14:11:30 & &
& & (0)(1)引用
1 #include &stdio.h& 2 #include &stdlib.h& 3&
4 int test() 5 { 6
printf(&this is a test\n&); 7
return 0; 8 }gcc -o lib -c lib.c 这个没问题wendy_kudouran & &
14:11:30 & &
& & (0)(1)引用
本问题标题:
本问题地址:
温馨提示:本问答中心的任何言论仅代表发言者个人的观点,与希赛网立场无关。请对您的言论负责,遵守中华人民共和国有关法律、法规。如果您的言论违反希赛网问答中心的规则,将会被删除。
暂无合适的专家
&&&&&&&&&&&&&&&
希赛网 版权所有 & &&1760人阅读
linux系统编程(4)
本文是转载,原文是:
一、基本概念
1.1、什么是库
&&&&&&&在 windows 平台和 linux 平台下都大量存在着库。
&&&&&& 本质上来说库是一种可执行的二进制代码(但不可以独立执行),可以被操作系统载入内存执行。
&&&&&& 由于 windows 和 linux 的平台不同(主要是编译器、汇编器和连接器 的不同),因此二者库的二进制是不兼容的。
&&&&&& 本文仅限于介绍 linux 下的库。
1.2、&库的种类
&&&&&&linux&下的库有两种:静态库和共享库(动态库)。
&&&二者的不同点在于代码被载入的时刻不同:
&&&静态库的代码在编译过程中已经被载入可执行程序,因此生成的可执行程序体积较大。静态用.a为后缀,
例如: libhello.a
&& 共享库(动态库)的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此生成的可执行程序代码体积较小。
&&&动态通常用.so为后缀, 例如:libhello.so
&&&&&&共享库(动态库)的好处是::&不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
&&&&&&为了在同一系统中使用不同版本的库,可以在库文件名后加上版本号为后缀,例如: libhello.so.1.0,由于程序连接默认以.so为文件后缀名。所以为了使用这些库,通常使用建立符号连接的方式。
&&&&&&ln -s libhello.so.1.0 libhello.so.1&
&&&&& ln -s libhello.so.1 libhello.so
1.3、静态库,动态库文件在linux下是如何生成的:
以下面的代码为例,生成上面用到的hello库:
/* hello.c */
#include &hello.h&
void sayhello()
printf(&hello,world &);
}首先用gcc编绎该文件,在编绎时可以使用任何合法的编绎参数,例如-g加入调试代码等:gcc -c hello.c -o hello.o1、生成静态库 生成静态库使用ar工具,其实ar是archive的意思ar cqs libhello.a hello.o2、生成动态库 用gcc来完成,由于可能存在多个版本,因此通常指定版本号:gcc -shared -o libhello.so.1.0 hello.o&1.4、库文件是如何命名的,有没有什么规范: 在 linux 下,库文件一般放在/usr/lib和/lib下, 静态库的名字一般为libxxxx.a,其中 xxxx 是该lib的名称;动态库的名字一般为libxxxx.so.major.minor,xxxx 是该lib的名称,major是主版本号,minor是副版本号&1.5、可执行程序在执行的时候如何定位共享库(动态库)文件 :
当系统加载可执行代码(即库文件)的时候,能够知道其所依赖的库的名字,但是还需要知道绝对路径,此时就需要系统动态载入器 (dynamic linker/loader)
对于 elf 格式的可执行程序,是由 ld-linux.so* 来完成的,它先后搜索 elf 文件的 DT_RPATH 段--&环境变量LD_LIBRARY_PATH—-&/etc/ld.so.cache 文件列表--& /lib/,/usr/lib 目录找到库文件后将其载入内存
如: export LD_LIBRARY_PATH=’pwd’
将当前文件目录添加为共享目录。&1.6、使用ldd工具,查看可执行程序依赖那些动态库或着动态库依赖于那些动态库:
ldd 命令可以查看一个可执行程序依赖的共享库,
例如 # ldd /bin/lnlibc.so.6
=& /lib/libc.so.6 (0×)/lib/ld-linux.so.2
=& /lib/ld- linux.so.2 (0×)
可以看到 ln 命令依赖于 libc 库和 ld-linux 库 &1.7、使用nm工具,查看静态库和动态库中有那些函数名;
(T类表示函数是当前库中定义的,U类表示函数是被调用的,在其它库中定义的,W类是当前库中定义,被其它库中的函数覆盖)。:
有时候可能需要查看一个库中到底有哪些函数,nm工具可以打印出库中的涉及到的所有符号,这里的库既可以是静态的也可以是动态的。
nm列出的符号有很多, 常见的有三种::
T类:是在库中定义的函数,用T表示,这是最常见的;
U类:是在库中被调用,但并没有在库中定义(表明需要其他库支持),用U表示;
W类:是所谓的“弱态”符号,它们虽然在库中被定义,但是可能被其他库中的同名符号覆盖,用W表示。
例如,假设开发者希望知道上文提到的hello库中是否引用了 printf():
nm libhello.so | grep printf
发现printf是U类符号,说明printf被引用,但是并没有在库中定义。
由此可以推断,要正常使用hello库,必须有其它库支持,使用ldd工具查看hello依赖于哪些库:
ldd libhello.so
libc.so.6=&/lib/libc.so.6(0x400la000)
/lib/ld-linux.so.2=&/lib/ld-linux.so.2 (0x)
从上面的结果可以继续查看printf最终在哪里被定义,有兴趣可以go on&1.8、使用ar工具,可以生成静态库,同时可以查看静态库中包含那些.o文件,即有那些源文件构成。
可以使用 ar -t libname.a 来查看一个静态库由那些.o文件构成。
可以使用 ar q libname.a xxx1.o
xxx2.o xxx3.o ... xxxn.o 生成静态库&1.9、如何查看动态库和静态库是32位,还是64位下的库:
如果是动态库,可以使用file *.so;
如果是静态哭,可以使用objdump -x *.a&Linux下进行程序设计时,关于库的使用:一、gcc/g++命令中关于库的参数:
-shared: 该选项指定生成动态连接库;
-fPIC:表示编译为位置独立(地址无关)的代码,不用此选项的话,编译后的代码是位置相关的,所以动态载入时,是通过代码拷贝的方式来满足不同进程的需要,而不能达到真正代码段共享的目的。
-L:指定链接库的路径,-L. 表示要连接的库在当前目录中
-ltest:指定链接库的名称为test,编译器查找动态连接库时有隐含的命名规则,即在给出的名字前面加上lib,后面加上.so来确定库的名称
-Wl,-rpath: 记录以来so文件的路径信息。
LD_LIBRARY_PATH:这个环境变量指示动态连接器可以装载动态库的路径。
当然如果有root权限的话,可以修改/etc/ld.so.conf文件,然后调用 /sbin/ldconfig来达到同样的目的,
不过如果没有root权限,那么只能采用修改LD_LIBRARY_PATH环境变量的方法了。 调用动态库的时候,有几个问题会经常碰到:
1、有时,明明已经将库的头文件所在目录 通过 “-I” include进来了,库所在文件通过 “-L”参数引导,并指定了“-l”的库名,但通过ldd命令察看时,就是死活找不到你指定链接的so文件,这时你要作的就是通过修改 LD_LIBRARY_PATH或者/etc/ld.so.conf文件来指定动态库的目录。通常这样做就可以解决库无法链接的问题了。&二、静态库链接时搜索路径的顺序:
1. ld会去找gcc/g++命令中的参数-L;
2. 再找gcc的环境变量LIBRARY_PATH,它指定程序静态链接库文件搜索路径;
export LIBRARY_PATH=$LIBRARY_PATH:data/home/billchen/lib
3. 再找默认库目录 /lib
/usr/local/lib,这是当初compile gcc时写在程序内的。
&三、动态链接时、执行时搜索路径顺序:
1. 编译目标代码时指定的动态库搜索路径;
2. 环境变量LD_LIBRARY_PATH指定动态库搜索路径,它指定程序动态链接库文件搜索路径;
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:data/home/billchen/lib
3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
4. 默认的动态库搜索路径/lib;
5. 默认的动态库搜索路径/usr/lib。 &四、静态库和动态链接库同时存在时,gcc/g++默认链接的是动态库:
当一个库同时存在静态库和动态库时,比如libmysqlclient.a和libmysqlclient.so同时存在时:
在Linux下,动态库和静态库同事存在时,gcc/g++的链接程序,默认链接的动态库。
可以使用下面的方法,给连接器ld传递参数,看是否链接动态库还是静态库。
-Wl,-Bstatic -llibname
//指定让gcc/g++链接静态库
gcc/g++ test.c -o test -Wl,-Bstatic -llibname -Wl,-Bdynamic -lm -lc
-Wl,-Bdynamic -llibname
//指定让gcc/g++链接动态库
gcc/g++ test.c -o test -Wl,-Bdynamic -llibname
如果要完全静态加在,使用-static参数,即将所有的库以静态的方式链入可执行程序,这样生成的可执行程序,不再依赖任何库,同事出现的问题是,这样编译出来的程序非常大,占用空间。如果不适用-Wl,-Bdynamic -lm -c会有如下错误:[chenbaihu@build17 lib]$ ls
libtest.so
[chenbaihu@build17 lib]$ g++ -Wall -g t.cc -o t -L./ -Wl,-Bstatic -ltest -Wl,-Bdynamic -lm -lc
[chenbaihu@build17 lib]$ g++ -Wall -g t.cc -o t -L./ -Wl,-Bstatic -ltest
/usr/bin/ld: cannot find -lm
collect2: ld 返回 1参考:
五、有关环境变量:
LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径
LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径 &六、库的依赖问题:
比如我们有一个基础库libbase.a,还有一个依赖libbase.a编译的库,叫做libchild.a;在我们编译程序时,一定要先-lchild再-lbase。&如果使用 -lbase -lchild,在编译时将出现一些函数undefined,而这些函数实际上已经在base中已经定义;
为什么会有库的依赖问题?
一、静态库解析符号引用:
链接器ld是如何使用静态库来解析引用的。在符号解析阶段,链接器从左至右,依次扫描可重定位目标文件(*.o)和静态库(*.a)。
在这个过程中,链接器将维持三个集合:
集合E:可重定位目标文件(*.o文件)的集合。
集合U:未解析(未定义)的符号集,即符号表中UNDEF的符号。
集合D: 已定义的符号集。
初始情况下,E、U、D均为空。
1、对于每个输入文件f,如果是目标文件(.o),则将f加入E,并用f中的符号表修改U、D(在文件f中定义实现的符号是D,在f中引用的符号是U),然后继续下个文件。
2、如果f是一个静态库(.a),那么链接器将尝试匹配U中未解析符号与静态库成员(静态库的成员就是.o文件)定义的符号。如果静态库中某个成员m(某个.o文件)定义了一个符号来解析U中引用,那么将m加入E中,
&& 同时使用m的符号表,来更新U、D。对静态库中所有成员目标文件反复进行该过程,直至U和D不再发生变化。此时,静态库f中任何不包含在E中的成员目标文件都将丢弃,链接器将继续下一个文件。
3、当所有输入文件完成后,如果U非空,链接器则会报错,否则合并和重定位E中目标文件,构建出可执行文件。
到这里,为什么会有库的依赖问题已经得到解答:
&因为libchild.a依赖于libbase.a,但是libbase.a在libchild.a的左边,导致libbase.a中的目标文件(*.o)根本就没有被加载到E中,所以解决方法就是交换两者的顺序。当然也可以使用-lbase -lchild -lbase的方法。参考文章:&七、动态库升级问题:
在动态链接库升级时,
不能使用cp newlib.so oldlib.so,这样有可能会使程序core掉;
而应该使用:
rm oldlib.so 然后 cp newlib.so oldlib.so
mv oldlib.so oldlib.so_bak 然后 cp newlib.so oldlib.so
为什么不能用cp newlib.so oldlib.so ?在替换so文件时,如果在不停程序的情况下,直接用 cp new.so old.so 的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃。解决方法:解决的办法是采用“rm+cp” 或“mv+cp” 来替代直接“cp” 的操作方法。linux系统的动态库有两种使用方法:运行时动态链接库,动态加载库并在程序控制之下使用。1、为什么在不停程序的情况下,直接用 cp 命令替换程序使用的 so 文件,会使程序崩溃?
很多同学在工作中遇到过这样一个问题,在替换 so 文件时,如果在不停程序的情况下,直接用cp new.so old.so的方式替换程序使用的动态库文件会导致正在运行中的程序崩溃,退出。这与 cp 命令的实现有关,cp 并不改变目标文件的 inode,cp 的目标文件会继承被覆盖文件的属性而非源文件。实际上它是这样实现的:
strace cp libnew.so libold.so 2&&1 |grep open.*lib.*.so
open(&libnew.so&, O_RDONLY|O_LARGEFILE) = 3
open(&libold.so&, O_WRONLY|O_TRUNC|O_LARGEFILE) = 4
在 cp 使用“O_WRONLY|O_TRUNC” 打开目标文件时,原 so 文件的镜像被意外的破坏了。这样动态链接器 ld.so 不能访问到 so 文件中的函数入口。从而导致 Segmentation fault,程序崩溃。ld.so 加载 so 文件及“再定位”的机制比较复杂。2、怎样在不停止程序的情况下替换so文件,并且保证程序不会崩溃?
答案是采用“rm+cp” 或“mv+cp” 来替代直接“cp” 的操作方法。
在用新的so文件 libnew.so 替换旧的so文件 libold.so 时,如果采用如下方法:
rm libold.so
//如果内核正在使用libold.so,那么inode节点不会立刻别删除掉。
cp libnew.so libold.so
采用这种方法,目标文件 libold.so 的 inode 其实已经改变了,原来的 libold.so 文件虽然不能用&ls&查看到,但其inode并没有被真正删除,直到内核释放对它的引用。(即: rm libold.so,此时,如果ld.so正在加在libold.so,内核就在引用libold.so的inode节点,rm libold.so的inode并没有被真正删除,当ld.so对libold.so的引用结束,inode才会真正删除。这样程序就不会崩溃,因为它还在使用旧的libold.so,当下次再使用libold.so时,已经被替换,就会使用新的libold.so)
同理,mv只是改变了文件名,其 inode 不变,新文件使用了新的 inode。这样动态链接器 ld.so 仍然使用原来文件的 inode 访问旧的 so 文件。因而程序依然能正常运行。(即: mv libold.so ***后,如果程序使用动态库,还是使用旧的inode节点,当下次再使用libold.so时,就会使用新的libold.so)
到这里,为什么直接使用“cp new_exec_file old_exec_file”这样的命令时,系统会禁止这样的操作,并且给出这样的提示“cp: cannot create regular file `old': Text file busy”。
这时,我们采用的办法仍然是用“rm+cp”或者“mv+cp”来替代直接“cp”,这跟以上提到的so文件的替换有同样的道理。
但是,为什么系统会阻止cp覆盖可执行程序,而不阻止覆盖so文件呢?
这是因为 Linux 有个 Demand Paging 机制,所谓“Demand Paging”,简单的说,就是系统为了节约物理内存开销,并不会程序运行时就将所有页(page)都加载到内存中,而只有在系统有访问需求时才将其加载。“Demand Paging”要求正在运行中的程序镜像(注意,并非文件本身)不被意外修改,因此内核在启动程序后会锁定这个程序镜像的 inode。对于 so 文件,它是靠 ld.so 加载的,而ld.so毕竟也是用户态程序,没有权利去锁定inode,也不应与内核的文件系统底层实现耦合。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:8553次
排名:千里之外
转载:10篇
(1)(1)(1)(4)(5)(6)(1)

我要回帖

更多关于 gcc依赖包 的文章

 

随机推荐