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

2023-11-02

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

 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

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

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言(Head First C)-9_1:静态库与动态库:静态库 的相关文章

随机推荐

  • 【深度学习】RetinaFace人脸检测简要介绍

    介绍 Insight Face在2019年提出的最新人脸检测模型 原模型使用了deformable convolution和dense regression loss 当时在 WiderFace 数据集上达到SOTA 基网络有三种结构 基于
  • Java使用base64格式上传图片

    使用蚂蚁金服ui直接返回的是base64格式的图片 通过post方式进行请求 然后在控制器中以字符串的形式进行接收 接收之后进行转图片存储处理 只保存路径到数据库中 base64字节转图片代码 package com utils impor
  • qsort函数实现对任意数据的排序

    学会使用qsort函数排序 qsort介绍 compare函数介绍 不同的数据类型相应的比较函数定义 对数组元素为数字的 数组元素为字符时比较函数定义 结构体数据比较函数定义 qsort介绍 qsort函数是一个库函数 它的作用是对数据进行
  • 14.1 矩阵幂级数

    文章目录 矩阵的幂 矩阵幂的极限 谱半径与范数 矩阵幂级数 矩阵的幂 现在讨论下矩阵的n次方的问题 比如下面的矩阵 A 1
  • 医学图像配准MATLAB实现

    医学图像配准MATLAB实现 医学图像处理在临床诊断 肿瘤治疗和医学信息融合等领域中起着至关重要的作用 医学图像配准作为医学图像处理中的重要研究方向之一 其目的是将通过不同机器或技术获取到的不同角度或时间段的医学图像进行对比 以便医生或研究
  • 七种排序算法

    排序算法主要分为三大类 分别是插入排序 选择排序和交换排序 其中插入排序包括直接插入排序和希尔排序 选择排序包括直接选择排序和堆排序 交换排序包括冒泡排序 快速排序和归并排序 各种排序算法的时间复杂度和空间复杂度如下 一 插入排序 1 直接
  • android之fragment与fragment、activity与activity、fragment与activity之间的通信

    Broadcast广播接受者可以实现所有通信 activity与activity之间的通信 当下一个activity关闭时传值给上一个activity 主要用得到startActivityForResult和onActivityResult
  • epoll_create和epoll_create1

    名字 epoll create epoll create1 创建epoll文件描述符 摘要 include
  • 创建自定义类的对象数组

    源代码 public class Student static int number 0 静态变量的访问可以不用创建类的实例就可就可使用 lt 类名 属性 gt 的方法访问 String name 学生姓名 Student 无参构造函数 S
  • 【HDLBits 刷题 12】Circuits(8)Finite State Manchines 27-34

    目录 写在前面 Finite State Manchines 2014 q3c m2014 q6b m2014 q6c m2014 q6 2012 q2fsm 2012 q2b 2013 q2afsm 2013 q2bfsm 写在前面 HD
  • C++中的虚函数表和虚函数在内存中的位置

    目录 结论 今天在看别人面经的时候发现了这个问题 一时间发现自己也说不清楚 还想当然的以为 虚函数表既然是类对象公有的 那么应该在静态存储区 想当然终究只是想当然 经过试验得知 这种想法是错误的 由于不同的编译器在虚函数表上的实现可能不同
  • Jdk1.8新特性 - 方法引用

    一 说明 方法引用使用一对冒号 标识 通过方法的名字来指向一个方法 是函数式接口的另一种书写方式 通过方法引用 可以将方法的引用赋值给一个Function变量 Lambda表达式一般用于自己提供方法体 而方法引用一般直接引用现成的方法 二
  • Linux系统shell脚本之根分区监控

    Linux系统shell脚本之根分区监控 一 脚本要求 二 脚本分析 三 执行脚本 查看执行输出文件 一 脚本要求 1 编写一个shell脚本 脚本名为disk per sh 2 脚本检测根分区使用率 如果根分区超过80 则显示使用率 且提
  • ES6笔记(解构)

    1 解构 解构通俗点说 就是通过一种特定格式 快捷的读取对象 数组中的数据的方法 基本用法 如果右边是对象 左边也要用对象的格式 解构出来就是变量了 再也不是属性了 解构对象 var oUser name aaa age 20 es5读数据
  • [QT_001]解决Ubuntu下Qt无法连接MySQL数据库[Linux环境]

    准备 简要思路 编译Qt下MySQL项目 使其重新生成libqsqlmysql so这个动态库 而后进行替换 编译项目过程中 项目配置文件需要引入使用到MySQL的头文件和库 所以需要安装MySQL 我的环境 1 Ubuntu 18 04
  • 对受控组件和非受控组件的理解,以及应用场景?

    一 受控组件 受控组件 简单来讲 就是受我们控制的组件 组件的状态全程响应外部数据 举个简单的例子 class TestComponent extends React Component constructor props super pr
  • 笨人可以学计算机吗,为什么有的笨人一旦开窍,其人生就像开了挂似的呢?

    前言 真正的天才 是懂得在别人面前装糊涂的人 但如果他受到某种刺激 装糊涂也就没有必要了 低调惯了的人 总有一天 会一鸣惊人 韬盛和夫 大自然有一种特有的现象 自作聪明的动物往往不会活的太长 而那些看起不起眼的动物 往往隐藏着巨大的力量 笨
  • Bootstarp入门教程(6) 表格

    基本案例 为任意 table 标签添加 table可以为其赋予基本的样式 少量的内补 padding 和水平方向的分隔线 table
  • Redis中的事务

    1 Redis事务的定义 Redis事务是一个单独的隔离操作 事务中的所有命令都会序列化 然后按照顺序地执行 事务在执行的过程中 不会被其他客户端发送来的命令请求所打断 其实就是一个缓存队列 将所有任务放入 然后再某一个阶段 将其中的任务拿
  • C语言(Head First C)-9_1:静态库与动态库:静态库

    该系列文章系个人读书笔记及总结性内容 任何组织和个人不得转载进行商业活动 9 1 静态库与动态库 静态库 我们已经见识过标准库的威力了 现在是时候在代码中发挥这种威力了 本章内容 创建自己的库 并在多个程序中复用相同代码 通过动态库在运行时