C语言(Head First C)-9_1:静态库与动态库:静态库

来源:互联网 发布:dating付费软件靠谱吗 编辑:程序博客网 时间:2024/04/25 16:22

 该系列文章系个人读书笔记及总结性内容,任何组织和个人不得转载进行商业活动!

 9_1:静态库与动态库:静态库

 

 我们已经见识过标准库的威力了,现在是时候在代码中发挥这种威力了;

 

 本章内容:

     创建自己的库,并在多个程序中复用相同代码;

     通过动态库在运行时共享代码;

 基于此,我们可以写出易于扩展并可以有效管理的代码;

 

 场景:

     还记得之前我们使用过一个加密的函数encrypt();并把它放到了单独的源文件中(头文件声明),这样可以做到在多个程序中使用它;

     现在又写了一个checksum()的函数,可以用于校验字符串是否被篡改;

     加密和防篡改是安全领域很重要的问题;

 (Code9_1)

 9_1-checksum.h

int checksum(char * message);
9_1-checksum.c

/* * 校验字符串是否被串改 */#include "9_1-checksum.h"int checksum(char * message){    int c = 0;    while (*message) {        c += c ^ (int)(*message);        message++;    }    return c;}

9_1-encrypt.h

void encrypt(char * message);
9_1-encrypt.c

/* * 异或加密 */#include <stdio.h>#include "9_1-encrypt.h"void encrypt(char * message){    printf("%s",message);    char c;    while (*message) {        *message = *message ^ 31;//可以对char进行运算,因为他是数值类型        message++;    }}
9_1-demo.c

/* * 加密和防篡改使用 */#include <stdio.h>#include "9_1-checksum.h"#include "9_1-encrypt.h"int main() {        char s[] = "Flower Boy!";    printf("摘要值:%i\n",checksum(s));    encrypt(s);        encrypt(s);    printf("解密后:%s\n",s);    printf("摘要值:%i\n",checksum(s));        return 0;}

 我们先使用命令生成目标文件:

     gcc -c 9_1-checksum.c -o 9_1-checksum.o

     gcc -c 9_1-encrypt.c -o 9_1-encrypt.o

 

 之后链接成可执行文件:

     gcc 9_1-demo.c *.o -o 9_1

 

 之后执行:

     ./9_1

 

 log:

 摘要值:55081

 Flower Boy!Ysphzm?]pf>解密后:Flower Boy!

 摘要值:55081

 

 以上算是对之前内容的一个回顾;

 

 尖括号代表标准头文件:

     我们注意到上面例子中导入头文件的方式有些许不同;

 如果在#include语句中使用<>尖括号,编译器就会在 标准头文件目录 汇总查找头文件,而不是当前目录;

     为了使用本地头文件编译程序,需要使用""双引号;

 

 标准头文件目录在哪里:

     通常类UNIX操作系统(如Mac或Linux),编译器会在以下目录查找头文件:

     /usr/local/include //通常用来存第三方库的头文件;

     /usr/include       //一般用来存操作系统的头文件;(这个路径在我的Mac上就没有)

 

 来看一下我的电脑(Macpro):

 bogon:C_Head First huaqiang$ cd /

 bogon:/ huaqiang$ ls

 Applications            etc

 Library                home

 Network                installer.failurerequests

 System                net

 User Information        private

 Users                sbin

 Volumes                tmp

 bin                usr

 cores                var

 dev

 

 bogon:/ huaqiang$ cd usr/

 bogon:usr huaqiang$ ls

 bin        libexec        sbin        standalone

 lib        local        share

 bogon:usr huaqiang$ cd local

 bogon:local huaqiang$ ls

 CODEOFCONDUCT.md    bin            remotedesktop

 Cellar            etc            sbin

 LICENSE.txt        include            share

 Library            lib            var

 README.md        opt

 

 bogon:local huaqiang$ cd include/

 bogon:include huaqiang$ ls

 google            lzma.h            pcre_stringpiece.h

 libltdl            node            pcrecpp.h

 ltdl.h            pcre.h            pcrecpparg.h

 lzma            pcre_scanner.h        pcreposix.h

 

 如何共享代码:

     如果需要在多个程序中使用相同代码,但这些程序四散在计算中各个角落,不同的文件夹中,我们之前示例的方案就不是太好了,因为需要复制好多的代码道不同程序的文件夹中;

     我们需要共享两类代码:.h头文件和.o目标文件;

 

 来看看方式-先说头文件;

 

 共享.h头文件:

     在多个C项目中共享头文件的方法很多;

 1)把头文件保存在标准目录中:

     把头文件保存到/usr/local/include标准目录中,就可以在源代码中用尖括号包含它们;(因为会搜索这个目录)

     出于安全考虑,有的操作系统会禁止往标准目录中写文件;

 

 2)在include语句中使用完整路径名:

     如果头文件放在了其他地方,可以把目录名加到include语句中;

 如临时建一个test目录,把9_1-checksum.h和9_1-encrypt.h放到里边,在需要导入头文件的地方使用以下的方式:

     #include "/Users/huaqiang/HQDSwiftDemo/C_Head_First/test/9_1-checksum.h"

     #include "/Users/huaqiang/HQDSwiftDemo/C_Head_First/test/9_1-encrypt.h"

 

 3)你可以告诉编译器去哪里找头文件:

    最后一种方式是告诉编译器去哪里找头文件,可以使用gcc -I选项:

    在当前目录新建一个9_1-test文件夹,将9_1-checksum.h 9_1-encrypt.h 移到这个目录下,然后使用gcc -I选项进行编译:

    gcc -I/Users/huaqiang/HQDSwiftDemo/C_Head_First/0907-1/9_1-test 9_1-demo.c 9_1-checksum.c 9_1-encrypt.c -o 9_1 && ./9_1

 我们的代码仍然是可以正常运行的;

 这种方式是:让编译器同时在/test以及标准目录中进行查找:

    -I选项告诉编译器还可以去哪里找头文件;编译器会先检查-I选项中的目录,然后像往常一样查找所有标准目录;

 

 再看一下.o目标文件;

 

 用完整路径名共享.o目标文件:

    可以把.o目标文件放到一个类似共享目录的地方;当编译器编译程序时,只要在目标文件前加上完整路径就行了;

 (Code9_2)

