linux下 GCC编译链接静态库&动态库
静态库
有时候需要把一组代码编译成一个库,这个库在很多项目中都要用到,例如libc就是这样一个库, 我们在不同的程序中都会用到libc中的库函数(例如printf),也会用到libc中的变量(例如以后 要讲到的environ变量)。本文将介绍怎么创建这样一个库。
这些文件的目录结构是:
$ tree
.
|-- main.c
`-- stack
|-- is_empty.c
|-- pop.c
|-- push.c
|-- stack.c
`-- stack.h
1 directory, 6 files
我们把stack.c、push.c、pop.c、is_empty.c编译成目标文件:
$ gcc -c stack/stack.c stack/push.c stack/pop.c stack/is_empty.c
然后打包成一个静态库libstack.a:
$ ar rs libstack.a stack.o push.o pop.o is_empty.o
ar: creating libstack.a
库文件名都是以lib开头的,静态库以.a作为后缀,表示Archive。ar命令类似于tar命令,起一个打包的作用,但是把目标文件打包成静态库只能用ar命令而不能用tar命令。选项r表示将后面的文件列表添加到文件包,如果文件包不存在就创建它,如果文件包中已有同名文件就替换成新的。s是专用于生成静态库的,表示为静态库创建索引,这个索引被链接器使用。ranlib命令也可以为静态库创建索引,以上命令等价于:
$ ar r libstack.a stack.o push.o pop.o is_empty.o
$ ranlib libstack.a
然后我们把libstack.a和main.c编译链接在一起:
$ gcc main.c -L. -lstack -Istack -o main
-L选项告诉编译器去哪里找需要的库文件,-L.表示在当前目录找。-lstack告诉编译器要链 接libstack库,-I选项告诉编译器去哪里找头文件。注意,即使库文件就在当前目录,编译器默认 也不会去找的,所以-L.选项不能少。编译器默认会找的目录可以用-print-search-dirs选项查看。编译器会在这些搜索路径以及-L选项指定的路径中查找用-l选项指定的库,比如-lstack,编译器会首先找有没有共享库libstack.so,如果有就链接它,如果没有就找有没有静态库libstack.a,如果有就链接它。所以编译器是优先考虑共享库的,如果希望编译器只链接静态库,可以指定-static选项。
动态库(共享库)
组成共享库的目标文件和一般的目标文件有所不同,在编译时要加-fPIC选项,例如:
$ gcc -c -fPIC stack/stack.c stack/push.c stack/pop.c stack/is_empty.c
-f后面跟一些编译选项,PIC是其中一种,表示生成位置无关代码(Position Independent Code)。
现在把main.c和共享库编译链接在一起,然后运行:
$ gcc main.c -g -L. -lstack -Istack -o main
$ ./main
./main: error while loading shared libraries: libstack.so: cannot open shared object file: No such file or directory
结果出乎意料,编译的时候没问题,由于指定了-L.选项,编译器可以在当前目录下找到libstack.so,而运行时却说找不到libstack.so。那么运行时在哪些路径下找共享库呢?我们先用ldd命令查看可执行文件依赖于哪些共享库:
$ ldd main
linux-gate.so.1 => (0xb7f5c000)
libstack.so => not found
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7dcf000)
/lib/ld-linux.so.2 (0xb7f42000)
ldd模拟运行一遍main,在运行过程中做动态链接,从而得知这个可执行文件依赖于哪些共享库,每个共享库都在什么路径下,加载到进程地址空间的什么地址。/lib/ld-linux.so.2是动态链接器,它的路径是在编译链接时指定的,gcc在做链接时用dynamic-linker指定动态链接器的路径,它也像其它共享库一样加载到进程的地址空间中。libc.so.6的路径/lib/tls/i686/cmov/libc.so.6是由动态链接器ld-linux.so.2在做动态链接时搜索到的,而libstack.so的路径没有找到。linux-gate.so.1这个共享库其实并不存在于文件系统中,它是由内核虚拟出来的共享库,所以它没有对应的路径,它负责处理系统调用。总之,共享库的搜索路径由动态链接器决定,从ld.so(8)的Man Page可以查到共享库路径的搜索顺序:
- 首先在环境变量LD_LIBRARY_PATH所记录的路径中查找。
- 然后从缓存文件/etc/ld.so.cache中查找。这个缓存文件由ldconfig命令读取配置文 件/etc/ld.so.conf之后生成,稍后详细解释。
- 如果上述步骤都找不到,则到默认的系统路径中查找,先是/usr/lib然后是/lib。
先试试第一种方法,在运行main时通过环境变量LD_LIBRARY_PATH把当前目录添加到共享库的搜索路径:
$ LD_LIBRARY_PATH=. ./main
这种方法只适合在开发中临时用一下,通常LD_LIBRARY_PATH是不推荐使用的,尽量不要设置这个环境变量,理由可以参考Why LD_LIBRARY_PATH is bad。
再试试第二种方法,这是最常用的方法。把libstack.so所在目录的绝对路径(比如/home/akaedu/somedir)添加到/etc/ld.so.conf中(该文件中每个路径占一行),然后运行ldconfig:
$ sudo ldconfig -v
ldconfig命令除了处理/etc/ld.so.conf中配置的目录之外,还处理一些默认目录,如/lib、/usr/lib等,处理之后生成/etc/ld.so.cache缓存文件,动态链接器就从这个缓存中搜索共享库。现在再用ldd命令查看,libstack.so就能找到了:
$ ldd main
linux-gate.so.1 => (0xb809c000)
libstack.so => /home/akaedu/somedir/libstack.so (0xb806a000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7f0c000)
/lib/ld-linux.so.2 (0xb8082000)
第三种方法就是把libstack.so拷到/usr/lib或/lib目录,这样可以确保动态链接器能找到这个共享库。
其实还有第四种方法,在编译可执行文件main的时候就把libstack.so的路径写死在可执行文件中:
$ gcc main.c -g -L. -lstack -Istack -o main -Wl,rpath,/home/akaedu/somedir
-Wl,-rpath,/home/akaedu/somedir表示-rpath /home/akaedu/somedir是由gcc传递给链接器的选项。可以看到readelf的结果多了一条rpath记录:
$ readelf -a main
...
Dynamic section at offset 0xf10 contains 23 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library:
[libstack.so]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000f (RPATH) Library rpath: [/home/akaedu/somedir]
...
还可以看出,可执行文件运行时需要哪些共享库也都记录在.dynamic段中。当然rpath这种办法也是不推荐的,把共享库的路径定死了,失去了灵活性。
甚至还可以这样写:
$ gcc -o main main.c -g -L. -lstack -Istack ./stack/libstack.so
参考资料:[linux_C编程一站式学习](https://book.douban.com/subject/4141733/)
linux下 GCC编译链接静态库&动态库的更多相关文章
- 从四个问题透析Linux下C++编译&链接
摘要:编译&链接对C&C++程序员既熟悉又陌生,熟悉在于每份代码都要经历编译&链接过程,陌生在于大部分人并不会刻意关注编译&链接的原理.本文通过开发过程中碰到的四个典型 ...
- Linux 下 GCC 编译共享库控制导出函数的方法
通过一些实际项目的开发,发现这样一个现象,在 Windows 下可以通过指定 __declspec(dllexport) 定义来控制 DLL(动态链接库)中哪些函数可以导出,暴露给其他程序链接使用,哪 ...
- Linux下gcc编译控制动态库导出函数小结
根据说明文档“How To Write Shared Libraries"介绍, 有四种方法: 1. 在方法声明定义时,加修饰:__attribute__((visibility(" ...
- linux下gcc编译的参数详细说明
参考网址:1 http://hi.baidu.com/zengzhaonong/item/f1f9383565fa5c302e0f8125 gcc使用方法 汇总 2 http://s99f.blog. ...
- linux下gcc编译多个源文件、gdb的使用方法
一. gcc常用编译命令选项 假设源程序文件名为test.c. 1. 无选项编译链接 用法:#gcc test.c 作用:将test.c预处理.汇编.编译并链接形成可执行文件.这里未指定输出文件,默认 ...
- linux下gcc默认搜索头文件及库文件的路径
一.头文件gcc 在编译时如何去寻找所需要的头文件:※所以header file的搜寻会从-I开始※然后找gcc的环境变量 C_INCLUDE_PATH,CPLUS_INCLUDE_PATH,OBJC ...
- LINUX下PHP编译添加相应的动态扩展模块so(不需要重新编译PHP,以openssl.so为例)
本文转自:原文链接 http://www.cnblogs.com/doseoer/p/4367536.html 网上我看到有很多相关的文章都是简述这个问题的,但毕竟因为LINUX版本众多,很多LIU ...
- Linux 下Python调用C++编写的动态库
在工程中用到使用Python调用C++编写的动态库,结果报如下错误: OSError: ./extract_str.so: undefined symbol: _ZNSt8ios_base4InitD ...
- linux 下gcc 编译结构体问题
最近在linux 学习c语言的编程,发现好多原来在vs 上的在linux 都编译不过去,今天就遇到了一个问题就是结构体的编译的问题, 结构体大概的定义是 struct Node{ int a; int ...
随机推荐
- element-ui的滚动条组件el-scrollbar(官方没有)
<template> <div style="height:600px;"> <el-scrollbar style="height:100 ...
- C#使用HttpWebRequest与HttpWebResponse模拟用户登录
模拟艺龙旅游网登录 想模拟登录,首先整理一下流程 1.通过360浏览器(IE,火狐等等)F12开发人员工具抓到相关数据 2.获取验证码(拿到cookie),登录时也需要使用 3.登录 -------- ...
- 线程间操作无效: 从不是创建控件“button1”的线程访问它。
.net2后是不能跨线程访问控件的.,窗体上的控件是当前线程创建的,当用户异步执行一个方法:在该方法中给窗体上的控件赋值,记住:当执行一个异步委托的时候,其实 就是开了一个线程去执行那个方法,这样就会 ...
- 大数据-05-Spark之读写HBase数据
本文主要来自于 http://dblab.xmu.edu.cn/blog/1316-2/ 谢谢原作者 准备工作一:创建一个HBase表 这里依然是以student表为例进行演示.这里假设你已经成功安装 ...
- 发布网站配置文件和SSL
1.将cert下新建一个文件将所有证书文件放在新建的文件下 例如:cert/medcard 2.配置网站的.conf文件 <VirtualHost *:443> ServerName ww ...
- VS2010和选中代码相同的代码的颜色设置,修改高亮颜色
使用Visual Studio 2010, 发现很难看清非活动的选中代码,研究了下,发现以下方法可以设置: 1. 菜单:工具 -> 选项 ->环境 ->字体和颜色 2. 在右边 ...
- 基于AOP注解实现业务功能的动态配置
一.导入jar包 <dependency><!-- 4.引入AOP--> <groupId>org.springframework.boot</groupId ...
- I/O流的概念和流类库的结构
概念: 程序的输入指的是从输入文件将数据传送给程序,程序的输出指的是从程序将数据传送给输出文件. C++输入输出包含以下三个方面的内容: 1.对系统指定的标准设备的输入和输出.即从键盘输入数据,输出到 ...
- html5-移动端布局模板
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8&qu ...
- CNN那么多的网络有什么区别吗?如何对CNN网络进行修改?
https://www.zhihu.com/question/53727257/answer/136261195 http://blog.csdn.net/csmqq/article/details/ ...