库的创建与使用

 当一段代码在一个程序中被多次使用的时候,我们可以把他写成函数来调用,当一类代码被多个程序重复使用的时候,我们就可以将其组建成一个库,来实现对这类代码的重复使用。
在对库进行修改的时候应该考虑其兼容性,也就是说,依赖于就的库的软件在将库更新后,这个软件还是可以使用的,不能因为库的更新导致软件的运行失败,否则的话,如果这个库非常的重要,那后果将会是灾难性的。那就意味着软件要重写。听别人说,linux的libc5过渡到libc6的时候是多么的麻烦,因为以来libc5的软件不能运行在libc6的库上,导致许多软件需要重新编译甚至重写。但是,这不能说,库不能被修改,而是说,在对库的修改和升级的时候要注意兼容。
对于库的命名,这里有些约定,但很简单,首先,所有的库都要以lib开头,gcc就依赖于这个约定,当你使用-l这个选项的时候,就会被默认的加上lib这个字符串。还有,以.a结尾表示静态库,以.so结尾的表示是共享库。另外还有就是编号的约定,一般格式为:库名.主版本号.次版本号.补丁级别号。对于编号的增加,约定是:若库达到了不能和前一个版本兼容了,就要升级主版本号,如果仅是增加了新的功能,但有和以前的版本兼容的话,那就是升级次版本号,若只是修正错误,则只需要升级补丁的级别。还有一类很特殊的库,他们以_p或者_g来结尾,通常,以_g结尾的库是调试库,编入了特殊的符号和功能,能够增加采用这个库的程序的调试能力,_p表示代码剖析库,他们包含的代码和符号能够进行复杂的代码剖析和性能分析。记住,当你使用这些特殊的库完成了程序的调试的时候,就要用,正常的库重新将软件进行编译。