9_2-checksum.h

int checksum(char * message);

9_2-checksum.c

/* * 校验字符串是否被串改 */#include <9_2-checksum.h>int checksum(char * message){    int c = 0;    while (*message) {        c += c ^ (int)(*message);        message++;    }    return c;}

9_2-encrypt.h

void encrypt(char * message);

9_2-encrypt.c
/* * 异或加密 */#include <stdio.h>#include <9_2-encrypt.h>void encrypt(char * message){    printf("%s",message);    char c;    while (*message) {        *message = *message ^ 31;//可以对char进行运算,因为他是数值类型        message++;    }}

9_2-demo.c

/* * 加密和防篡改使用 */#include <stdio.h>#include <9_2-checksum.h>#include <9_2-encrypt.h>int main() {        char s[] = "Flower Boy!";    printf("摘要值:%i\n",checksum(s));    encrypt(s);        encrypt(s);    printf("解密后:%s\n",s);    printf("摘要值:%i\n",checksum(s));        return 0;}


    首先,使用共享头文件的第三种方法,把9_2-checksum.h 9_2-encrypt.h 移到新建目录9_2-my_header_files下;


    然后编译生成目标文件:

    gcc -c 9_2-checksum.c

    gcc -c 9_2-encrypt.c

 

    在我们的示例中9_2-xxxx.h的两个文件放在了目录9_2-my_header_files下,所以可以这样生成目标文件:

    gcc -c 9_2-encrypt.c 9_2-checksum.c -I/Users/huaqiang/HQDSwiftDemo/C_Head_First/0907-1/9_2-my_header_files

 

    然后把9_2-checksum.o 9_2-encrypt.o放到文件夹9_2-my_object_files下;


    重新编译链接程序:

    gcc -I/Users/huaqiang/HQDSwiftDemo/C_Head_First/0907-1/9_2-my_header_files 9_2-demo.c /Users/huaqiang/HQDSwiftDemo/C_Head_First/0907-1/9_2-my_object_files/9_2-checksum.o /Users/huaqiang/HQDSwiftDemo/C_Head_First/0907-1/9_2-my_object_files/9_2-encrypt.o -o 9_2 && ./9_2

