C再学习 06 - 动态链接库

C再学习 06 - 动态链接库

这一章是经常遇到但是没有仔细深究的一个玩意,叫做动态链接库的解说。原来这是C语言里的概念。 库位置与静态链接 一般linux都带有GCC,而windows则不会。最好还是在linux上运行程序。 之前写过的两个解密和生成验证码的程序: //encoder.c #include "encoder.h"

这一章是经常遇到但是没有仔细深究的一个玩意,叫做动态链接库的解说。原来这是C语言里的概念。

库位置与静态链接

一般linux都带有GCC,而windows则不会。最好还是在linux上运行程序。 之前写过的两个解密和生成验证码的程序:
//encoder.c
#include "encoder.h"

void encrypt(char *msg){
    while (*msg) {
        *msg = *msg^31;
        msg++;
    }
}

int checksum(char *msg){
    int c=0;
    while (*msg) {
        c += c ^ (int) (*msg);
        msg++;
    }

    return c;
}
//encoder.h
#ifndef C_ENCODER_H
#define C_ENCODER_H

#endif //C_ENCODER_H
void encrypt(char *msg);
int checksum(char *msg);
在编译的时候,Clion中包含头文件的位置是:
#include "chapter08/encoder.h"
而不能写成#include <encoder.h>,这是因为尖括号中的库有着特殊的路径,CentOS下实验了一下是/usr/include/。 而Windows下是D:\Software\mingw64\x86_64-w64-mingw32\include 如果想让自己的库可以被别的程序共享,有如下几种方法:
  1. 将程序复制到/usr/include这种目录下边
  2. 在程序的目录下边,可以使用相对main函数所在的文件的相对路径下边
  3. gcc -I加上目录,在编译的时候会先检查-I 选项中的目录路径
  4. 用完整的路径共享
一句话,只要能让编译器找到通过encoder.h和encoder.c生成的encoder.o文件,就可以使用其中的文件。

目标文件存档

目标文件存档就是一批目标文件,后缀名以.a结尾。比如在D:\Software\mingw64\x86_64-w64-mingw32\lib中就可以找到很多.a文件。 在CMD中可以用nm命令来查看其中的内容。用ar命令可以保存我们的目标文件到一个库文件中。
ar -rcs libmyencoder.a encoder.o
这样就会生成一个libmyencoder.a的文件,然后将其放入系统的文档存档文件夹中,就可以在编译的时候通过-l带上lib后边的名称来进行编译。
gcc -c main.c -lmyencoder -o main.exe
这其实也是约定俗称的一种指定目标文件的方式。实验了一下,在CentOS7下成功了。 先要将生成的libmyencoder.a放入到/usr/lib中,这个目录也和/lib是同一个目录,表示系统的库。 由于已经不再需要encoder.c/.h/.o文件,可以都删除。然后main.c文件中的#include "encoder.h"这个也要删掉,不然会报找不到文件。 那么现在我们main.c文件中用到的函数从哪里来呢,答案就是通过gcc -l来指定库名称,就会自动从库中寻找函数然后链接。
gcc main.c -lmyencoder -o main
-l后边紧跟的,就是不带lib前缀和.a后缀的刚生成的库文件的名称,这样就成功编译出了可执行文件。 这些.a的库,叫做静态库,静态链接库。使用这些库编译成的软件,编译时候已经使用了某个具体库里的代码,写死了。

动态链接库