对库的操作:nm, ar, ldd, ldconfig
    nm命令
        nm命令用来列出目标文件或者二进制文件所有的符号,他可以用来查看程序到底调用了什么函数,还能用来查看,某一个库中有没有我们所需要的函数。
        nm [option(s)] [file(s)]
        当你不指定文件的时候,nm就会寻找但前目录现有没有a.out文件,若有,则会将该文件作为要查看的文件,否则他会提示
            nm: 'a.out': No such file
        例如:
            #include <stdio.h>

            int i = 5;     
            char s;
            const C = '0';

            void fun(void)
            {
                    printf("hello");
            }
        gcc -c 1.c后将会生成一个1.o的文件。然后我们利用nm工具来查看。
            nm 1.o
            00000000 R C
            00000000 T fun
            00000000 D i  /*有一点不明白就是如果i被初始化为0,则他显示的是B,为初始化的*/
                       U printf
            00000001 C s
        其中的大写字母表示符号的种类,下表给出了他们的意思:
            A  The symbol’s value is absolute, and will not be changed by further linking.
            B  The symbol is in the uninitialized data section (known as BSS)
            C  The symbol is common
            D  The symbol is in the initialized data section.
            G  The symbol is in an initialized data section for small objects.
            I       The symbol is an indirect reference to another symbol
            N   The symbol is a debugging symbol
            R   The symbol is in a read only data section
            S   The symbol is  in  an  uninitialized  data  section  for  small objects
            T   The symbol is in the text (code) section
            U   The symbol is undefined.
            V   The  symbol  is  a  weak object.
            W   The  symbol  is  a  weak  symbol that has not been specifically tagged as a weak object symbol.
            -   The symbol is a stabs symbol in an a.out object file
        还有几个比较常用的选项:
            -A  列出符号名的同时列出他来自那个文件。
            -a  列出所有符号,包括调试符号
            -l  列出对应的行号
            -n  根据符号的地址来排序,默认是按名称来排的
            -u  只列出未定义的符号(同--undefined-only,反--defined-only)
    ar命令
        ar命令使用来建立或修改备份文件,或是从备份文件中抽取文件,当然,也可以把他用在库的创建与修改上,他可以把多个文件按照一定的组织结构组成一个备份文件,而且所有的文件即使在备份文件中,仍然保存着其原来的权限与属性。组成他的文件称为他的member。
        ar [emulation options] [-]{dmpqrstx}[abcfilNoPsSuvV] [member-name] [count] archive-file file...
            ar -M [<mri-script]
        常用的选项:
        d   从库中删除模块
        m   在一个库中移动成员
        p   显示库中指定的成员到标准输出中。
        q   快速追加,增加模块到库的结尾处,并不见查是否替换
        r   在库中插入模块
        t   显示库的模块的清单
        x   提取库中的成员,若不指定,则提取所有的成员
        a   在库中一个已存在的成员的后面添加一个文件
        b   在库中一个已存在的成员的前面添加一个文件
        c   创建一个库,不论库是否已经存在
        f   在库中截断指定的名字
        i   类似b
        N   与count参数一起使用,在库中,有多个相同文件名的时候,指定提取或输出的个数。
        o   当提取成员时,保留成员的原始数据
        P   进行文件明匹配时使用全路径名
        s   写入一个目标文件索引到库中或者更新一个存在的目标文件索引。
        S   不创建目标文件索引
        u   ?
        例如:我们有如下几个程序:
        /*File 1.c*/
        #include "test.h"
        #include <stdio.h>

        void p1(void)
        {
                printf("We are in p1 function\n");
        }
        /*File 2.c*/
        #include "test.h"
        #include <stdio.h>
   
        void p2(void)
        {
                printf("We are in p2 function\n");
        }
        /*File test.h*/
        #ifndef _TEST_
        #define _TEST_

        void p1(void);
        void p2(void);

        #endif
        /*File 3.c*/
        #include "test.h"
        #include <stdio.h>

        int main(void)
        {
                printf("We are in main function\n");
                p1();
                p2();
                return 0;
        }
        他们之间的关系是:3.c掉用了1.c和2.c中的p1,p2函数,首先要编译1.c 2.c:
        $gcc -c 1.c
        $gcc -c 2.c
        然后建立一个库,将产生的1.o 2.o放到库里面去:
        $ar -rcs libtest.a 1.o 2.o
        这就行了,然后使用gcc编译并连接3.c
        $gcc 3.c -static -L. -l test
        我们使用-static是为了将libtest.a和他连接起来,执行:
        $./a.out
        We are in main function
        We are in p1 function
        We are in p2 function
        很明显,程序发的运行结果说明了,我们已经使用libtest.a这个库中的p1,p2函数。
    ldd命令
        将会列出为使程序正常运行所需要的共享库。
            ldd [options] file
            常用的选项,
                -d  执行重定位,并报告所有丢失的函数
                -r  执行对函数和数据对象的重定位,并报告丢失的任何函数或数据
    ldconfig命令:
        他是一个动态连接库的管理命令,能使动态连接库为系统所共享,他的工作是,在默认搜索目录也就是/lib和/usr/lib还有动态库配置文件/etc/ld.so.conf内所记录的目录中,搜索动态连接库,来创建动态装入程序ld.so所需的连接和缓存文件(/etc/ld.so.cache(保存已排序的动态连接库名)),程序连接时首先在cache中找,之后在到ld.so.conf找详细的路径。
    共享库的编写
        创建共享库的办法,和创建静态库的办法有点不同,我们仍然拿上面的程序当作例子:
        1.编译1.o 2.o,注意,这里有不同的地方,再用gcc编译是,要用上-fPIC的选项,这能产生无关的代码,并能被加载到任何地址。
        $gcc -fPIC -c 1.c
        $gcc -fPIC -c 2.c
        2.用ar创建库,注意,这个时候的文件名有所改变
        ar -rcs libtest.so 1.o 2.o
        3.使用gcc的-shared的选项和-soname选项,用-Wl选项把参数传递连接器ld,使用-l选项显示的链接C库,以保证得到所需的启动(startup)代码,从而避免程序在使用不同的,可能不兼容版本的C库的系统上不能运行。
        $ gcc -g -shared -Wl,-soname,libtest.so -o libtest.so.1.0.0 1.o 2.o -lc
        之后会产生一个libtest.so.1.0.0文件,
        我们只是为了测试并不想 把他安装到系统上面,所以要为其建立连接:(用于soname)
        $ ln -s libtest.so.1.0.0 libtest.so.1
        另一个是链接程序在使用-l test连接到libtest使用的:
        $ ln -s libtest.so.1.0.0 libtest.so
        为了使用刚才建立的共享库,我们需要将libtest.so拷贝到/usr/lib的目录下面:
        $cp libtest.so /usr/lib
        然够编译3.c:
        $gcc 3.c -l test
        $./a.out
        We are in main function
        We are in p1 function
        We are in p2 function  
        还有一种方法是把他的路径放到/etc/ld.so.conf文件中,然后以root的身份运行一下ldconfig命令,要说的是,ldconfig在/sbin中。
    dl接口
        加载共享对象:
            void    *   dlopen  (const char *filename, int flag);
        他会以flag指定的模式加载filename指定的对象,若filename是NULL,则dlopen打开当前执行的文件,如果是一个绝对的路径名,dlopen就会打开那个文件,如果仅仅是一个文件名,则dlopen会以下面给定的顺序搜索下列目录,查找文件$LD_ELF_LIBRARY_PATH:
            $LD_LIBRARY_PATH, /etc/ld.so.cache, /usr/lib/, /lib
        参数flag:
            RTLD_LAZY : 来自被加载的对象的符号在被调用时解析
            RTLD_NOW : 来自被加载的对象的所有符号在函数dlopen返回前解析
            如果他们的其中一个 | RTDL_GLOBAL 就会导致导出所有的符号,就像他们被直接链接一样;
        另外,当dlopen成功返回时,返回的是一个句柄,否则返回NULL;
        使用共享对象:
            void *dlsym(void *handle, const char *symbol);
        dlsym在handle中搜索symbol,失败返回NULL;
    错误检查
            char *dlerror(void);
        返回描述最近的发生错误的字符串,没错时返回NULL;
    卸载共享对象:
        int dlclose(void *handle);
    顺便说一下,他们所在的头文件是dlfcn.h
    例如:
        #include <stdio.h>
        #include <dlfcn.h>

        int main(void)
        {
                void * handle;
                void (*pfun)(void);
                char *err;
       
                printf("we are in function main\n");
                if (NULL == (handle = dlopen("./libtest.so",RTLD_NOW)))
                {
                        printf("Failed open libteat\n");
                        return 1;
                }
                dlerror();
                pfun = dlsym(handle,"p1");
                printf("%s\n",((err = dlerror()) == NULL) ? "get p1" : "err");
                (*pfun)();
                pfun = dlsym(handle,"p2");
                printf("%s\n",((err = dlerror()) == NULL) ? "get p2" : "err"); 
                (*pfun)();
                dlclose(handle);
                return 0;
        }
    将其保存为4.c后:
    $gcc -Wall 4.c -l dl
    $./a.out
    we are in function main
    get p1
    We are in p1 function
    get p2
    We are in p2 function
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值