log:

 摘要值:55081

 Flower Boy!Ysphzm?]pf>解密后:Flower Boy!

 摘要值:55081

 

 我们看到程序正常运行了;

 我们重新看一下这条命令:

 gcc  -I/.../9_2-my_header_files 9_2-demo.c     //-I选项指定头文件目录

        /.../9_2-my_object_files/9_2-checksum.o //目标文件前加上路径

        /.../9_2-my_object_files/9_2-encrypt.o

 -o 9_2 && ./9_2

 

 小结:

    使用目标文件的完整路径名,就能在多个C项目中共享它们;

    /.../9_2-my_object_files就好比一个中央仓库,专门用来保存目标文件;

 

 不足之处:

    我们看到上边命令长的可以,如果要共享的目标文件很多,这简直就是灾难;

    有没有什么方法可以告诉编译器我想共享一大堆目标文件呢;

 

 创建目标文件存档:

    通过创建目标文件存档,就可以一次告诉编译器一批目标文件;

    把一批目标文件打包在一起就成了存档文件;创建安全代码的存档文件,就可以很方便的在多个项目之间共享代码;

 

 存档中包含多个.o文件:

    和我们平时用的压缩文件类似,其实就是这么简单;

    打开终端,进入库目录,比如我的Mac就是/usr/lib、/usr/local/lib,库代码就存放在这个目录下;(当然,如果有需要,我们可以创建一个自己的库目录,就叫my_lib如何)

    可以看到其中有很多.a存档;我们可以用nm命令查看存档中的内容:nm xxxx.a;

    nm命令列出存档中保存文件的名字(诸多目标文件);

 

