C 高级编程3 静态库与动态库

时间:2021-12-14 14:34:08
http://blog.csdn.net/Lux_Veritas/article/details/11934083
http://www.cnblogs.com/catch/p/3857964.html mmap/munmap工具使用 #include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
main()
{
int *p=mmap(NULL,
getpagesize(), //页的大小
PROT_READ|PROT_WRITE, //读写权限
MAP_ANONYMOUS|MAP_SHARED,,);
*p=;
*(p+)=;
*p(p+)=;
printf("%d\n",p[]);
munmap(p,); //释分配的页 } --------------------------------------
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
main()
{
int *p=mmap(NULL,
getpagesize(),
PROT_READ, //测试只读权限
MAP_ANONYMOUS|MAP_SHARED,,);
*p=;
*(p+)=;
*p(p+)=;
printf("%d\n",p[]);
munmap(p,); //释分配的页 } 段错误
------------------------------------------------------
总结:
选择择什么样的管理方法
C++:
STL :操作方便
new :操作方便
智能指针 C:
malloc :小而多数据(类型多,数据多)
brk/sbrk : (同类型的数据,动态移动指针)
mmap/munmap: 控制内存的访问权限,使用文件映射,控制内存的共享(进程数据间共享) brk/sbrk,mmap/munmap 效率最高的
------------------------------------------------------------------- 编程工具与动态库
.GCC
.MAKE
.GDB
.其他工具
.共享库 gcc:
-o: 输出的文件名
-O,-O0,-O1,-O2,-O3 :编绎优化
-g,-g0,-g1,-g2,-g3 :产生调试信息 越来越大,调试信息越多
-W :取消警告
-Wall 显示所有警告
-Werror:把警告当错误
-c :只编绎不链接:
-E :预编绎 gcc map.c -E -omain.i
-S :汇编 编绎过程:-e -> -c ->-S :自动调用连接器
连接器 :ld -D 在命令行定义宏
在代码中定义宏
在命令令行定义宏
-x 指定编译的语言类型号
c++,c,s,none
gcc -x assembler map.s
gcc -x none map.c //自动 -std: c99,c89
gcc -std=c99 gcc.c -DNUM=
gcc -std=c89 gcc.c -DNUM= ----------------------------------
eg:
gcc.c
int printf(const char *,...);
main()
{
printf("%d\n",NUM); } gcc gcc.c -omain -DNUM= //在命令定义宏 EG2
int add(int * restrict a)
restrict:值放到寄存器,提搞速度,c99可以编绎通过,c89不可以
-----------------------------------
文件扩展名:
.c: c文件
.cpp:c++
.CC:c++
.h 头文件
.hpp 头文件
.a
.o 目标文件
.so :动态链接库
.i 预编绎文件
.s 汇编 示例:
gcc map.c -omain -O
gcc map.c -omain -O0
gcc map.c -omain -O1
gcc map.c -omain -O2
gcc map.c -omain -O3 gcc map.c -omain -w
gcc map.c -omain -Wall
gcc map.c -omain -Werror
---------------------------------------------- 三 .静态库的编绎
.编绎过程(*.a)
1.1.编绎成目标文件
-static 可选
gcc -c -static <code>
1.2.归档成静态库
ar工具
ar -r 静态库文件 被归档的文件
ar -r ku.a ku1.o ku2.o
ar -t ku.a //查看有那些目标文件 nm工具:(查看函数符号表)
nm ku.a
nm 表态库/动态库/目标文件/执行文件 总结:
什么是库?
.函数封装的的二进制已经编译的归档中
.ar归档工具
.才用库方式管理代码优点
容易组织代码
复用
保护代码版
.静态库的静态的含义:
编译好的程序运行时候不依赖库
库作用为程序的一部分编译连接
.表态库本质:
就是目标文件的集合(归档文件) .-static 可选 .库的规范与约定
库命令名规则
lib<库名>.a.主版本号.次版本号.批号
lib<库名>.a eg:
ar -r libdemo1.a optool.o graphic.o 库的使用规则:标准使用方法
-l:库名
-L:库目录
gcc main.c -omain -ldemo2 -L. //静态库
//在当前目录搜索libdemo2.a这个文件
. eg: ku1.c
int add(int ,a int b)
{
return a+b; } ku2.c
int sub( int a,int b)
{ return a-b; } callku.c
main()
{ int r=add(,);
int s=sub(,); } gcc -static -c ku1.c
ku1.o gcc -static -c ku2.c
ku2.o ar -r ku.a ku1.o ku2.o gcc callku.c ku.a -omain //非标准库调用 //ku.a静态库
----------------------------------------------------------- 四.动态库的编绎
.什么是动态库?(共享库)
动态库是可以执行,静态库不能执行
但动态库没有main,不能独立执行
动态库不会连接成程序的一部分
程序执行时候,必须需要动态库文件 .工具
ldd 察看程序使用的动态库(只可以查看可执行文件)
eg:
ldd main nm 查看库中的函数符号 readelf -h main 查看执行程序头信息 elf格式 [root@monitor ~]# readelf -h a.out
ELF Header:
Magic: 7f 4c
Class: ELF64
Data: 's complement, little endian
Version: (current)
OS/ABI: UNIX - System V
ABI Version:
Type: EXEC (Executable file)
Machine: Advanced Micro Devices X86-
Version: 0x1
Entry point address: 0x400500
Start of program headers: (bytes into file)
Start of section headers: (bytes into file)
Flags: 0x0
Size of this header: (bytes)
Size of program headers: (bytes)
Number of program headers:
Size of section headers: (bytes)
Number of section headers:
Section header string table index: .动态库的编译
3.1.编绎
-c -fpic(可选) gcc -c -fpic iotool.c
gcc -c -fpic graphic.c .gcc -shared -odemo3.so iotool.o graphic.o 非标准动态库名
.gcc -shared -olibdemo4.so iotool.o graphic.o 标准动态库名 4使用动态库
gcc 代码 动态库文件名
gcc code -l库名 -L动态库所在路径
3.2
gcc -c -fpic iotool.c
gcc -c -fpic graphic.c
gcc -shared -olibdemo4.so iotool.o graphic.o
gcc main.c -ldemo4 -L. -omain ldd main
结果:
linux-gate.so.
libdemo4.so=>not found
libc.so.
/lib/ld-linux.so. ./main报错 解决方法:
export LD_LIBRARY_PATH=.:~:..:~soft01 //只对当前shell有效 表示到这些目录去找 ~soft01:指定soft01用户的主目录
~ :当前用户主目录 库标准命令名规则:
lib库名.so
lib库名.a
-l 库名 -L 库所在路径 问题:
4.1.执行程序怎么加载动态库?
见(动态库的加载) 4.2.动态库没有作为执行程序的一部分,为什么连接需要指定动态库以及目标录?
连接器需要确定函数在动态库的偏移的位置 动态库的加载:
.找到动态库(系统对动态库查找规则)
.加载动态库到内存
.映射到用户的内存空间 系统对动态库查找规则
./lib :需要ROOT权限把动态库考到该目录
./usr/lib :需要ROOT权限把动态库考到该目录
.到环境变量LD_LIBRARY_PATH指定的路径中查找
如果没有root 权限,可以在LD_LIBRARY_PATH指定的路径中查找
export LD_LIBRARY_PATH=.:~:..:~soft01 缓冲机制:
把/lib:/usr/lib:LD_LIBRARY_PATH加载到内存缓存中,加速读取,不到硬盘查找 /sbin/ldconfig -v 刷新缓冲中so的搜索路径 /lib,/usr/lib
eg:
/sbin/ldconfig -v |grep "libdl.so"
-v:
用此选项时,ldconfig将显示正在扫描的目录及搜索到的动态链接库,还有它所创建的连接的名字. .使用libdl.so 库 位置:/usr/lib/libdl.so
动态库加载原理
http://www.ibm.com/developerworks/cn/linux/l-dynamic-libraries/
-----------------------------------------------------------
[root@monitor ~]# gcc test.c -m32 -o test
[root@monitor ~]# ldd test
linux-gate.so. => (0x00d28000)
libc.so. => /lib/libc.so. (0x00194000) //C语言标准库
/lib/ld-linux.so. (0x00ba3000) [root@monitor ~]# /lib/ld-linux.so. ./test -----------------------------------------------------------------
[root@monitor ~]# gcc test.c -o test
[root@monitor ~]# ldd test
linux-vdso.so. => (0x00007fff629ff000)
libc.so. => /lib64/libc.so. (0x0000003c4ec00000) //C语言标准库
/lib64/ld-linux-x86-.so. (0x0000003c4e400000) [root@monitor ~]# /lib64/ld-linux-x86-.so. ./test
------------------------------------------------------------------ 动态库中函数的查找已经封装成库libdl.so,用户写代码加载动态库
dlopen 打开一个动态库 (flag:RTLD_LAZYRTLD_NOW)
dlsym 在打开的动态库中找一个函数
dlclose 关闭动态库
dlerror eg:
dldemo.c :动态库用代码加载加载演示
#include <dlfcn.h>
main()
{
void *handle=dlopen("./libdemo4.so",RTLD_LAZY);
void (*fun)(int)=dlsym(handle,"diamond");
fun();
dllclose(handle);
} gcc -dldemo.c -omain -ldl -L/usr/lib 没有指定-ldemo4
gcc -dldemo.c -omain -ldl //-L/usr/lib 不指定也可以
./main ldd main
nm libdemo4.so 查函数列表 man:
SYNOPSIS
#include <dlfcn.h> void *dlopen(const char *filename, int flag); char *dlerror(void); void *dlsym(void *handle, const char *symbol); int dlclose(void *handle); Link with -ldl. 总结:
编绎连接动态库
使用动态库
怎么配置于让程序调用动态库
常握工具:nm ldd lddconfig objdump strip eg:
strip libdemo4.so 删除多的信息
nm libdemo4.so .工具make使用
make 编绎脚本解释
编译脚本Makefile make -f 脚本文件 目标 脚本文件:
.文本文件 demo.mk
.语法
基本单位目标target
目标名:依赖目标
\tab目标指令
\tab目标指令
\tab目标指令
\tab目标指令
...
--------------------------------------------------
demo.mk
---------------------------------------------------
demo:iotool.c graphic.c main.c
gcc iotool.c -c //以tab健开始
gcc graphic.c -c //以tab健开始
gcc iotool.o graphic.o -shared -olibdemo.so //以tab健开始
gcc main.c -ldemo -omain -L. //以tab健开始
----------------------------------------------------
make -f demo.mk demo
----------------------------------------------------
./main
---------------------------------------------------- ldconfig命令: ldconfig为在命令行中说明的目录或文件/etc/ld.so.config中指定的目录或一些可信任的目录
(象/usr/lib, /lib)中的最新的动态连接库创建必要的连接和绶存。这些绶存起来的数据会被动态
连接器ld.so 或 ld-linux.so所使用。ldconfig会检查它所遇到的动态库文件的名称与版本号,
以决定那些动态库的连接要进行更新。 ldconfig会尝试推断ELF类型库(象libc5, libc6/glibc)是基于那一个版本的C库的。当然,在创建
动态库的时候,最好是明确的指定出使用C库(使用 -lc) 一些已经存在的动态库的能提供的信息,不足以使ldconfig来推断出它们的类型。因此,/etc/ld.so.config
文件格式允许进行明确的说明。这只是对于我们不能计算出类型的ELF库才有用。文件的格式是"dirname=TYPE",
TYPE可以是libc4, libc5, libc6. (这个语法在命令行上也可以使用). 空格是的禁止的。可以
参见-p 选项。ldconfig通常只能由超级管理员使用。因为它可能会修改一些root拥有的目录和文件。 选项: -v 详细模式。打印当前版本,扫描的目录,创建的连接。 覆盖安静模式。 -n 只处理在命令行上指定的目录。不处理可信息目录(/lib, 或 /usr/lib)和在 /etc/ld.so.config
中指定的目录。暗含 -N 选项 -N 不重建绶存。除非使用了 -X 选项,否则连接还是会更新的。 -X 不更新连接。除非使用了 -N 选项,否则绶存还是会更新的。 -f conf
使用conf 代替 /etc/ld.so.conf -C cache
使用cache 代替 /etc/ld.so.cache -r root
转换到并使用root -l 库模式。手动连接单个库。 建议专家级才使用。 -p 打印在绶存中的目录和候选库。 文件: /lib/ld-linux.so.2
运行时库加载器 /etc/ld.so.conf
由逗号、空格、制表符、换行符或引号分隔的目录列表。ld将会在这些目录中查找连接库。 /etc/ld.so.cache
包含了在/etc/ld.so.conf中指定的目录中查找到所有连接库。按顺序存储。

在基于 GNU glibc 的系统上,包括所有 linux 系统,ELF 可执行二进制文件的运行自动导致程序加载器被加载并且运行。

在 linux 下,加载器是 /lib/ld-linux.so.X(X是版本号)。然后加载器搜索、加载程序所要使用的动态链接库。

被搜索的文件夹列表保存在文件 /etc/ld.so.conf 里。

在程序启动的时候搜索这些文件夹是很没有效率的,所以实际上使用缓存。ldconfig(8) 默认读取 /etc/ld.so.conf 文件,在 DLL 文件夹里创建合适的符号链接,在 /etc/ld.so.cache 里写入一个缓存。缓存大大加速了库的读取。所以,当一个 DLL 被添加、删除时,或DLL文件夹被改变时都需要运行 ldconfig 程序,当安装了一个新的 DLL 时,由软件包管理器自动运行 ldconfig。当程序启动时,装载器实际使用的是缓存。

------------------------------------------------------------------------------------------------------------------------------------------------------------


1. 库的命名习惯

一个linux DLL 有三个不同名字的文件组成

soname 文件


lib + 链接库名字 + .so + .版本号

每当链接库接口改变时都递增版本号。soname 文件其实只是一个符号链接而已,指向他的real name 文件。

real name 文件


lib + 链接库名字 + .so + .版本号.次版本号.发行号

发行号是可选的。该文件包含实际代码。

linker name 文件


lib + 链接库名字 + .so

编译器以这个名字来请求指定的链接库。

当程序在内部列出所需要的链接库时,仅仅使用 soname。当你创建一个链接库时,使用 real name。安装一个新的链接库时,把它复制到一个DLL文件夹里,然后运行程序 ldconfig(8)。ldconfig 检查存在的 real name 文件,并且创建指向它的符号链接 soname 文件。ldconfig 还做一件事情就是建立 cache 文件 /etc/ld.so.cache

ldconfig 不会创建 linker name 文件,但是一般性 linker name 文件在安装链接库的时候创建。linker name 文件也只是一个符号链接,指向最新的 soname 文件或 real name 文件。建议指向 soname 文件,因为当你更新库以后,在编译器链接的时候,一般总是想使用新的库。

2. 库的放置

DLL 必须放置在文件系统的指定位置。多数开源软件遵守GNU 标准:当分发源代码的时候,库默认安装在 /usr/local/lib,命令安装在 /usr/local/bin。该标准还定义了如何重写这些默认标准以及如何调用安装程序。

Filesystem Hierarchy Standard(FHS) 规定:多数库应安装在 /usr/lib,启动时需要的库安装在 /lib,非系统库应安装在 /usr/local/lib

GNU 标准是针对开发人员的,FHS 是针对发行者的。

二、 库是如何被使用的

在基于 GNU glibc 的系统上,包括所有 linux 系统,ELF 可执行二进制文件的运行自动导致程序加载器被加载并且运行。在 linux 下,加载器是 /lib/ld-linux.so.X(X是版本号)。然后加载器搜索、加载程序所要使用的动态链接库。

被搜索的文件夹列表保存在文件 /etc/ld.so.conf 里。

在程序启动的时候搜索这些文件夹是很没有效率的,所以实际上使用缓存。ldconfig(8) 默认读取 /etc/ld.so.conf 文件,在 DLL 文件夹里创建合适的符号链接,在 /etc/ld.so.cache 里写入一个缓存。缓存大大加速了库的读取。所以,当一个 DLL 被添加、删除时,或DLL文件夹被改变时都需要运行 ldconfig 程序,当安装了一个新的 DLL 时,由软件包管理器自动运行 ldconfig。当程序启动时,装载器实际使用的是缓存。

环境变量

LD_LIBRARY_PATH
该变量里所指定的文件夹将会首先被搜索,然后才会搜索默认的 DLL 文件夹。该变量对开发和测试比较有用,但不应该为了给普通用户使用而设置。如果你不想设置该变量,在 linux 下你可以直接调用程序加载器,比如,你可以传递 PATH 参数给加载器代替该变量来运行程序:


/lib/ld-linux.so.2 --library-path PATH EXECUTABLE

不带参数执行加载器,可以得到更多帮助。但是,不要这样执行程序,仅供调试时使用。

LD_DEBUG
看名字就知道,是供调试使用的。该变量是dl*函数的开关,用来显示正在做的事情的详细信息。可以取值为:


files 显示so文件的加载顺序
bindings 显示关于符号帮定的信息
libs 显示库搜索路径的信息
versions 显示版本依赖的信息
help 使用该值运行程序将会显示可用的选项

三、创建动态链接库


首先用 -fpic 或 -fPIC 选项创建要放入 DLL 中的目标文件。使用该选项生成的代码是位置无关的代码(DLL的必要条件)。使用 gcc 的 -Wl 选项传递 soname 参数给链接器。-Wl 选项里不能有未转义的 whitespace。使用如下命令创建 DLL:


gcc -shared -Wl,-soname,your_soname /
    -o library_name file_list library_list

举个例子:


gcc -fPIC -g -c -Wall a.c
gcc -fPIC -g -c -Wall b.c
gcc -shared -Wl,-soname,libmystuff.so.1 /
    -o libmystuff.so.1.0.1 a.o b.o -lc

该例子首先创建了两个与位置无关的目标文件 a.o、b.o,然后生成了一个包含两者的 DLL。注意:-g 选项使代码包含调试信息,-Wall 选项用来产生警告,两者并不是创建 DLL 必须的,但是建议加上。

不要 strip 所生成的 DLL,或使用编译器参数 -fomit-frame-pointer,这样做将会无法使用调试器。

-fPIC 选项总是可以使用,但生成的代码比使用 -fpic 的要大。-fpic 选项生成的代码比较小、快,但是有平台相关的限制,当创建 DLL 时,链接器将会告诉你是否符合限制。

链接器还有一个有用的选项 -rpath,可以用来指定程序在运行时搜索DLL时的路径,使用 gcc 时可以这样传递参数给链接器:


-Wl,-rpath,$(DEFAULT_LIB_INSTALL_PATH)

如果你使用了这个选项,就不用考虑 LD_LIBRARY_PATH 这个环境变量了。



四、安装、使用动态链接库


1.安装在标准位置
最简单的安装方式是复制 DLL 到一个标准的 DLL 文件夹(/usr/lib等)并且运行 ldconfig(8),然后手动创建 linker name 符号链接。

2.安装在非标准位置
下面的命令将会在指定的文件夹里创建适当的 soname 符号链接。


ldconfig -n directory_with_shared_libraries

然后手动创建 linker name 文件指向 soname 文件。


编译程序的时候使用 -l、-L 参数指定需要链接的库和库所在的位置。
除非使用 -rpath 参数指定过运行时库搜索路径,否则在运行时也必须指定。(所以在eclipse中运行称找不到liqwt.so.5)


比如可以使用如下命令添加当前工作目录到 LD_LIBRARY_PATH 来运行程序:


LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH  my_program

ldd 命令可以用来查看程序的依赖,例如:


ldd /bin/ls
输出的是 soname 列表