动态库相比静态库,也是从一堆O文件中创建,但是有一些额外信息,比如库的名称,操作系统用来确定如何把库链接到程序。 动态库官方名称叫做带有原信息的可重定位目标文件。 我理解了一下,实际上就是运行的时候才会去调用的东西,在exe文件里只保留了一个函数调用,至于调用的时候是什么样子,就看链接进来的库文件是什么样子。 好处就是无需重新编译,只需要修改动态链接库,就可以改变可执行文件的行为。动态链接库会把库名也写入到文件里,所以必须知道库名才能明确的调用。如果库不存在,exe文件就会报错。 下边还是来实验一下: 依然使用上边的encoder系列文件,先把刚才生成的静态库给删除掉。然后把encoder.hencoder.c放到一个文件夹里。这次来编译动态库文件:
gcc -fPIC -c encoder.c -o encoder.o
这里的 -fPIC命令表示编译位置无关的代码,-c表示不要链接文件,-o表示生成目标代码。 然后需要使用这个.o文件生成动态链接库文件:
gcc -shared encoder.o -o libencoder.so
-shared指令加上-o,就会根据目标文件生成动态链接库文件。 然后需要将动态链接库文件放到编译器可以找到的路径中去,有很多种办法,比如指定环境变量,或者放到系统的库路径去。 这里我把libencoder.so还是放到系统的库路径/usr/lib/中去。 现在把main.c文件弄到另外一个目录去,记得也要去掉开头的包含头文件的地方,然后就可以编译了:
gcc main.c -lencoder -o main
这样编译的话,可执行文件main中就会记住动态链接库的名字libencoder.so,然后需要设置一下linux系统到哪里去找动态链接库文件。
export LD_LIBRARY_PATH=/usr/lib:$LD_LIBRARY_PATH
LD_LIBRARY_PATH是环境变量,表示动态库加载的位置,在centos上看了一下,默认是空,这里改成了/usr/lib,然后运行一下./main,发现成功的运行了。 把libencoder.so/usr/lib中删除。再运行一下看看:
./main: error while loading shared libraries: libencoder.so: cannot open shared object file: No such file or directory
会提示找不到库。 现在重新复制进去,运行之后看一下显示:
\pqf?qzz{l?wzm?svkksz?phs1
-558420795
Cony needs her little owl.
460729038
现在我们来修改一下动态链接库文件,原来是与31做异或运算,现在改成与33做异或运算。我们无需编译main.c文件,只要修改encoder.c,然后再按照上述步骤生成动态链接库,覆盖掉上一版。 再运行./main
bNOXODDERIDSMHUUMDNVM
-1850266385
Cony needs her little owl.
460729038
可以看到,程序的结果发生了改变。全程并没有重新编译可执行文件main,而是通过修改动态链接库,让程序执行的时候使用库中的代码。如果还想更改行为,只要热插拔一下dll文件就可以了。

gcc命令

最后复习一下GCC的常用命令吧,首先是编译选项:
  1. gcc test.c -o test,单独使用-o是直接将源文件编译成可执行文件。但其实编译有四个阶段,因此还有其他指令。
  2. gcc -E test.c -o test.i,-E控制着预处理,这样会生成test.i,即经过预处理的文件。
  3. gcc -S test.i -o test.s,-S表示编译成汇编后停止,此时输出文件得到的就是汇编文件
  4. gcc -c test.s -o test.o,-c表示complie,即编译成目标文件。
  5. gcc test.o -o test,链接,对.o文件使用,就是链接o文件,输出就是可执行文件
这里要注意的是,把-o filename看成是输出到什么文件中。而前边的各种参数和无参数表示编译的各个阶段。 对于多个文件(每个文件是一个编译单元),可以对多个文件使用,比如gcc test1.c test2.c -o test,其内部就是把两个编译单元都生成目标文件,然后再链接起来得到可执行文件。 其他选项:
  1. -Wall,输出所有警告信息
  2. -Werror,遇到警告就停止编译,强制要求改正代码
  3. -I,这个参数后边跟着的是路径,表示头文件的所在地
  4. -l,这个参数比较特别,在前边已经知道,-lname表示的是libname.a或者libname.so
  5. -static,这个参数表示强制使用静态库。如果同时存在静态库和动态库,都用-l,到底使用哪一种?GCC默认优先使用动态链接库。
还找了点文章,看这里。 最后放两张图,感觉第二张放在朋友圈里不太好。 3 3
LICENSED UNDER CC BY-NC-SA 4.0
Comment