示例:

 bogon:lib huaqiang$ nm libprotoc.a

 

 libprotoc.a(code_generator.o):

 0000000000000ae0 s GCC_except_table11

 0000000000000b20 s GCC_except_table12

 0000000000000bb0 s GCC_except_table15

 0000000000000a3c s GCC_except_table3

 0000000000000a88 s GCC_except_table8

 0000000000000ab4 s GCC_except_table9

 U __Unwind_Resume

 U __ZN6google8protobuf16SplitStringUsingERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEPKcPNS1_6vectorIS7_NS5_IS7_EEEE

 0000000000000020 T __ZN6google8protobuf8compiler13CodeGeneratorD0Ev

 ... 

 

 以上只是存档libprotoc.a的一部分:

    我们已经能看到他所包含的一个目标文件:code_generator.o;

    看这个示例可能不太清楚,稍后我们会生成自己的存档,然后再用nm查看,以便我们理解;

 

 在使用.a文件编译程序之前,我们先看看如何在存档中保存目标文件;

 

 用ar命令创建存档:

    存档命令(ar)会在存档文件中保存一批目标文件:我们在9_2-my_object_files目录下将之前生成的两个目标文件进行存档;

    ar -rcs lib9_2-hqsecurity.a 9_2-encrypt.o 9_2-checksum.o 

    

 分析:

    ar:存档;

    rcs:r表示如果.a文件存在就更新他;

        c表示创建存档时不显示反馈信息;

        s告诉ar要在.a文件开头建立索引;

    要创建的.a文件名须是libxxx.a的形式;

    最后跟着的是保存在存档中的诸多文件;

 

 到目前为止为了目录越来越多,我把当前的目录结构放这下边,以便理解:

 (P9_1)

 

 我们使用nm命令查看一下:

 nm lib9_2-hqsecurity.a

 

 lib9_2-hqsecurity.a(9_2-encrypt.o):

 0000000000000000 T _encrypt

 U _printf

 

 lib9_2-hqsecurity.a(9_2-checksum.o):

 0000000000000000 T _checksum

 

 可以清晰的看到存档中包含的目标文件,T _encrypt T _checksum表示各自的目标文件中包含encrypt 和 checksum函数,T是文本的意思;

 

 所有.a文件名都是libxxx.a的形式,这是命名存档的标准方式,存档是静态库(static library),所以以lib开头;

 

 注意:

    必须把存档名命名为libxxx.a,否则编译器找不到它们;

 

 在库目录下保存.a文件:

    可以把存档保存在库目录中,我们已经知道,库目录可以是系统也可以是自定义的,具体由你决定;

 1)把.a文件保存在标准目录中,如/usr/local/lib:

    可以在代码正确运行之后,将存档安装到标准目录中;我们之前看到的/usr/local/lib的目录,就是专门用来放本地自定义库的;

    很多系统,只有管理员才能这样做;

 2)把.a文件放到其他目录:

    如果还在开发阶段,或者不想在系统目录中安装代码,可以创建自己的库目录,例如/my_lib;

 

 最后编译其他程序:

    创建库存档是为了能在其他程序中使用它;

 1)如果把程序安装到标准目录,可以用-l开关编译代码:

 gcc 9_2-demo.c -l9_2-hqsecurity -o 9_2 && ./9_2

 

 分析:

    首先列出必要的源文件;

    如果要使用多个存档,可以设置多个-l;

    9_2-hqsecurity会叫编译器去找一个叫lib9_2-hqsecurity.a的存档;

 

 现在知道为什么要把存档命名为libxxx.a了吧;-l选项后的名字必须与存档名的一部分匹配;

 

 2)如果存档在其他地方,比如我们建的一个目录:/9_2-my_lib;可以用-L选项告诉编译器去哪个目录查找存档:

 bogon:0907-1 huaqiang$ gcc 9_2-demo.c -L/Users/huaqiang/HQDSwiftDemo/C_Head_First/0907-1/9_2-my_lib -l9_2-hqsecurity -I/Users/huaqiang/HQDSwiftDemo/C_Head_First/0907-1/9_2-my_header_files -o 9_2 && ./9_2

 摘要值:55081

 Flower Boy!Ysphzm?]pf>解密后:Flower Boy!

 摘要值:55081

 

 程序编译运行成功了!注意别忘了-I指定头文件的搜索目录;

 源文件结构图:

 (P9_2)

 

 需要注意的是:

    如果存档和源文件都在当前目录下,编译命令应该这样写:

    gcc 9_2-demo.c -I . -L . -l9_2-hqsecurity -o 9_2 && ./9_2

    对应的目录结构如下:

 (P9_3)


    这里的-I其实是需要写的,因为导入头文件的方式使用的是尖括号,需要指定头文件目录;

 

 nm命令补充说明:

    nm命令会告诉你每个.o目标文件的名字,然后列车=出所有目标文件中的名字,如果某个名字前出现了T,就说明它是目标文件中某个函数的名字;

 

 要点:

 -使用尖括号<>,编译器就会从标准目录中读取头文件;

 -常见的标准头文件目录有/usr/include、/usr/local/include等;

 -一个库存档中有多个目标文件;

 -可以使用ar -rcs libarchive.a file0.o  file1.o ... 创建存档;

 -库存档名应以lib开头,以.a结尾;

 -如果想链接一个叫libfred.a的存档,就使用-lfred选项;

 -在gcc命令中,-l标志应该在源代码文件后出现;

 -大多数Unix操作系统,标准库目录有/usr/lib和/usr/local/lib,具体的可以查看编译器文档;

 -ar命令的存档格式在不同系统中有很大不同,并不是说存档格式差异很大,而是目标文件的格式可谓天差地别;

 -可以使用ar -t <文件名>列出存档中的目标文件:

    bogon:9_2-my_lib huaqiang$ ar -t 9_2libhqsecurity.a

    __.SYMDEF SORTED

    9_2-checksum.o

    9_2-encrypt.o

 -存档中目标文件以独立文件的形式保存;ar命令会检查文件类型,并不是任何类型的文件都可以放到存档中;

 -可以使用ar -x libxxx.a xxx.o命令把目标文件从存档中提取出来;

    ar -x 9_2libhqsecurity.a 9_2-encrypt.o

 -之所以生成的存档叫 静态链接,是因为一旦链接以后就不能修改;

 

原创粉丝点击